summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjolsa@redhat.com <jolsa@redhat.com>2011-10-21 11:10:51 +0200
committerJiri Olsa <Jiri Olsa jolsa@redhat.com>2011-11-24 21:20:27 +0100
commit8967d2b09abc5f3e2bace9f6f14e1cfef8e47030 (patch)
treef5d4a59b81327608e08c4f1d7b386b5e81e93ff8
parent27b90e3d32a6c311fca017f55d0fb8e2a1bd451e (diff)
downloadlatrace-8967d2b09abc5f3e2bace9f6f14e1cfef8e47030.tar.gz
latrace-8967d2b09abc5f3e2bace9f6f14e1cfef8e47030.tar.xz
latrace-8967d2b09abc5f3e2bace9f6f14e1cfef8e47030.zip
error simulation: automation application part
-rw-r--r--etc/latrace.d/latrace.conf.in40
-rw-r--r--src/config-bison.y36
-rw-r--r--src/config-flex.l3
-rw-r--r--src/config.c2
-rw-r--r--src/config.h13
-rw-r--r--src/error.c206
-rw-r--r--src/error.h48
-rw-r--r--src/list.h7
-rw-r--r--src/run.c28
9 files changed, 326 insertions, 57 deletions
diff --git a/etc/latrace.d/latrace.conf.in b/etc/latrace.d/latrace.conf.in
index 80c6247..4186882 100644
--- a/etc/latrace.d/latrace.conf.in
+++ b/etc/latrace.d/latrace.conf.in
@@ -127,8 +127,9 @@ OPTIONS {
# }
#
# RETURN R4 {
-# malloc = 100
-# krava = 1
+# malloc = 100, 0, -1
+# krava = 1,2,3
+# krava1 = 0
# FILTER INTERACTIVE
# }
#
@@ -149,10 +150,45 @@ OPTIONS {
# ARGS a a a
# }
#
+# RUN run4 {
+# ARGS a a a
+# }
+#
# GO go1 N=100 run1,run2
# GO go2 run3
+# GO go3 AUTOMATED run4
#}
#
+#
+#ERROR krava {
+# DIR /krava
+# PROG krava
+#
+# RETURN R1 {
+# sym1 = -1
+# sym2 = 0
+# sym1 = NA
+# sym3 = 0
+# sym4 = -1
+# }
+#
+# RETURN R2 {
+# sym1 = -1
+# sym2 = 0
+# sym1 = KEEP
+# sym1 = -2
+# sym3 = 0
+# sym4 = -1
+# }
+#
+# RUN run1 {
+# RETURN R1, R2
+# ARGS a a a
+# }
+#
+# GO go1 AUTOMATED run1
+# GO go2 REPLAY run2
+#}
# directory structure
# -------------------
# DIR/latrace-PROG
diff --git a/src/config-bison.y b/src/config-bison.y
index c9512ac..db1d6bc 100644
--- a/src/config-bison.y
+++ b/src/config-bison.y
@@ -74,7 +74,7 @@ static struct lt_list_head ln_names;
%token ERROR
%token ERR_DO ERR_DIR ERR_RUN ERR_GO ERR_RETURN ERR_N
%token ERR_PROG ERR_ARGS ERR_FILTER ERR_SEQ ERR_START
-%token ERR_SIGSEGV
+%token ERR_SIGSEGV ERR_AUTOMATED ERR_REPLAY ERR_KEEP
%union
{
@@ -347,15 +347,21 @@ error_return: ERR_RETURN NAME '{' error_return_def '}'
}
error_return_def:
-error_return_def NAME '=' VALUE
+error_return_def NAME '=' list_values_comma
{
- if (lt_error_return_ass(scfg, &error_app_return, $2, $4, 0))
+ if (lt_error_return_ass(scfg, &error_app_return, $2, &ln_names, 0, 0))
ABORT("failed to add symbol to return definition");
}
|
-error_return_def NAME '=' VALUE ERR_SIGSEGV
+error_return_def NAME '=' ERR_KEEP
{
- if (lt_error_return_ass(scfg, &error_app_return, $2, $4, 1))
+ if (lt_error_return_ass(scfg, &error_app_return, $2, NULL, 0, 1))
+ ABORT("failed to add symbol to return definition");
+}
+|
+error_return_def NAME '=' list_values_comma ERR_SIGSEGV
+{
+ if (lt_error_return_ass(scfg, &error_app_return, $2, &ln_names, 1, 0))
ABORT("failed to add symbol to return definition");
}
|
@@ -392,21 +398,35 @@ error_go:
ERR_GO NAME list_names_comma
{
if (lt_error_go(scfg, &error_app_go, &error_app,
- strdup($2), 0, 1, &ln_names))
+ strdup($2), 0, 1, 0, 0, &ln_names))
+ ABORT("failed to add go");
+}
+|
+ERR_GO NAME ERR_AUTOMATED list_names_comma
+{
+ if (lt_error_go(scfg, &error_app_go, &error_app,
+ strdup($2), 0, 1, 1, 0, &ln_names))
+ ABORT("failed to add go");
+}
+|
+ERR_GO NAME ERR_REPLAY list_names_comma
+{
+ if (lt_error_go(scfg, &error_app_go, &error_app,
+ strdup($2), 0, 1, 1, 1, &ln_names))
ABORT("failed to add go");
}
|
ERR_GO NAME ERR_N '=' VALUE list_names_comma
{
if (lt_error_go(scfg, &error_app_go, &error_app,
- strdup($2), 0, $5, &ln_names))
+ strdup($2), 0, $5, 0, 0, &ln_names))
ABORT("failed to add go");
}
|
ERR_GO NAME ERR_START '=' VALUE ERR_N '=' VALUE list_names_comma
{
if (lt_error_go(scfg, &error_app_go, &error_app,
- strdup($2), $5, $8, &ln_names))
+ strdup($2), $5, $8, 0, 0, &ln_names))
ABORT("failed to add go");
}
diff --git a/src/config-flex.l b/src/config-flex.l
index 5f86b9f..03d7a1b 100644
--- a/src/config-flex.l
+++ b/src/config-flex.l
@@ -130,6 +130,9 @@ ERROR { BEGIN(error); return ERROR; }
<error>FILTER { return ERR_FILTER; }
<error>SEQ { return ERR_SEQ; }
<error>SIGSEGV { return ERR_SIGSEGV; }
+<error>AUTOMATED { return ERR_AUTOMATED; }
+<error>REPLAY { return ERR_REPLAY; }
+<error>KEEP { return ERR_KEEP; }
<error>{value} { RETURN_LONG(VALUE); }
<error>{name} { RETURN_STR(NAME); }
diff --git a/src/config.c b/src/config.c
index 7b6ecef..9bde8a4 100644
--- a/src/config.c
+++ b/src/config.c
@@ -479,6 +479,8 @@ int lt_config(struct lt_config_app *cfg, int argc, char **argv)
lt_init_list_head(&cfg->error_apps);
lt_init_list_head(&cfg->process_funcs);
+ lt_init_list_head(&cfg->error_symbols_all);
+ lt_init_list_head(&cfg->error_symbols_current);
/* read the default config file first */
if (read_config(cfg, LT_CONF_DIR "/latrace.conf")) {
diff --git a/src/config.h b/src/config.h
index 379c7b8..5e44935 100644
--- a/src/config.h
+++ b/src/config.h
@@ -158,9 +158,6 @@ struct lt_config_shared {
/* for 'not_follow_fork' */
pid_t pid;
- /* error definition (error_sim = 1) */
- struct lt_error_def error_def;
-
/* XXX feel like an idiot.. find another way!!! */
struct lt_config_shared *sh;
};
@@ -197,6 +194,12 @@ struct lt_config_app {
struct lt_error_app *error_app;
/* list of defined errors */
struct lt_list_head error_apps;
+
+ /* error definition (error_sim = 1 in shared config) */
+ struct lt_error_config *error_config;
+
+ struct lt_list_head error_symbols_all;
+ struct lt_list_head error_symbols_current;
};
struct lt_config_ctl {
@@ -255,6 +258,9 @@ struct lt_config_audit {
char *dir;
int init_ok;
+
+ /* error definition (error_sim = 1 in shared config) */
+ struct lt_error_config *error_config;
};
enum {
@@ -438,6 +444,7 @@ void tty_close(struct lt_config_app *cfg);
/* process functions registration */
int lt_process_register(struct lt_config_app *cfg, lt_process_cb cb);
+int lt_process_unregister(struct lt_config_app *cfg, lt_process_cb cb);
/* error simulation app */
int lt_error_app(struct lt_config_app *cfg, struct lt_error_app *error_app);
diff --git a/src/error.c b/src/error.c
index fc13cad..6698ff8 100644
--- a/src/error.c
+++ b/src/error.c
@@ -6,6 +6,7 @@
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
+#include <limits.h>
#include "config.h"
@@ -281,33 +282,46 @@ static int prepare_config_error(struct lt_config_app *cfg,
int n)
{
struct lt_error_app_return *ret;
- struct lt_error_def *error_def = &lt_sh(cfg, error_def);
- int i = 0;
+ struct lt_error_config *cfg_err;
+ int sym_cnt = 0;
- bzero(error_def, sizeof(struct lt_error_def));
+ /* get the nymber of configured symbols */
+ lt_list_for_each_entry(ret, &run->head_return, list_run) {
+ struct lt_error_app_return_sym *sym;
+ lt_list_for_each_entry(sym, &ret->head_sym, list)
+ sym_cnt++;
+ }
+
+ PRINT_VERBOSE(cfg, 1, "number of symbols %d\n", sym_cnt);
+
+ cfg_err = malloc(sizeof(*cfg_err) +
+ sym_cnt * sizeof(struct lt_error_config_sym));
+ if (!cfg_err)
+ return -ENOMEM;
+
+ cfg_err->sym_cnt = sym_cnt;
+ cfg_err->n = n;
+
+ sym_cnt = 0;
lt_list_for_each_entry(ret, &run->head_return, list_run) {
struct lt_error_app_return_sym *sym;
lt_list_for_each_entry(sym, &ret->head_sym, list) {
- struct lt_error_def_sym *sym_def;
+ struct lt_error_config_sym *cfg_sym;
- sym_def = &error_def->sym[i];
- strncpy(sym_def->symbol, sym->name, LT_MAXNAME);
- sym_def->ret = sym->val;
- sym_def->filter.type = ret->filter.type;
- sym_def->handle_sigsegv = sym->handle_sigsegv;
+ cfg_sym = &cfg_err->sym[sym_cnt++];
+ strncpy(cfg_sym->symbol, sym->name, LT_MAXNAME);
+ cfg_sym->ret = sym->val;
+ cfg_sym->filter.type = ret->filter.type;
+ cfg_sym->handle_sigsegv = sym->handle_sigsegv;
PRINT_VERBOSE(cfg, 1, "symbol %s, ret %s\n",
sym->name, sym->val);
-
- if (i++ >= LT_ERROR_MAXSYM)
- return -1;
}
}
- lt_sh(cfg, error_def.n) = n;
- PRINT_VERBOSE(cfg, 1, "N = %d\n", n);
+ cfg->error_config = cfg_err;
return 0;
}
@@ -376,18 +390,82 @@ static int process_run(struct lt_config_app *cfg,
return 0;
}
-static int process_go(struct lt_config_app *cfg, char *dir_base,
- struct lt_error_app *app,
- struct lt_error_app_go *go)
+static int process_automated_cb(struct lt_config_app *cfg,
+ struct lt_thread *t,
+ struct lt_fifo_mbase *mbase)
{
- static char dir[LT_MAXFILE];
- int i;
+ return 0;
+}
- if (!app->no_storage &&
- dir_go(cfg, dir, dir_base, go))
+static int automated_get_all_syms(struct lt_config_app *cfg,
+ struct lt_error_app_go *go)
+{
+ struct lt_list_head *sym_all = &cfg->error_symbols_all;
+ struct lt_error_app_run *run;
+ int count = 0;
+
+ lt_list_for_each_entry(run, &go->head_run, list_go) {
+ struct lt_error_app_return *ret;
+
+ lt_list_for_each_entry(ret, &run->head_return, list_run) {
+ struct lt_error_app_return_sym *sym;
+
+ lt_list_for_each_entry(sym, &ret->head_sym, list) {
+ lt_list_move_tail(&sym->list, sym_all);
+ count++;
+ }
+ }
+ }
+
+ return count;
+}
+
+static int process_go_automated(struct lt_config_app *cfg, char *dir,
+ struct lt_error_app *app,
+ struct lt_error_app_go *go)
+{
+ int ret = 0;
+
+ if (lt_process_register(cfg, process_automated_cb))
return -1;
- PRINT_VERBOSE(cfg, 1, "dir '%s'\n", dir);
+ if (!automated_get_all_syms(cfg, go)) {
+ PRINT_VERBOSE(cfg, 1, "no symbols defined\n");
+ return -1;
+ }
+
+ /* TODO
+ * 1) first run with no symbols
+ * 2) after each run add new symbol to the 'cur' list from global 'all' list
+ * 3) 'cur' list has fresh reallocated symbols giving the position and return
+ * for each symbol occurence
+ * 4) each 'cur' list symbol moves to new return value if all symbols 'below' it
+ * has changed all their return values
+ * 5) finish when first symbol cycles its all return values
+ * 6) repeating till there are new symbols (from 'all' list) visible
+ *
+ */
+
+ BUG_ON(lt_process_unregister(cfg, process_automated_cb));
+
+ return ret;
+}
+
+static int process_go_replay(struct lt_config_app *cfg, char *dir,
+ struct lt_error_app *app,
+ struct lt_error_app_go *go)
+{
+ return 0;
+}
+
+static int process_go_configured(struct lt_config_app *cfg, char *dir,
+ struct lt_error_app *app,
+ struct lt_error_app_go *go)
+{
+ int i;
+
+ PRINT_VERBOSE(cfg, 1, "dir '%s', name %s, start %d, n %d\n",
+ dir, go->name, go->start, go->n);
for(i = go->start; i < (go->start + go->n); i++) {
struct lt_error_app_run *run;
@@ -400,6 +478,28 @@ static int process_go(struct lt_config_app *cfg, char *dir_base,
return 0;
}
+static int process_go(struct lt_config_app *cfg, char *dir_base,
+ struct lt_error_app *app,
+ struct lt_error_app_go *go)
+{
+ static char dir[LT_MAXFILE];
+
+ if (!app->no_storage &&
+ dir_go(cfg, dir, dir_base, go))
+ return -1;
+
+ /* automated processing.. God save us */
+ if (go->automated)
+ return process_go_automated(cfg, dir, app, go);
+
+ /* exact symbols return replay */
+ if (go->replay)
+ return process_go_replay(cfg, dir, app, go);
+
+ /* Whatever user configuration wants.. */
+ return process_go_configured(cfg, dir, app, go);
+}
+
int lt_error_run(struct lt_config_app *cfg)
{
struct lt_error_app *app = cfg->error_app;
@@ -493,7 +593,7 @@ int lt_error_run_return(struct lt_config_app *cfg,
PRINT_VERBOSE(cfg, 1, "return %s, empty %d\n",
ret->name, lt_list_empty(&ret->list_run));
- /* TODO Allow more than 1 RUN assignment to GO,
+ /* TODO Allow more than 1 RETURN assignment to RUN,
* so far only one assignment is allowed. */
if (!lt_list_empty(&ret->list_run))
return -EINVAL;
@@ -558,6 +658,7 @@ int lt_error_go(struct lt_config_app *cfg,
struct lt_error_app_go **go,
struct lt_error_app *error_app,
char *name, int start, int n,
+ int automated, int replay,
struct lt_list_head *runs)
{
struct lt_error_app_go *app_go = *go;
@@ -574,6 +675,8 @@ int lt_error_go(struct lt_config_app *cfg,
app_go->n = n;
app_go->start = start;
app_go->name = name;
+ app_go->automated = automated;
+ app_go->replay = replay;
lt_list_for_each_entry_safe(ln, h, runs, list) {
struct lt_error_app_run *run;
@@ -625,11 +728,13 @@ app_return_get(struct lt_config_app *cfg, struct lt_error_app_return **ret)
int lt_error_return_ass(struct lt_config_app *cfg,
struct lt_error_app_return **ret,
- char *name, unsigned long val,
- int handle_sigsegv)
+ char *name, struct lt_list_head *list_vals,
+ int handle_sigsegv, int keep)
{
struct lt_error_app_return *app_ret = app_return_get(cfg, ret);
struct lt_error_app_return_sym *sym;
+ struct lt_config_ln *ln, *h;
+ int vals_cnt = 0;
if (!app_ret)
return -ENOMEM;
@@ -639,11 +744,41 @@ int lt_error_return_ass(struct lt_config_app *cfg,
return -ENOMEM;
sym->name = strdup(name);
- sym->val = val;
sym->handle_sigsegv = handle_sigsegv;
+ sym->keep = keep;
- PRINT_VERBOSE(cfg, 1, "%s = %ld\n", sym->name, val);
+ /* process return values name list */
+ lt_list_for_each_entry_safe(ln, h, list_vals, list) {
+ if (vals_cnt == LT_MAX_SYM_RETURNS) {
+ PRINT_VERBOSE(cfg, 1,
+ "failed: too many return values for (%d allowed) for symbol %s\n",
+ LT_MAX_SYM_RETURNS, name);
+ return -1;
+ }
+
+ BUG_ON(ln->type != LT_CONFIG_LN_VALUE);
+
+ sym->vals[vals_cnt++] = ln->val;
+
+ lt_list_del(&ln->list);
+ free(ln);
+ }
+
+ PRINT_VERBOSE(cfg, 1, "%s = %ld, handle_sigsegv = %d, keep %d\n",
+ sym->name, sym->val, handle_sigsegv, keep);
+
+ if (vals_cnt > 1) {
+ int i;
+
+ for(i = 0; i < vals_cnt; i++)
+ PRINT_VERBOSE(cfg, 1,
+ "\tret[%d] = %lu\n",
+ i, sym->vals[i]);
+ }
+
+ /* It's essential we keep the user
+ * defined sequenece of symbols. */
lt_list_add_tail(&sym->list, &app_ret->head_sym);
return 0;
}
@@ -673,3 +808,20 @@ int lt_error_app_init(struct lt_error_app *app)
app->no_storage = 1;
return 0;
}
+
+int lt_error_config_write(struct lt_config_app *cfg, int fd)
+{
+ struct lt_error_config *cfg_err = cfg->error_config;
+ int size = sizeof(struct lt_error_config);
+
+ BUG_ON(!cfg_err);
+
+ size += cfg_err->sym_cnt * sizeof(struct lt_error_config_sym);
+
+ if (size != write(fd, cfg_err, size)) {
+ perror("write failed");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/src/error.h b/src/error.h
index 2486549..d22ba4f 100644
--- a/src/error.h
+++ b/src/error.h
@@ -8,24 +8,25 @@ enum {
LT_ERROR_FILTER_TYPE_INTERACTIVE,
};
-struct lt_error_def_filter {
+struct lt_error_config_filter {
int type;
};
-struct lt_error_def_sym {
+struct lt_error_config_sym {
#define LT_MAXNAME 20
char symbol[LT_MAXNAME];
+#define LT_MAXRET 20
long ret;
+ struct lt_error_config_filter filter;
+
int handle_sigsegv;
- struct lt_error_def_filter filter;
+ int keep;
};
-struct lt_error_def {
-#define LT_ERROR_MAXSYM 50
- struct lt_error_def_sym sym[LT_ERROR_MAXSYM];
-
- /* call number */
+struct lt_error_config {
unsigned long n;
+ int sym_cnt;
+ struct lt_error_config_sym sym[0];
};
struct lt_error_audit_filter {
@@ -47,8 +48,18 @@ struct lt_error_sym {
struct lt_error_app_return_sym {
char *name;
- long val;
+
+#define LT_MAX_SYM_RETURNS 10
+ union {
+ long val;
+ long vals[LT_MAX_SYM_RETURNS];
+ };
+
+ /* When the symbol return is changed,
+ * set SIGSEGV handlers. */
int handle_sigsegv;
+ /* Do not change the return value. */
+ int keep;
struct lt_list_head list;
};
@@ -78,6 +89,8 @@ struct lt_error_app_run {
struct lt_error_app_go {
int n;
int start;
+ int automated;
+ int replay;
char *name;
struct lt_list_head head_run;
@@ -97,6 +110,7 @@ struct lt_error_app {
};
struct lt_config_app;
+struct lt_config_audit;
#ifdef CONFIG_ARCH_HAVE_ERROR_SIM
int lt_error_app(struct lt_config_app *cfg,
@@ -105,6 +119,7 @@ int lt_error_go(struct lt_config_app *cfg,
struct lt_error_app_go **go,
struct lt_error_app *error_app,
char *name, int start, int n,
+ int automated, int replay,
struct lt_list_head *runs);
int lt_error_run_return(struct lt_config_app *cfg,
struct lt_error_app_run **run,
@@ -115,13 +130,14 @@ int lt_error_run_args(struct lt_config_app *cfg,
struct lt_list_head *args);
int lt_error_return_ass(struct lt_config_app *cfg,
struct lt_error_app_return **ret,
- char *name, unsigned long val,
- int handle_sigsegv);
+ char *name, struct lt_list_head *list_vals,
+ int handle_sigsegv, int keep);
int lt_error_return_filter(struct lt_config_app *cfg,
struct lt_error_app_return **ret,
int type, void *data);
-
int lt_error_app_init(struct lt_error_app *app);
+int lt_error_config_write(struct lt_config_app *cfg, int fd);
+int lt_error_config_read(struct lt_config_audit *cfg, int fd);
#else
static inline int lt_error_app(struct lt_config_app *cfg,
struct lt_error_app *error_app)
@@ -133,6 +149,7 @@ static inline int lt_error_go(struct lt_config_app *cfg,
struct lt_error_app_go **go,
struct lt_error_app *error_app,
char *name, int start, int n,
+ int automated, int replay,
struct lt_list_head *runs)
{
return -1;
@@ -160,7 +177,8 @@ static inline int lt_error_app_init(struct lt_error_app *app)
static inline int lt_error_return_ass(struct lt_config_app *cfg,
struct lt_error_app_return **ret,
- char *name, unsigned long val)
+ char *name, struct lt_list_head *list_vals,
+ int handle_sigsegv, int keep)
{
return -1;
}
@@ -172,6 +190,10 @@ static inline int lt_error_return_filter(struct lt_config_app *cfg,
return -1;
}
+static inline int lt_error_config_write(struct lt_config_app *cfg, int fd)
+{
+ return 0;
+}
#endif
#endif /* ERROR_H */
diff --git a/src/list.h b/src/list.h
index 98e1c6b..027fcdc 100644
--- a/src/list.h
+++ b/src/list.h
@@ -62,6 +62,13 @@ static inline void lt_list_del(struct lt_list_head *obj)
obj->prev->next = obj->next;
}
+static inline void lt_list_move_tail(struct lt_list_head *list,
+ struct lt_list_head *head)
+{
+ lt_list_del(list);
+ lt_list_add_tail(list, head);
+}
+
static inline int lt_list_empty(struct lt_list_head *head)
{
return head->next == head;
diff --git a/src/run.c b/src/run.c
index 34fccc6..d3f1826 100644
--- a/src/run.c
+++ b/src/run.c
@@ -70,6 +70,10 @@ static int store_config(struct lt_config_app *cfg, char *file)
return -1;
}
+ if (lt_sh(cfg, error_sim) &&
+ lt_error_config_write(cfg, fd))
+ return -1;
+
close(fd);
return 0;
@@ -210,10 +214,6 @@ static int process(struct lt_config_app *cfg, struct lt_process_args *pa)
PRINT_VERBOSE(cfg, 1, "doing pipe\n");
FD_SET(fd_notify, &cfg_set);
max_fd = fd_notify;
-
- /* default fifo processing callback */
- if (lt_process_register(cfg, process_fifo_cb))
- return -1;
}
if (cfg->output_tty) {
@@ -487,6 +487,10 @@ int lt_run(struct lt_config_app *cfg)
if (get_config_dir(pa.dir, LT_MAXFILE))
return -1;
+ /* default fifo processing callback */
+ if (lt_process_register(cfg, process_fifo_cb))
+ return -1;
+
do {
int status;
@@ -524,6 +528,7 @@ int lt_run(struct lt_config_app *cfg)
ret = WEXITSTATUS(status);
} while(0);
+ BUG_ON(lt_process_unregister(cfg, process_fifo_cb));
run_cleanup(cfg, &pa);
return ret;
}
@@ -540,3 +545,18 @@ int lt_process_register(struct lt_config_app *cfg, lt_process_cb cb)
func->cb = cb;
return 0;
}
+
+int lt_process_unregister(struct lt_config_app *cfg, lt_process_cb cb)
+{
+ struct lt_process_func *func;
+
+ lt_list_for_each_entry(func, &cfg->process_funcs, list) {
+ if (cb == func->cb) {
+ lt_list_del(&func->list);
+ free(func);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}