mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-21 16:29:59 +00:00
![Ian Rogers](/assets/img/avatar_default.png)
struct addr_location holds references to multiple reference counted objects. Add init/exit functions to make maintenance of those more consistent with the rest of the code and to try to avoid leaks. Modification of thread reference counts isn't included in this change. Committer notes: I needed to initialize result to sample->ip to make sure is set to something, fixing a compile time error, mostly keeping the previous logic as build_alloc_func_list() already does debugging/error prints about what went wrong if it takes the 'goto out'. Signed-off-by: Ian Rogers <irogers@google.com> Cc: Adrian Hunter <adrian.hunter@intel.com> Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com> Cc: Ali Saidi <alisaidi@amazon.com> Cc: Andi Kleen <ak@linux.intel.com> Cc: Athira Rajeev <atrajeev@linux.vnet.ibm.com> Cc: Brian Robbins <brianrob@linux.microsoft.com> Cc: Changbin Du <changbin.du@huawei.com> Cc: Dmitrii Dolgov <9erthalion6@gmail.com> Cc: Fangrui Song <maskray@google.com> Cc: German Gomez <german.gomez@arm.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Ivan Babrou <ivan@cloudflare.com> Cc: James Clark <james.clark@arm.com> Cc: Jing Zhang <renyu.zj@linux.alibaba.com> Cc: Jiri Olsa <jolsa@kernel.org> Cc: John Garry <john.g.garry@oracle.com> Cc: K Prateek Nayak <kprateek.nayak@amd.com> Cc: Kan Liang <kan.liang@linux.intel.com> Cc: Leo Yan <leo.yan@linaro.org> Cc: Liam Howlett <liam.howlett@oracle.com> Cc: Mark Rutland <mark.rutland@arm.com> Cc: Miguel Ojeda <ojeda@kernel.org> Cc: Mike Leach <mike.leach@linaro.org> Cc: Namhyung Kim <namhyung@kernel.org> Cc: Naveen N. Rao <naveen.n.rao@linux.vnet.ibm.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Ravi Bangoria <ravi.bangoria@amd.com> Cc: Sean Christopherson <seanjc@google.com> Cc: Steinar H. Gunderson <sesse@google.com> Cc: Suzuki Poulouse <suzuki.poulose@arm.com> Cc: Wenyu Liu <liuwenyu7@huawei.com> Cc: Will Deacon <will@kernel.org> Cc: Yang Jihong <yangjihong1@huawei.com> Cc: Ye Xingchen <ye.xingchen@zte.com.cn> Cc: Yuan Can <yuancan@huawei.com> Cc: coresight@lists.linaro.org Cc: linux-arm-kernel@lists.infradead.org Link: https://lore.kernel.org/r/20230608232823.4027869-7-irogers@google.com Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
630 lines
13 KiB
C
630 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* dlfilter.c: Interface to perf script --dlfilter shared object
|
|
* Copyright (c) 2021, Intel Corporation.
|
|
*/
|
|
#include <dlfcn.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <dirent.h>
|
|
#include <subcmd/exec-cmd.h>
|
|
#include <linux/zalloc.h>
|
|
#include <linux/build_bug.h>
|
|
|
|
#include "debug.h"
|
|
#include "event.h"
|
|
#include "evsel.h"
|
|
#include "dso.h"
|
|
#include "map.h"
|
|
#include "thread.h"
|
|
#include "trace-event.h"
|
|
#include "symbol.h"
|
|
#include "srcline.h"
|
|
#include "dlfilter.h"
|
|
#include "../include/perf/perf_dlfilter.h"
|
|
|
|
static void al_to_d_al(struct addr_location *al, struct perf_dlfilter_al *d_al)
|
|
{
|
|
struct symbol *sym = al->sym;
|
|
|
|
d_al->size = sizeof(*d_al);
|
|
if (al->map) {
|
|
struct dso *dso = map__dso(al->map);
|
|
|
|
if (symbol_conf.show_kernel_path && dso->long_name)
|
|
d_al->dso = dso->long_name;
|
|
else
|
|
d_al->dso = dso->name;
|
|
d_al->is_64_bit = dso->is_64_bit;
|
|
d_al->buildid_size = dso->bid.size;
|
|
d_al->buildid = dso->bid.data;
|
|
} else {
|
|
d_al->dso = NULL;
|
|
d_al->is_64_bit = 0;
|
|
d_al->buildid_size = 0;
|
|
d_al->buildid = NULL;
|
|
}
|
|
if (sym) {
|
|
d_al->sym = sym->name;
|
|
d_al->sym_start = sym->start;
|
|
d_al->sym_end = sym->end;
|
|
if (al->addr < sym->end)
|
|
d_al->symoff = al->addr - sym->start;
|
|
else
|
|
d_al->symoff = al->addr - map__start(al->map) - sym->start;
|
|
d_al->sym_binding = sym->binding;
|
|
} else {
|
|
d_al->sym = NULL;
|
|
d_al->sym_start = 0;
|
|
d_al->sym_end = 0;
|
|
d_al->symoff = 0;
|
|
d_al->sym_binding = 0;
|
|
}
|
|
d_al->addr = al->addr;
|
|
d_al->comm = NULL;
|
|
d_al->filtered = 0;
|
|
}
|
|
|
|
static struct addr_location *get_al(struct dlfilter *d)
|
|
{
|
|
struct addr_location *al = d->al;
|
|
|
|
if (!al->thread && machine__resolve(d->machine, al, d->sample) < 0)
|
|
return NULL;
|
|
return al;
|
|
}
|
|
|
|
static struct thread *get_thread(struct dlfilter *d)
|
|
{
|
|
struct addr_location *al = get_al(d);
|
|
|
|
return al ? al->thread : NULL;
|
|
}
|
|
|
|
static const struct perf_dlfilter_al *dlfilter__resolve_ip(void *ctx)
|
|
{
|
|
struct dlfilter *d = (struct dlfilter *)ctx;
|
|
struct perf_dlfilter_al *d_al = d->d_ip_al;
|
|
struct addr_location *al;
|
|
|
|
if (!d->ctx_valid)
|
|
return NULL;
|
|
|
|
/* 'size' is also used to indicate already initialized */
|
|
if (d_al->size)
|
|
return d_al;
|
|
|
|
al = get_al(d);
|
|
if (!al)
|
|
return NULL;
|
|
|
|
al_to_d_al(al, d_al);
|
|
|
|
d_al->is_kernel_ip = machine__kernel_ip(d->machine, d->sample->ip);
|
|
d_al->comm = al->thread ? thread__comm_str(al->thread) : ":-1";
|
|
d_al->filtered = al->filtered;
|
|
|
|
return d_al;
|
|
}
|
|
|
|
static const struct perf_dlfilter_al *dlfilter__resolve_addr(void *ctx)
|
|
{
|
|
struct dlfilter *d = (struct dlfilter *)ctx;
|
|
struct perf_dlfilter_al *d_addr_al = d->d_addr_al;
|
|
struct addr_location *addr_al = d->addr_al;
|
|
|
|
if (!d->ctx_valid || !d->d_sample->addr_correlates_sym)
|
|
return NULL;
|
|
|
|
/* 'size' is also used to indicate already initialized */
|
|
if (d_addr_al->size)
|
|
return d_addr_al;
|
|
|
|
if (!addr_al->thread) {
|
|
struct thread *thread = get_thread(d);
|
|
|
|
if (!thread)
|
|
return NULL;
|
|
thread__resolve(thread, addr_al, d->sample);
|
|
}
|
|
|
|
al_to_d_al(addr_al, d_addr_al);
|
|
|
|
d_addr_al->is_kernel_ip = machine__kernel_ip(d->machine, d->sample->addr);
|
|
|
|
return d_addr_al;
|
|
}
|
|
|
|
static char **dlfilter__args(void *ctx, int *dlargc)
|
|
{
|
|
struct dlfilter *d = (struct dlfilter *)ctx;
|
|
|
|
if (dlargc)
|
|
*dlargc = 0;
|
|
else
|
|
return NULL;
|
|
|
|
if (!d->ctx_valid && !d->in_start && !d->in_stop)
|
|
return NULL;
|
|
|
|
*dlargc = d->dlargc;
|
|
return d->dlargv;
|
|
}
|
|
|
|
static __s32 dlfilter__resolve_address(void *ctx, __u64 address, struct perf_dlfilter_al *d_al_p)
|
|
{
|
|
struct dlfilter *d = (struct dlfilter *)ctx;
|
|
struct perf_dlfilter_al d_al;
|
|
struct addr_location al;
|
|
struct thread *thread;
|
|
__u32 sz;
|
|
|
|
if (!d->ctx_valid || !d_al_p)
|
|
return -1;
|
|
|
|
thread = get_thread(d);
|
|
if (!thread)
|
|
return -1;
|
|
|
|
thread__find_symbol_fb(thread, d->sample->cpumode, address, &al);
|
|
|
|
al_to_d_al(&al, &d_al);
|
|
|
|
d_al.is_kernel_ip = machine__kernel_ip(d->machine, address);
|
|
|
|
sz = d_al_p->size;
|
|
memcpy(d_al_p, &d_al, min((size_t)sz, sizeof(d_al)));
|
|
d_al_p->size = sz;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const __u8 *dlfilter__insn(void *ctx, __u32 *len)
|
|
{
|
|
struct dlfilter *d = (struct dlfilter *)ctx;
|
|
|
|
if (!len)
|
|
return NULL;
|
|
|
|
*len = 0;
|
|
|
|
if (!d->ctx_valid)
|
|
return NULL;
|
|
|
|
if (d->sample->ip && !d->sample->insn_len) {
|
|
struct addr_location *al = d->al;
|
|
|
|
if (!al->thread && machine__resolve(d->machine, al, d->sample) < 0)
|
|
return NULL;
|
|
|
|
if (thread__maps(al->thread)) {
|
|
struct machine *machine = maps__machine(thread__maps(al->thread));
|
|
|
|
if (machine)
|
|
script_fetch_insn(d->sample, al->thread, machine);
|
|
}
|
|
}
|
|
|
|
if (!d->sample->insn_len)
|
|
return NULL;
|
|
|
|
*len = d->sample->insn_len;
|
|
|
|
return (__u8 *)d->sample->insn;
|
|
}
|
|
|
|
static const char *dlfilter__srcline(void *ctx, __u32 *line_no)
|
|
{
|
|
struct dlfilter *d = (struct dlfilter *)ctx;
|
|
struct addr_location *al;
|
|
unsigned int line = 0;
|
|
char *srcfile = NULL;
|
|
struct map *map;
|
|
struct dso *dso;
|
|
u64 addr;
|
|
|
|
if (!d->ctx_valid || !line_no)
|
|
return NULL;
|
|
|
|
al = get_al(d);
|
|
if (!al)
|
|
return NULL;
|
|
|
|
map = al->map;
|
|
addr = al->addr;
|
|
dso = map ? map__dso(map) : NULL;
|
|
|
|
if (dso)
|
|
srcfile = get_srcline_split(dso, map__rip_2objdump(map, addr), &line);
|
|
|
|
*line_no = line;
|
|
return srcfile;
|
|
}
|
|
|
|
static struct perf_event_attr *dlfilter__attr(void *ctx)
|
|
{
|
|
struct dlfilter *d = (struct dlfilter *)ctx;
|
|
|
|
if (!d->ctx_valid)
|
|
return NULL;
|
|
|
|
return &d->evsel->core.attr;
|
|
}
|
|
|
|
static __s32 dlfilter__object_code(void *ctx, __u64 ip, void *buf, __u32 len)
|
|
{
|
|
struct dlfilter *d = (struct dlfilter *)ctx;
|
|
struct addr_location *al;
|
|
struct addr_location a;
|
|
struct map *map;
|
|
u64 offset;
|
|
__s32 ret;
|
|
|
|
if (!d->ctx_valid)
|
|
return -1;
|
|
|
|
al = get_al(d);
|
|
if (!al)
|
|
return -1;
|
|
|
|
map = al->map;
|
|
|
|
if (map && ip >= map__start(map) && ip < map__end(map) &&
|
|
machine__kernel_ip(d->machine, ip) == machine__kernel_ip(d->machine, d->sample->ip))
|
|
goto have_map;
|
|
|
|
addr_location__init(&a);
|
|
thread__find_map_fb(al->thread, d->sample->cpumode, ip, &a);
|
|
if (!a.map) {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
map = a.map;
|
|
have_map:
|
|
offset = map__map_ip(map, ip);
|
|
if (ip + len >= map__end(map))
|
|
len = map__end(map) - ip;
|
|
ret = dso__data_read_offset(map__dso(map), d->machine, offset, buf, len);
|
|
out:
|
|
addr_location__exit(&a);
|
|
return ret;
|
|
}
|
|
|
|
static const struct perf_dlfilter_fns perf_dlfilter_fns = {
|
|
.resolve_ip = dlfilter__resolve_ip,
|
|
.resolve_addr = dlfilter__resolve_addr,
|
|
.args = dlfilter__args,
|
|
.resolve_address = dlfilter__resolve_address,
|
|
.insn = dlfilter__insn,
|
|
.srcline = dlfilter__srcline,
|
|
.attr = dlfilter__attr,
|
|
.object_code = dlfilter__object_code,
|
|
};
|
|
|
|
static char *find_dlfilter(const char *file)
|
|
{
|
|
char path[PATH_MAX];
|
|
char *exec_path;
|
|
|
|
if (strchr(file, '/'))
|
|
goto out;
|
|
|
|
if (!access(file, R_OK)) {
|
|
/*
|
|
* Prepend "./" so that dlopen will find the file in the
|
|
* current directory.
|
|
*/
|
|
snprintf(path, sizeof(path), "./%s", file);
|
|
file = path;
|
|
goto out;
|
|
}
|
|
|
|
exec_path = get_argv_exec_path();
|
|
if (!exec_path)
|
|
goto out;
|
|
snprintf(path, sizeof(path), "%s/dlfilters/%s", exec_path, file);
|
|
free(exec_path);
|
|
if (!access(path, R_OK))
|
|
file = path;
|
|
out:
|
|
return strdup(file);
|
|
}
|
|
|
|
#define CHECK_FLAG(x) BUILD_BUG_ON((u64)PERF_DLFILTER_FLAG_ ## x != (u64)PERF_IP_FLAG_ ## x)
|
|
|
|
static int dlfilter__init(struct dlfilter *d, const char *file, int dlargc, char **dlargv)
|
|
{
|
|
CHECK_FLAG(BRANCH);
|
|
CHECK_FLAG(CALL);
|
|
CHECK_FLAG(RETURN);
|
|
CHECK_FLAG(CONDITIONAL);
|
|
CHECK_FLAG(SYSCALLRET);
|
|
CHECK_FLAG(ASYNC);
|
|
CHECK_FLAG(INTERRUPT);
|
|
CHECK_FLAG(TX_ABORT);
|
|
CHECK_FLAG(TRACE_BEGIN);
|
|
CHECK_FLAG(TRACE_END);
|
|
CHECK_FLAG(IN_TX);
|
|
CHECK_FLAG(VMENTRY);
|
|
CHECK_FLAG(VMEXIT);
|
|
|
|
memset(d, 0, sizeof(*d));
|
|
d->file = find_dlfilter(file);
|
|
if (!d->file)
|
|
return -1;
|
|
d->dlargc = dlargc;
|
|
d->dlargv = dlargv;
|
|
return 0;
|
|
}
|
|
|
|
static void dlfilter__exit(struct dlfilter *d)
|
|
{
|
|
zfree(&d->file);
|
|
}
|
|
|
|
static int dlfilter__open(struct dlfilter *d)
|
|
{
|
|
d->handle = dlopen(d->file, RTLD_NOW);
|
|
if (!d->handle) {
|
|
pr_err("dlopen failed for: '%s'\n", d->file);
|
|
return -1;
|
|
}
|
|
d->start = dlsym(d->handle, "start");
|
|
d->filter_event = dlsym(d->handle, "filter_event");
|
|
d->filter_event_early = dlsym(d->handle, "filter_event_early");
|
|
d->stop = dlsym(d->handle, "stop");
|
|
d->fns = dlsym(d->handle, "perf_dlfilter_fns");
|
|
if (d->fns)
|
|
memcpy(d->fns, &perf_dlfilter_fns, sizeof(struct perf_dlfilter_fns));
|
|
return 0;
|
|
}
|
|
|
|
static int dlfilter__close(struct dlfilter *d)
|
|
{
|
|
return dlclose(d->handle);
|
|
}
|
|
|
|
struct dlfilter *dlfilter__new(const char *file, int dlargc, char **dlargv)
|
|
{
|
|
struct dlfilter *d = malloc(sizeof(*d));
|
|
|
|
if (!d)
|
|
return NULL;
|
|
|
|
if (dlfilter__init(d, file, dlargc, dlargv))
|
|
goto err_free;
|
|
|
|
if (dlfilter__open(d))
|
|
goto err_exit;
|
|
|
|
return d;
|
|
|
|
err_exit:
|
|
dlfilter__exit(d);
|
|
err_free:
|
|
free(d);
|
|
return NULL;
|
|
}
|
|
|
|
static void dlfilter__free(struct dlfilter *d)
|
|
{
|
|
if (d) {
|
|
dlfilter__exit(d);
|
|
free(d);
|
|
}
|
|
}
|
|
|
|
int dlfilter__start(struct dlfilter *d, struct perf_session *session)
|
|
{
|
|
if (d) {
|
|
d->session = session;
|
|
if (d->start) {
|
|
int ret;
|
|
|
|
d->in_start = true;
|
|
ret = d->start(&d->data, d);
|
|
d->in_start = false;
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int dlfilter__stop(struct dlfilter *d)
|
|
{
|
|
if (d && d->stop) {
|
|
int ret;
|
|
|
|
d->in_stop = true;
|
|
ret = d->stop(d->data, d);
|
|
d->in_stop = false;
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void dlfilter__cleanup(struct dlfilter *d)
|
|
{
|
|
if (d) {
|
|
dlfilter__stop(d);
|
|
dlfilter__close(d);
|
|
dlfilter__free(d);
|
|
}
|
|
}
|
|
|
|
#define ASSIGN(x) d_sample.x = sample->x
|
|
|
|
int dlfilter__do_filter_event(struct dlfilter *d,
|
|
union perf_event *event,
|
|
struct perf_sample *sample,
|
|
struct evsel *evsel,
|
|
struct machine *machine,
|
|
struct addr_location *al,
|
|
struct addr_location *addr_al,
|
|
bool early)
|
|
{
|
|
struct perf_dlfilter_sample d_sample;
|
|
struct perf_dlfilter_al d_ip_al;
|
|
struct perf_dlfilter_al d_addr_al;
|
|
int ret;
|
|
|
|
d->event = event;
|
|
d->sample = sample;
|
|
d->evsel = evsel;
|
|
d->machine = machine;
|
|
d->al = al;
|
|
d->addr_al = addr_al;
|
|
d->d_sample = &d_sample;
|
|
d->d_ip_al = &d_ip_al;
|
|
d->d_addr_al = &d_addr_al;
|
|
|
|
d_sample.size = sizeof(d_sample);
|
|
d_ip_al.size = 0; /* To indicate d_ip_al is not initialized */
|
|
d_addr_al.size = 0; /* To indicate d_addr_al is not initialized */
|
|
|
|
ASSIGN(ip);
|
|
ASSIGN(pid);
|
|
ASSIGN(tid);
|
|
ASSIGN(time);
|
|
ASSIGN(addr);
|
|
ASSIGN(id);
|
|
ASSIGN(stream_id);
|
|
ASSIGN(period);
|
|
ASSIGN(weight);
|
|
ASSIGN(ins_lat);
|
|
ASSIGN(p_stage_cyc);
|
|
ASSIGN(transaction);
|
|
ASSIGN(insn_cnt);
|
|
ASSIGN(cyc_cnt);
|
|
ASSIGN(cpu);
|
|
ASSIGN(flags);
|
|
ASSIGN(data_src);
|
|
ASSIGN(phys_addr);
|
|
ASSIGN(data_page_size);
|
|
ASSIGN(code_page_size);
|
|
ASSIGN(cgroup);
|
|
ASSIGN(cpumode);
|
|
ASSIGN(misc);
|
|
ASSIGN(raw_size);
|
|
ASSIGN(raw_data);
|
|
ASSIGN(machine_pid);
|
|
ASSIGN(vcpu);
|
|
|
|
if (sample->branch_stack) {
|
|
d_sample.brstack_nr = sample->branch_stack->nr;
|
|
d_sample.brstack = (struct perf_branch_entry *)perf_sample__branch_entries(sample);
|
|
} else {
|
|
d_sample.brstack_nr = 0;
|
|
d_sample.brstack = NULL;
|
|
}
|
|
|
|
if (sample->callchain) {
|
|
d_sample.raw_callchain_nr = sample->callchain->nr;
|
|
d_sample.raw_callchain = (__u64 *)sample->callchain->ips;
|
|
} else {
|
|
d_sample.raw_callchain_nr = 0;
|
|
d_sample.raw_callchain = NULL;
|
|
}
|
|
|
|
d_sample.addr_correlates_sym =
|
|
(evsel->core.attr.sample_type & PERF_SAMPLE_ADDR) &&
|
|
sample_addr_correlates_sym(&evsel->core.attr);
|
|
|
|
d_sample.event = evsel__name(evsel);
|
|
|
|
d->ctx_valid = true;
|
|
|
|
if (early)
|
|
ret = d->filter_event_early(d->data, &d_sample, d);
|
|
else
|
|
ret = d->filter_event(d->data, &d_sample, d);
|
|
|
|
d->ctx_valid = false;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool get_filter_desc(const char *dirname, const char *name, char **desc,
|
|
char **long_desc)
|
|
{
|
|
char path[PATH_MAX];
|
|
void *handle;
|
|
const char *(*desc_fn)(const char **long_description);
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", dirname, name);
|
|
handle = dlopen(path, RTLD_NOW);
|
|
if (!handle || !(dlsym(handle, "filter_event") || dlsym(handle, "filter_event_early")))
|
|
return false;
|
|
desc_fn = dlsym(handle, "filter_description");
|
|
if (desc_fn) {
|
|
const char *dsc;
|
|
const char *long_dsc;
|
|
|
|
dsc = desc_fn(&long_dsc);
|
|
if (dsc)
|
|
*desc = strdup(dsc);
|
|
if (long_dsc)
|
|
*long_desc = strdup(long_dsc);
|
|
}
|
|
dlclose(handle);
|
|
return true;
|
|
}
|
|
|
|
static void list_filters(const char *dirname)
|
|
{
|
|
struct dirent *entry;
|
|
DIR *dir;
|
|
|
|
dir = opendir(dirname);
|
|
if (!dir)
|
|
return;
|
|
|
|
while ((entry = readdir(dir)) != NULL)
|
|
{
|
|
size_t n = strlen(entry->d_name);
|
|
char *long_desc = NULL;
|
|
char *desc = NULL;
|
|
|
|
if (entry->d_type == DT_DIR || n < 4 ||
|
|
strcmp(".so", entry->d_name + n - 3))
|
|
continue;
|
|
if (!get_filter_desc(dirname, entry->d_name, &desc, &long_desc))
|
|
continue;
|
|
printf(" %-36s %s\n", entry->d_name, desc ? desc : "");
|
|
if (verbose > 0) {
|
|
char *p = long_desc;
|
|
char *line;
|
|
|
|
while ((line = strsep(&p, "\n")) != NULL)
|
|
printf("%39s%s\n", "", line);
|
|
}
|
|
free(long_desc);
|
|
free(desc);
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
int list_available_dlfilters(const struct option *opt __maybe_unused,
|
|
const char *s __maybe_unused,
|
|
int unset __maybe_unused)
|
|
{
|
|
char path[PATH_MAX];
|
|
char *exec_path;
|
|
|
|
printf("List of available dlfilters:\n");
|
|
|
|
list_filters(".");
|
|
|
|
exec_path = get_argv_exec_path();
|
|
if (!exec_path)
|
|
goto out;
|
|
snprintf(path, sizeof(path), "%s/dlfilters", exec_path);
|
|
|
|
list_filters(path);
|
|
|
|
free(exec_path);
|
|
out:
|
|
exit(0);
|
|
}
|