perf/core improvements and fixes:
User visible: - Take tracefs into account when reporting errors about accessing tracepoint information in tools like 'perf trace' (Arnaldo Carvalho de Melo) - Let user have timestamps with per-thread recording in 'perf record' (Adrian Hunter) Infrastructure: - Introduce series of functions to build event filters so that we can set them in just one ioctl call, useful to set up common_pid, raw_syscalls:sys_{enter,exit}'s "id" filters to use with 'perf trace' (Arnaldo Carvalho de Melo) - Delete an unnecessary check before calling strfilter__delete() (Markus Elfring) Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJVmp9sAAoJENZQFvNTUqpASCEQALTZ9nhHcZVWr4Uivgsjp62G axlWXO0U4wXNjpzwkL/OJsPvCLnOnxcaTshSaOKmHC95NpOMzktwu+rjd4zojTW3 /4RQaXd2DZ2NmdOdVW6SDqOl91OYslr4TFRXlfwDPGtq/udTbt4ZHnpGGPS4TWP2 Iwlyv0iOczZw8pBb2BQ4QSE42lNmZ9ZyLIVnuNF11pt8Gnx8hmeh1665m7dPrukc jhjZjPqVA/U4nyo6B3eG+nheACL+744nE32GKGiMEFws1CQcjXHx6i/Zgcq7dSRk 2wfr2Ki0dnaEVjJiuQKa+D52EDyYQLARVLnfNNFzUp+UincBSx2OELVYf+Vbh2FT uVWYQ8HaHfPLAftyPsDr8jIkQn29uO0wtwLv1Aefxj9EGc5GcYI6GleoDVfn+4VW EV0fkJcuqyViG1+ixBTj/aaepp553VPAN2JYyuSuN/ilSi5WCGic8hwGUTxX2Dlm NUV9i0BufdXlg3KYTV334mtS+2Du4R3FcPR3qI2cjipILrELlhQXITdkC6Nxze+o t0M6MOZDYB+O4HMwUIpYB58OoOeoqnJZ4WPp4q1xI0CiMxxhzBCuHJifUhgcWuXE 6mYpDf5ZrjCjDWUF64f9e1BTkZ6iv1hVnitST5wzdVbiHVc2F94voUorznOz5kMb PmdiXRuofZg++8faqJbH =A0RX -----END PGP SIGNATURE----- Merge tag 'perf-core-for-mingo' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux into perf/core Pull perf/core improvements and fixes from Arnaldo Carvalho de Melo: User visible changes: - Take tracefs into account when reporting errors about accessing tracepoint information in tools like 'perf trace' (Arnaldo Carvalho de Melo) - Let user have timestamps with per-thread recording in 'perf record' (Adrian Hunter) Infrastructure changes: - Introduce series of functions to build event filters so that we can set them in just one ioctl call, useful to set up common_pid, raw_syscalls:sys_{enter,exit}'s "id" filters to use with 'perf trace' (Arnaldo Carvalho de Melo) - Delete an unnecessary check before calling strfilter__delete() (Markus Elfring) Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
commit
60cd37eb10
@ -12,6 +12,7 @@
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "debugfs.h"
|
||||
#include "tracefs.h"
|
||||
|
||||
#ifndef DEBUGFS_DEFAULT_PATH
|
||||
#define DEBUGFS_DEFAULT_PATH "/sys/kernel/debug"
|
||||
@ -94,11 +95,21 @@ int debugfs__strerror_open(int err, char *buf, size_t size, const char *filename
|
||||
"Hint:\tIs the debugfs filesystem mounted?\n"
|
||||
"Hint:\tTry 'sudo mount -t debugfs nodev /sys/kernel/debug'");
|
||||
break;
|
||||
case EACCES:
|
||||
case EACCES: {
|
||||
const char *mountpoint = debugfs_mountpoint;
|
||||
|
||||
if (!access(debugfs_mountpoint, R_OK) && strncmp(filename, "tracing/", 8) == 0) {
|
||||
const char *tracefs_mntpoint = tracefs_find_mountpoint();
|
||||
|
||||
if (tracefs_mntpoint)
|
||||
mountpoint = tracefs_mntpoint;
|
||||
}
|
||||
|
||||
snprintf(buf, size,
|
||||
"Error:\tNo permissions to read %s/%s\n"
|
||||
"Hint:\tTry 'sudo mount -o remount,mode=755 %s'\n",
|
||||
debugfs_mountpoint, filename, debugfs_mountpoint);
|
||||
debugfs_mountpoint, filename, mountpoint);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
snprintf(buf, size, "%s", strerror_r(err, sbuf, sizeof(sbuf)));
|
||||
|
@ -297,8 +297,7 @@ static void cleanup_params(void)
|
||||
clear_perf_probe_event(params.events + i);
|
||||
line_range__clear(¶ms.line_range);
|
||||
free(params.target);
|
||||
if (params.filter)
|
||||
strfilter__delete(params.filter);
|
||||
strfilter__delete(params.filter);
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
}
|
||||
|
||||
|
@ -1030,7 +1030,9 @@ struct option __record_options[] = {
|
||||
OPT_BOOLEAN('s', "stat", &record.opts.inherit_stat,
|
||||
"per thread counts"),
|
||||
OPT_BOOLEAN('d', "data", &record.opts.sample_address, "Record the sample addresses"),
|
||||
OPT_BOOLEAN('T', "timestamp", &record.opts.sample_time, "Record the sample timestamps"),
|
||||
OPT_BOOLEAN_SET('T', "timestamp", &record.opts.sample_time,
|
||||
&record.opts.sample_time_set,
|
||||
"Record the sample timestamps"),
|
||||
OPT_BOOLEAN('P', "period", &record.opts.period, "Record the sample period"),
|
||||
OPT_BOOLEAN('n', "no-samples", &record.opts.no_samples,
|
||||
"don't sample"),
|
||||
|
@ -247,42 +247,6 @@ out_delete:
|
||||
({ struct syscall_tp *fields = evsel->priv; \
|
||||
fields->name.pointer(&fields->name, sample); })
|
||||
|
||||
static int perf_evlist__add_syscall_newtp(struct perf_evlist *evlist,
|
||||
void *sys_enter_handler,
|
||||
void *sys_exit_handler)
|
||||
{
|
||||
int ret = -1;
|
||||
struct perf_evsel *sys_enter, *sys_exit;
|
||||
|
||||
sys_enter = perf_evsel__syscall_newtp("sys_enter", sys_enter_handler);
|
||||
if (sys_enter == NULL)
|
||||
goto out;
|
||||
|
||||
if (perf_evsel__init_sc_tp_ptr_field(sys_enter, args))
|
||||
goto out_delete_sys_enter;
|
||||
|
||||
sys_exit = perf_evsel__syscall_newtp("sys_exit", sys_exit_handler);
|
||||
if (sys_exit == NULL)
|
||||
goto out_delete_sys_enter;
|
||||
|
||||
if (perf_evsel__init_sc_tp_uint_field(sys_exit, ret))
|
||||
goto out_delete_sys_exit;
|
||||
|
||||
perf_evlist__add(evlist, sys_enter);
|
||||
perf_evlist__add(evlist, sys_exit);
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
return ret;
|
||||
|
||||
out_delete_sys_exit:
|
||||
perf_evsel__delete_priv(sys_exit);
|
||||
out_delete_sys_enter:
|
||||
perf_evsel__delete_priv(sys_enter);
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
struct syscall_arg {
|
||||
unsigned long val;
|
||||
struct thread *thread;
|
||||
@ -1223,7 +1187,6 @@ struct syscall {
|
||||
int nr_args;
|
||||
struct format_field *args;
|
||||
const char *name;
|
||||
bool filtered;
|
||||
bool is_exit;
|
||||
struct syscall_fmt *fmt;
|
||||
size_t (**arg_scnprintf)(char *bf, size_t size, struct syscall_arg *arg);
|
||||
@ -1307,6 +1270,10 @@ struct trace {
|
||||
struct {
|
||||
int max;
|
||||
struct syscall *table;
|
||||
struct {
|
||||
struct perf_evsel *sys_enter,
|
||||
*sys_exit;
|
||||
} events;
|
||||
} syscalls;
|
||||
struct record_opts opts;
|
||||
struct perf_evlist *evlist;
|
||||
@ -1316,6 +1283,10 @@ struct trace {
|
||||
FILE *output;
|
||||
unsigned long nr_events;
|
||||
struct strlist *ev_qualifier;
|
||||
struct {
|
||||
size_t nr;
|
||||
int *entries;
|
||||
} ev_qualifier_ids;
|
||||
const char *last_vfs_getname;
|
||||
struct intlist *tid_list;
|
||||
struct intlist *pid_list;
|
||||
@ -1578,19 +1549,6 @@ static int trace__read_syscall_info(struct trace *trace, int id)
|
||||
sc = trace->syscalls.table + id;
|
||||
sc->name = name;
|
||||
|
||||
if (trace->ev_qualifier) {
|
||||
bool in = strlist__find(trace->ev_qualifier, name) != NULL;
|
||||
|
||||
if (!(in ^ trace->not_ev_qualifier)) {
|
||||
sc->filtered = true;
|
||||
/*
|
||||
* No need to do read tracepoint information since this will be
|
||||
* filtered out.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sc->fmt = syscall_fmt__find(sc->name);
|
||||
|
||||
snprintf(tp_name, sizeof(tp_name), "sys_enter_%s", sc->name);
|
||||
@ -1619,13 +1577,27 @@ static int trace__read_syscall_info(struct trace *trace, int id)
|
||||
|
||||
static int trace__validate_ev_qualifier(struct trace *trace)
|
||||
{
|
||||
int err = 0;
|
||||
int err = 0, i;
|
||||
struct str_node *pos;
|
||||
|
||||
trace->ev_qualifier_ids.nr = strlist__nr_entries(trace->ev_qualifier);
|
||||
trace->ev_qualifier_ids.entries = malloc(trace->ev_qualifier_ids.nr *
|
||||
sizeof(trace->ev_qualifier_ids.entries[0]));
|
||||
|
||||
if (trace->ev_qualifier_ids.entries == NULL) {
|
||||
fputs("Error:\tNot enough memory for allocating events qualifier ids\n",
|
||||
trace->output);
|
||||
err = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
|
||||
strlist__for_each(pos, trace->ev_qualifier) {
|
||||
const char *sc = pos->s;
|
||||
int id = audit_name_to_syscall(sc, trace->audit.machine);
|
||||
|
||||
if (audit_name_to_syscall(sc, trace->audit.machine) < 0) {
|
||||
if (id < 0) {
|
||||
if (err == 0) {
|
||||
fputs("Error:\tInvalid syscall ", trace->output);
|
||||
err = -EINVAL;
|
||||
@ -1635,13 +1607,17 @@ static int trace__validate_ev_qualifier(struct trace *trace)
|
||||
|
||||
fputs(sc, trace->output);
|
||||
}
|
||||
|
||||
trace->ev_qualifier_ids.entries[i++] = id;
|
||||
}
|
||||
|
||||
if (err < 0) {
|
||||
fputs("\nHint:\ttry 'perf list syscalls:sys_enter_*'"
|
||||
"\nHint:\tand: 'man syscalls'\n", trace->output);
|
||||
zfree(&trace->ev_qualifier_ids.entries);
|
||||
trace->ev_qualifier_ids.nr = 0;
|
||||
}
|
||||
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
@ -1833,9 +1809,6 @@ static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel,
|
||||
if (sc == NULL)
|
||||
return -1;
|
||||
|
||||
if (sc->filtered)
|
||||
return 0;
|
||||
|
||||
thread = machine__findnew_thread(trace->host, sample->pid, sample->tid);
|
||||
ttrace = thread__trace(thread, trace->output);
|
||||
if (ttrace == NULL)
|
||||
@ -1891,9 +1864,6 @@ static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel,
|
||||
if (sc == NULL)
|
||||
return -1;
|
||||
|
||||
if (sc->filtered)
|
||||
return 0;
|
||||
|
||||
thread = machine__findnew_thread(trace->host, sample->pid, sample->tid);
|
||||
ttrace = thread__trace(thread, trace->output);
|
||||
if (ttrace == NULL)
|
||||
@ -2283,9 +2253,68 @@ static void trace__handle_event(struct trace *trace, union perf_event *event, st
|
||||
}
|
||||
}
|
||||
|
||||
static int trace__add_syscall_newtp(struct trace *trace)
|
||||
{
|
||||
int ret = -1;
|
||||
struct perf_evlist *evlist = trace->evlist;
|
||||
struct perf_evsel *sys_enter, *sys_exit;
|
||||
|
||||
sys_enter = perf_evsel__syscall_newtp("sys_enter", trace__sys_enter);
|
||||
if (sys_enter == NULL)
|
||||
goto out;
|
||||
|
||||
if (perf_evsel__init_sc_tp_ptr_field(sys_enter, args))
|
||||
goto out_delete_sys_enter;
|
||||
|
||||
sys_exit = perf_evsel__syscall_newtp("sys_exit", trace__sys_exit);
|
||||
if (sys_exit == NULL)
|
||||
goto out_delete_sys_enter;
|
||||
|
||||
if (perf_evsel__init_sc_tp_uint_field(sys_exit, ret))
|
||||
goto out_delete_sys_exit;
|
||||
|
||||
perf_evlist__add(evlist, sys_enter);
|
||||
perf_evlist__add(evlist, sys_exit);
|
||||
|
||||
trace->syscalls.events.sys_enter = sys_enter;
|
||||
trace->syscalls.events.sys_exit = sys_exit;
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
return ret;
|
||||
|
||||
out_delete_sys_exit:
|
||||
perf_evsel__delete_priv(sys_exit);
|
||||
out_delete_sys_enter:
|
||||
perf_evsel__delete_priv(sys_enter);
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int trace__set_ev_qualifier_filter(struct trace *trace)
|
||||
{
|
||||
int err = -1;
|
||||
char *filter = asprintf_expr_inout_ints("id", !trace->not_ev_qualifier,
|
||||
trace->ev_qualifier_ids.nr,
|
||||
trace->ev_qualifier_ids.entries);
|
||||
|
||||
if (filter == NULL)
|
||||
goto out_enomem;
|
||||
|
||||
if (!perf_evsel__append_filter(trace->syscalls.events.sys_enter, "&&", filter))
|
||||
err = perf_evsel__append_filter(trace->syscalls.events.sys_exit, "&&", filter);
|
||||
|
||||
free(filter);
|
||||
out:
|
||||
return err;
|
||||
out_enomem:
|
||||
errno = ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int trace__run(struct trace *trace, int argc, const char **argv)
|
||||
{
|
||||
struct perf_evlist *evlist = trace->evlist;
|
||||
struct perf_evsel *evsel;
|
||||
int err = -1, i;
|
||||
unsigned long before;
|
||||
const bool forks = argc > 0;
|
||||
@ -2293,9 +2322,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
|
||||
|
||||
trace->live = true;
|
||||
|
||||
if (trace->trace_syscalls &&
|
||||
perf_evlist__add_syscall_newtp(evlist, trace__sys_enter,
|
||||
trace__sys_exit))
|
||||
if (trace->trace_syscalls && trace__add_syscall_newtp(trace))
|
||||
goto out_error_raw_syscalls;
|
||||
|
||||
if (trace->trace_syscalls)
|
||||
@ -2356,11 +2383,21 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
|
||||
else if (thread_map__pid(evlist->threads, 0) == -1)
|
||||
err = perf_evlist__set_filter_pid(evlist, getpid());
|
||||
|
||||
if (err < 0) {
|
||||
printf("err=%d,%s\n", -err, strerror(-err));
|
||||
exit(1);
|
||||
if (err < 0)
|
||||
goto out_error_mem;
|
||||
|
||||
if (trace->ev_qualifier_ids.nr > 0) {
|
||||
err = trace__set_ev_qualifier_filter(trace);
|
||||
if (err < 0)
|
||||
goto out_errno;
|
||||
}
|
||||
|
||||
pr_debug("%s\n", trace->syscalls.events.sys_exit->filter);
|
||||
|
||||
err = perf_evlist__apply_filters(evlist, &evsel);
|
||||
if (err < 0)
|
||||
goto out_error_apply_filters;
|
||||
|
||||
err = perf_evlist__mmap(evlist, trace->opts.mmap_pages, false);
|
||||
if (err < 0)
|
||||
goto out_error_mmap;
|
||||
@ -2462,10 +2499,21 @@ out_error_open:
|
||||
out_error:
|
||||
fprintf(trace->output, "%s\n", errbuf);
|
||||
goto out_delete_evlist;
|
||||
|
||||
out_error_apply_filters:
|
||||
fprintf(trace->output,
|
||||
"Failed to set filter \"%s\" on event %s with %d (%s)\n",
|
||||
evsel->filter, perf_evsel__name(evsel), errno,
|
||||
strerror_r(errno, errbuf, sizeof(errbuf)));
|
||||
goto out_delete_evlist;
|
||||
}
|
||||
out_error_mem:
|
||||
fprintf(trace->output, "Not enough memory to run!\n");
|
||||
goto out_delete_evlist;
|
||||
|
||||
out_errno:
|
||||
fprintf(trace->output, "errno=%d,%s\n", errno, strerror(errno));
|
||||
goto out_delete_evlist;
|
||||
}
|
||||
|
||||
static int trace__replay(struct trace *trace)
|
||||
|
@ -51,6 +51,7 @@ struct record_opts {
|
||||
bool sample_address;
|
||||
bool sample_weight;
|
||||
bool sample_time;
|
||||
bool sample_time_set;
|
||||
bool period;
|
||||
bool sample_intr_regs;
|
||||
bool running_time;
|
||||
|
@ -1161,7 +1161,7 @@ int perf_evlist__apply_filters(struct perf_evlist *evlist, struct perf_evsel **e
|
||||
if (evsel->filter == NULL)
|
||||
continue;
|
||||
|
||||
err = perf_evsel__set_filter(evsel, ncpus, nthreads, evsel->filter);
|
||||
err = perf_evsel__apply_filter(evsel, ncpus, nthreads, evsel->filter);
|
||||
if (err) {
|
||||
*err_evsel = evsel;
|
||||
break;
|
||||
@ -1175,11 +1175,9 @@ int perf_evlist__set_filter(struct perf_evlist *evlist, const char *filter)
|
||||
{
|
||||
struct perf_evsel *evsel;
|
||||
int err = 0;
|
||||
const int ncpus = cpu_map__nr(evlist->cpus),
|
||||
nthreads = thread_map__nr(evlist->threads);
|
||||
|
||||
evlist__for_each(evlist, evsel) {
|
||||
err = perf_evsel__set_filter(evsel, ncpus, nthreads, filter);
|
||||
err = perf_evsel__set_filter(evsel, filter);
|
||||
if (err)
|
||||
break;
|
||||
}
|
||||
|
@ -707,7 +707,8 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts)
|
||||
*/
|
||||
if (opts->sample_time &&
|
||||
(!perf_missing_features.sample_id_all &&
|
||||
(!opts->no_inherit || target__has_cpu(&opts->target) || per_cpu)))
|
||||
(!opts->no_inherit || target__has_cpu(&opts->target) || per_cpu ||
|
||||
opts->sample_time_set)))
|
||||
perf_evsel__set_sample_bit(evsel, TIME);
|
||||
|
||||
if (opts->raw_samples && !evsel->no_aux_samples) {
|
||||
@ -815,14 +816,44 @@ static int perf_evsel__run_ioctl(struct perf_evsel *evsel, int ncpus, int nthrea
|
||||
return 0;
|
||||
}
|
||||
|
||||
int perf_evsel__set_filter(struct perf_evsel *evsel, int ncpus, int nthreads,
|
||||
const char *filter)
|
||||
int perf_evsel__apply_filter(struct perf_evsel *evsel, int ncpus, int nthreads,
|
||||
const char *filter)
|
||||
{
|
||||
return perf_evsel__run_ioctl(evsel, ncpus, nthreads,
|
||||
PERF_EVENT_IOC_SET_FILTER,
|
||||
(void *)filter);
|
||||
}
|
||||
|
||||
int perf_evsel__set_filter(struct perf_evsel *evsel, const char *filter)
|
||||
{
|
||||
char *new_filter = strdup(filter);
|
||||
|
||||
if (new_filter != NULL) {
|
||||
free(evsel->filter);
|
||||
evsel->filter = new_filter;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int perf_evsel__append_filter(struct perf_evsel *evsel,
|
||||
const char *op, const char *filter)
|
||||
{
|
||||
char *new_filter;
|
||||
|
||||
if (evsel->filter == NULL)
|
||||
return perf_evsel__set_filter(evsel, filter);
|
||||
|
||||
if (asprintf(&new_filter,"(%s) %s (%s)", evsel->filter, op, filter) > 0) {
|
||||
free(evsel->filter);
|
||||
evsel->filter = new_filter;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads)
|
||||
{
|
||||
return perf_evsel__run_ioctl(evsel, ncpus, nthreads,
|
||||
|
@ -182,8 +182,11 @@ void __perf_evsel__reset_sample_bit(struct perf_evsel *evsel,
|
||||
void perf_evsel__set_sample_id(struct perf_evsel *evsel,
|
||||
bool use_sample_identifier);
|
||||
|
||||
int perf_evsel__set_filter(struct perf_evsel *evsel, int ncpus, int nthreads,
|
||||
const char *filter);
|
||||
int perf_evsel__set_filter(struct perf_evsel *evsel, const char *filter);
|
||||
int perf_evsel__append_filter(struct perf_evsel *evsel,
|
||||
const char *op, const char *filter);
|
||||
int perf_evsel__apply_filter(struct perf_evsel *evsel, int ncpus, int nthreads,
|
||||
const char *filter);
|
||||
int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads);
|
||||
|
||||
int perf_evsel__open_per_cpu(struct perf_evsel *evsel,
|
||||
|
@ -1177,8 +1177,7 @@ int parse_filter(const struct option *opt, const char *str,
|
||||
return -1;
|
||||
}
|
||||
|
||||
last->filter = strdup(str);
|
||||
if (last->filter == NULL) {
|
||||
if (perf_evsel__set_filter(last, str) < 0) {
|
||||
fprintf(stderr, "not enough memory to hold filter string\n");
|
||||
return -1;
|
||||
}
|
||||
|
@ -357,3 +357,42 @@ void *memdup(const void *src, size_t len)
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
char *asprintf_expr_inout_ints(const char *var, bool in, size_t nints, int *ints)
|
||||
{
|
||||
/*
|
||||
* FIXME: replace this with an expression using log10() when we
|
||||
* find a suitable implementation, maybe the one in the dvb drivers...
|
||||
*
|
||||
* "%s == %d || " = log10(MAXINT) * 2 + 8 chars for the operators
|
||||
*/
|
||||
size_t size = nints * 28 + 1; /* \0 */
|
||||
size_t i, printed = 0;
|
||||
char *expr = malloc(size);
|
||||
|
||||
if (expr) {
|
||||
const char *or_and = "||", *eq_neq = "==";
|
||||
char *e = expr;
|
||||
|
||||
if (!in) {
|
||||
or_and = "&&";
|
||||
eq_neq = "!=";
|
||||
}
|
||||
|
||||
for (i = 0; i < nints; ++i) {
|
||||
if (printed == size)
|
||||
goto out_err_overflow;
|
||||
|
||||
if (i > 0)
|
||||
printed += snprintf(e + printed, size - printed, " %s ", or_and);
|
||||
printed += scnprintf(e + printed, size - printed,
|
||||
"%s %s %d", var, eq_neq, ints[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return expr;
|
||||
|
||||
out_err_overflow:
|
||||
free(expr);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -339,4 +339,16 @@ int gzip_decompress_to_file(const char *input, int output_fd);
|
||||
int lzma_decompress_to_file(const char *input, int output_fd);
|
||||
#endif
|
||||
|
||||
char *asprintf_expr_inout_ints(const char *var, bool in, size_t nints, int *ints);
|
||||
|
||||
static inline char *asprintf_expr_in_ints(const char *var, size_t nints, int *ints)
|
||||
{
|
||||
return asprintf_expr_inout_ints(var, true, nints, ints);
|
||||
}
|
||||
|
||||
static inline char *asprintf_expr_not_in_ints(const char *var, size_t nints, int *ints)
|
||||
{
|
||||
return asprintf_expr_inout_ints(var, false, nints, ints);
|
||||
}
|
||||
|
||||
#endif /* GIT_COMPAT_UTIL_H */
|
||||
|
Loading…
Reference in New Issue
Block a user