struct filter *allow_filters; struct filter *deny_filters; int allow_filter_cnt; int deny_filter_cnt;
int files_processed; int files_skipped; int progs_processed; int progs_skipped; int top_src_lines; struct var_preset *presets; int npresets; char orig_cgroup[PATH_MAX]; char stat_cgroup[PATH_MAX]; int memory_peak_fd;
} env;
staticint libbpf_print_fn(enum libbpf_print_level level, constchar *format, va_list args)
{ if (!env.verbose) return0; if (level == LIBBPF_DEBUG && !env.debug) return0; return vfprintf(stderr, format, args);
}
/* First, let's check if it's a stats filter of the following form: *<stat><op><value,where: *-<stat>isoneofsupportednumericalstats(verdictisalso *considerednumerical,failure==0,success==1); *-<op>iscomparisonoperator(see`operators`definitions); *-<value>isaninteger(orfailure/success,orfalse/trueas *specialaliasesfor0and1,respectively). *Iftheformdoesn'tmatchwhatuserprovided,weassumefile/prog *globfilter.
*/ for (i = 0; i < ARRAY_SIZE(operators); i++) { enum stat_variant var; int id; long val; constchar *end = str; constchar *op_str; bool is_abs;
op_str = operators[i].op_str;
p = strstr(str, op_str); if (!p) continue;
if (!parse_stat_id_var(str, p - str, &id, &var, &is_abs)) {
fprintf(stderr, "Unrecognized stat name in '%s'!\n", str); return -EINVAL;
} if (id >= FILE_NAME) {
fprintf(stderr, "Non-integer stat is specified in '%s'!\n", str); return -EINVAL;
}
/* |<stat>| means we take absolute value of given stat */
*is_abs = false; if (len > 2 && name[0] == '|' && name[len - 1] == '|') {
*is_abs = true;
name += 1;
len -= 2;
}
for (i = 0; i < ARRAY_SIZE(stat_defs); i++) { struct stat_def *def = &stat_defs[i];
size_t alias_len, sfx_len; constchar *alias;
for (j = 0; j < ARRAY_SIZE(stat_defs[i].names); j++) {
alias = def->names[j]; if (!alias) continue;
alias_len = strlen(alias); if (strncmp(name, alias, alias_len) != 0) continue;
if (alias_len == len) { /* If no variant suffix is specified, we *assumecontrolgroup(justincaseweare *incomparisonmode.Variantisignoredin *non-comparisonmode.
*/
*var = VARIANT_B;
*id = i; returntrue;
}
for (k = 0; k < ARRAY_SIZE(var_sfxs); k++) {
sfx_len = strlen(var_sfxs[k]); if (alias_len + sfx_len != len) continue;
for (pos = strlen(buf) - 1, lines = 0; pos >= 0 && lines < MAX_PARSED_LOG_LINES; lines++) { /* find previous endline or otherwise take the start of log buf */ for (cur = &buf[pos]; cur > buf && cur[0] != '\n'; cur--, pos--) {
} /* next time start from end of previous line (or pos goes to <0) */
pos--; /* if we found endline, point right after endline symbol; *otherwise,stayatthebeginningoflogbuf
*/ if (cur[0] == '\n')
cur++;
/* fix up map size, if necessary */ switch (bpf_map__type(map)) { case BPF_MAP_TYPE_SK_STORAGE: case BPF_MAP_TYPE_TASK_STORAGE: case BPF_MAP_TYPE_INODE_STORAGE: case BPF_MAP_TYPE_CGROUP_STORAGE: case BPF_MAP_TYPE_CGRP_STORAGE: break; case BPF_MAP_TYPE_STRUCT_OPS:
mask_unrelated_struct_ops_progs(obj, map, prog); break; default: if (bpf_map__max_entries(map) == 0)
bpf_map__set_max_entries(map, 1);
}
}
/* SEC(freplace) programs can't be loaded with veristat as is, *butwecantryguessingtheirtargetprogram'sexpectedtypeby *lookingatthetypeofprogram'sfirstargumentandsubstituting *correspondingprogramtype
*/ if (bpf_program__type(prog) == BPF_PROG_TYPE_EXT) { conststruct btf *btf = bpf_object__btf(obj); constchar *prog_name = bpf_program__name(prog); enum bpf_prog_type prog_type; enum bpf_attach_type attach_type; conststruct btf_type *t; constchar *ctx_name; int id;
if (!btf) goto skip_freplace_fixup;
id = btf__find_by_name_kind(btf, prog_name, BTF_KIND_FUNC);
t = btf__type_by_id(btf, id);
t = btf__type_by_id(btf, t->type); if (!btf_is_func_proto(t) || btf_vlen(t) != 1) goto skip_freplace_fixup;
/* context argument is a pointer to a struct/typedef */
t = btf__type_by_id(btf, btf_params(t)[0].type); while (t && btf_is_mod(t))
t = btf__type_by_id(btf, t->type); if (!t || !btf_is_ptr(t)) goto skip_freplace_fixup;
t = btf__type_by_id(btf, t->type); while (t && btf_is_mod(t))
t = btf__type_by_id(btf, t->type); if (!t) goto skip_freplace_fixup;
if (!env.quiet) {
fprintf(stderr, "Using guessed program type '%s' for %s/%s...\n",
libbpf_bpf_prog_type_str(prog_type),
filename, prog_name);
}
} else { if (!env.quiet) {
fprintf(stderr, "Failed to guess program type for freplace program with context type name '%s' for %s/%s. Consider using canonical type names to help veristat...\n",
ctx_name, filename, prog_name);
}
}
}
skip_freplace_fixup: return;
}
if (val[0] == '-' || isdigit(val[0])) { /* must be a number */
errno = 0;
value = strtoll(val, &val_end, 0); if (errno == ERANGE) {
errno = 0;
value = strtoull(val, &val_end, 0);
} if (errno || *val_end != '\0') {
fprintf(stderr, "Failed to parse value '%s'\n", val); return -EINVAL;
}
rvalue->ivalue = value;
rvalue->type = INTEGRAL;
} else { /* if not a number, consider it enum value */
rvalue->svalue = strdup(val); if (!rvalue->svalue) return -ENOMEM;
rvalue->type = ENUMERATOR;
} return0;
}
if (sscanf(expr, " %[][a-zA-Z0-9_. ] = %s %n", var, val, &n) != 2 || n != strlen(expr)) {
fprintf(stderr, "Failed to parse expression '%s'\n", expr); return -EINVAL;
} /* Remove trailing spaces from var, as scanf may add those */
rtrim(var);
err = parse_rvalue(val, &cur->value); if (err) return err;
cur->full_name = strdup(var); if (!cur->full_name) return -ENOMEM;
err = parse_var_atoms(var, cur); if (err) return err;
f = fopen(filename, "rt"); if (!f) {
err = -errno;
fprintf(stderr, "Failed to open presets in '%s': %s\n", filename, strerror(-err)); return -EINVAL;
}
while (fscanf(f, " %1023[^\n]\n", buf) == 1) { if (buf[0] == '\0' || buf[0] == '#') continue;
err = append_var_preset(&env.presets, &env.npresets, buf); if (err) goto cleanup;
}
cleanup:
fclose(f); return err;
}
staticbool is_signed_type(conststruct btf_type *t)
{ if (btf_is_int(t)) return btf_int_encoding(t) & BTF_INT_SIGNED; if (btf_is_any_enum(t)) return btf_kflag(t); returntrue;
}
staticint enum_value_from_name(conststruct btf *btf, conststruct btf_type *t, constchar *evalue, longlong *retval)
{ if (btf_is_enum(t)) { struct btf_enum *e = btf_enum(t); int i, n = btf_vlen(t);
for (i = 0; i < n; ++i, ++e) { constchar *cur_name = btf__name_by_offset(btf, e->name_off);
if (strcmp(cur_name, evalue) == 0) {
*retval = e->val; return0;
}
}
} elseif (btf_is_enum64(t)) { struct btf_enum64 *e = btf_enum64(t); int i, n = btf_vlen(t);
for (i = 0; i < n; ++i, ++e) { constchar *cur_name = btf__name_by_offset(btf, e->name_off);
__u64 value = btf_enum64_value(e);
base_type = btf__type_by_id(btf, btf__resolve_type(btf, sinfo->type)); if (!base_type) {
fprintf(stderr, "Failed to resolve type %d\n", sinfo->type); return -EINVAL;
} if (!is_preset_supported(base_type)) {
fprintf(stderr, "Can't set %s. Only ints and enums are supported\n",
preset->full_name); return -EINVAL;
}
if (preset->value.type == ENUMERATOR) { if (btf_is_any_enum(base_type)) { if (enum_value_from_name(btf, base_type, preset->value.svalue, &value)) {
fprintf(stderr, "Failed to find integer value for enum element %s\n",
preset->value.svalue); return -EINVAL;
}
} else {
fprintf(stderr, "Value %s is not supported for type %s\n",
preset->value.svalue,
btf__name_by_offset(btf, base_type->name_off)); return -EINVAL;
}
}
/* Check if value fits into the target variable size */ if (sinfo->size < sizeof(value)) { bool is_signed = is_signed_type(base_type);
__u32 unsigned_bits = sinfo->size * 8 - (is_signed ? 1 : 0); longlong max_val = 1ll << unsigned_bits;
if (value >= max_val || value < -max_val) {
fprintf(stderr, "Variable %s value %lld is out of range [%lld; %lld]\n",
btf__name_by_offset(btf, base_type->name_off), value,
is_signed ? -max_val : 0, max_val - 1); return -EINVAL;
}
}
for (k = 0; k < npresets; ++k) { struct btf_var_secinfo tmp_sinfo;
if (strcmp(var_name, presets[k].atoms[0].name) != 0) continue;
if (presets[k].applied) {
fprintf(stderr, "Variable %s is set more than once",
var_name); return -EINVAL;
}
tmp_sinfo = *sinfo;
err = adjust_var_secinfo(btf, var_type,
&tmp_sinfo, presets + k); if (err) return err;
if (!should_process_file_prog(base_filename, NULL)) { if (env.verbose)
printf("Skipping '%s' due to filters...\n", filename);
env.files_skipped++; return0;
} if (!is_bpf_obj_file(filename)) { if (env.verbose)
printf("Skipping '%s' as it's not a BPF object file...\n", filename);
env.files_skipped++; return0;
}
if (!env.quiet && env.out_fmt == RESFMT_TABLE)
printf("Processing '%s'...\n", base_filename);
old_libbpf_print_fn = libbpf_set_print(libbpf_print_fn);
obj = bpf_object__open_file(filename, &opts); if (!obj) { /* if libbpf can't open BPF object file, it could be because *thatBPFobjectfileisincompleteandhastobestatically *linkedintoafinalBPFobjectfile;insteadofbailing *out,reportitintostderr,markitasskipped,and *proceed
*/
fprintf(stderr, "Failed to open '%s': %d\n", filename, -errno);
env.files_skipped++;
err = 0; goto cleanup;
}
switch (id) { case FILE_NAME:
cmp = strcmp(s1->file_name, s2->file_name); break; case PROG_NAME:
cmp = strcmp(s1->prog_name, s2->prog_name); break; case ATTACH_TYPE: case PROG_TYPE: case SIZE: case JITED_SIZE: case STACK: case VERDICT: case DURATION: case TOTAL_INSNS: case TOTAL_STATES: case PEAK_STATES: case MAX_STATES_PER_INSN: case MEMORY_PEAK: case MARK_READ_MAX_LEN: { long v1 = s1->stats[id]; long v2 = s2->stats[id];
for (i = 0; i < env.output_spec.spec_cnt; i++) { int id = env.output_spec.ids[i]; int *max_len = &env.output_spec.lens[i], len; constchar *str = NULL; long val = 0;
prepare_value(s, id, &str, &val);
switch (fmt) { case RESFMT_TABLE_CALCLEN: if (str)
len = snprintf(NULL, 0, "%s", str); else
len = snprintf(NULL, 0, "%ld", val); if (len > *max_len)
*max_len = len; break; case RESFMT_TABLE: if (str)
printf("%s%-*s", i == 0 ? "" : COLUMN_SEP, *max_len, str); else
printf("%s%*ld", i == 0 ? "" : COLUMN_SEP, *max_len, val); if (i == env.output_spec.spec_cnt - 1)
printf("\n"); break; case RESFMT_CSV: if (str)
printf("%s%s", i == 0 ? "" : ",", str); else
printf("%s%ld", i == 0 ? "" : ",", val); if (i == env.output_spec.spec_cnt - 1)
printf("\n"); break;
}
}
st = &(*statsp)[*stat_cntp];
memset(st, 0, sizeof(*st));
*stat_cntp += 1;
}
while ((next = strtok_r(cnt++ ? NULL : input, ",\n", &state))) { if (header) { /* for the first line, set up spec stats */
err = parse_stat(next, specs); if (err) goto cleanup; continue;
}
/* for all other lines, parse values based on spec */ if (col >= specs->spec_cnt) {
fprintf(stderr, "Found extraneous column #%d in row #%d of '%s'\n",
col, *stat_cntp, filename);
err = -EINVAL; goto cleanup;
}
err = parse_stat_value(next, specs->ids[col], st); if (err) goto cleanup;
col++;
}
if (header) {
header = false; continue;
}
if (col < specs->spec_cnt) {
fprintf(stderr, "Not enough columns in row #%d in '%s'\n",
*stat_cntp, filename);
err = -EINVAL; goto cleanup;
}
if (!st->file_name || !st->prog_name) {
fprintf(stderr, "Row #%d in '%s' is missing file and/or program name\n",
*stat_cntp, filename);
err = -EINVAL; goto cleanup;
}
/* in comparison mode we can only check filters after we *parsedentireline;ifrowshouldbeignoredwepretendwe *neverparsedit
*/ if (!should_process_file_prog(st->file_name, st->prog_name)) {
free(st->file_name);
free(st->prog_name);
*stat_cntp -= 1;
}
}
if (!feof(f)) {
err = -errno;
fprintf(stderr, "Failed I/O for '%s': %d\n", filename, err);
}
for (i = 0; i < env.output_spec.spec_cnt; i++) { int id = env.output_spec.ids[i]; /* key stats don't have A/B/DIFF columns, they are common for both data sets */ int max_j = is_key_stat(id) ? 1 : 3;
for (j = 0; j < max_j; j++) { int *max_len = &env.output_spec.lens[3 * i + j]; bool last = (i == env.output_spec.spec_cnt - 1) && (j == max_j - 1); constchar *sfx;
switch (fmt) { case RESFMT_TABLE_CALCLEN:
sfx = is_key_stat(id) ? "" : table_sfxs[j];
len = snprintf(NULL, 0, "%s%s", stat_defs[id].header, sfx); if (len > *max_len)
*max_len = len; break; case RESFMT_TABLE:
sfx = is_key_stat(id) ? "" : table_sfxs[j];
printf("%s%-*s%s", i + j == 0 ? "" : COLUMN_SEP,
*max_len - (int)strlen(sfx), stat_defs[id].header, sfx); if (last)
printf("\n"); break; case RESFMT_CSV:
sfx = is_key_stat(id) ? "" : name_sfxs[j];
printf("%s%s%s", i + j == 0 ? "" : ",", stat_defs[id].names[0], sfx); if (last)
printf("\n"); break;
}
}
}
if (fmt == RESFMT_TABLE)
output_comp_header_underlines();
}
for (i = 0; i < env.output_spec.spec_cnt; i++) { int id = env.output_spec.ids[i], len; int *max_len_base = &env.output_spec.lens[3 * i + 0]; int *max_len_comp = &env.output_spec.lens[3 * i + 1]; int *max_len_diff = &env.output_spec.lens[3 * i + 2]; constchar *base_str = NULL, *comp_str = NULL; long base_val = 0, comp_val = 0, diff_val = 0;
switch (f->op) { case OP_EQ: return value > f->value - eps && value < f->value + eps; case OP_NEQ: return value < f->value - eps || value > f->value + eps; case OP_LT: return value < f->value - eps; case OP_LE: return value <= f->value + eps; case OP_GT: return value > f->value + eps; case OP_GE: return value >= f->value - eps;
}
fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); returnfalse;
}
staticbool should_output_join_stats(conststruct verif_stats_join *stats)
{ struct filter *f; int i, allow_cnt = 0;
for (i = 0; i < env.deny_filter_cnt; i++) {
f = &env.deny_filters[i]; if (f->kind != FILTER_STAT) continue;
if (is_join_stat_filter_matched(f, stats)) returnfalse;
}
for (i = 0; i < env.allow_filter_cnt; i++) {
f = &env.allow_filters[i]; if (f->kind != FILTER_STAT) continue;
allow_cnt++;
if (is_join_stat_filter_matched(f, stats)) returntrue;
}
/* if there are no stat allowed filters, pass everything through */ return allow_cnt == 0;
}
err = parse_stats_csv(env.filenames[0], &base_specs,
&env.baseline_stats, &env.baseline_stat_cnt); if (err) {
fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[0], err); return err;
}
err = parse_stats_csv(env.filenames[1], &comp_specs,
&env.prog_stats, &env.prog_stat_cnt); if (err) {
fprintf(stderr, "Failed to parse stats from '%s': %d\n", env.filenames[1], err); return err;
}
/* To keep it simple we validate that the set and order of stats in *bothCSVsareexactlythesame.Thiscanbeliftedwithabitmore *pre-processinglater.
*/ if (base_specs.spec_cnt != comp_specs.spec_cnt) {
fprintf(stderr, "Number of stats in '%s' and '%s' differs (%d != %d)!\n",
env.filenames[0], env.filenames[1],
base_specs.spec_cnt, comp_specs.spec_cnt); return -EINVAL;
} for (i = 0; i < base_specs.spec_cnt; i++) { if (base_specs.ids[i] != comp_specs.ids[i]) {
fprintf(stderr, "Stats composition differs between '%s' and '%s' (%s != %s)!\n",
env.filenames[0], env.filenames[1],
stat_defs[base_specs.ids[i]].names[0],
stat_defs[comp_specs.ids[i]].names[0]); return -EINVAL;
}
}
/* Join two datasets together. If baseline and comparison datasets *havedifferentsubsetofrows(wematchby'object+prog'as *auniquekey)thenassumeempty/missing/zerovalueforrowsthat *aremissingintheoppositedataset.
*/
i = j = 0; while (i < env.baseline_stat_cnt || j < env.prog_stat_cnt) { conststruct verif_stats *base, *comp; struct verif_stats_join *join; void *tmp; int r;
base = i < env.baseline_stat_cnt ? &env.baseline_stats[i] : &fallback_stats;
comp = j < env.prog_stat_cnt ? &env.prog_stats[j] : &fallback_stats;
if (!base->file_name || !base->prog_name) {
fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n",
i, env.filenames[0]); return -EINVAL;
} if (!comp->file_name || !comp->prog_name) {
fprintf(stderr, "Entry #%d in '%s' doesn't have file and/or program name specified!\n",
j, env.filenames[1]); return -EINVAL;
}
/* now sort joined results according to sort spec */
qsort(env.join_stats, env.join_stat_cnt, sizeof(*env.join_stats), cmp_join_stats);
/* for human-readable table output we need to do extra pass to *calculatecolumnwidths,sowesubstitutecurrentoutputformat *withRESFMT_TABLE_CALCLENandlaterrevertitbacktoRESFMT_TABLE *anddoeverythingagain.
*/ if (env.out_fmt == RESFMT_TABLE)
cur_fmt = RESFMT_TABLE_CALCLEN; else
cur_fmt = env.out_fmt;
one_more_time:
output_comp_headers(cur_fmt);
last_idx = -1;
cnt = 0; for (i = 0; i < env.join_stat_cnt; i++) { conststruct verif_stats_join *join = &env.join_stats[i];
if (!should_output_join_stats(join)) continue;
if (env.top_n && cnt >= env.top_n) break;
if (cur_fmt == RESFMT_TABLE_CALCLEN)
last_idx = i;
output_comp_stats(join, cur_fmt, i == last_idx);
cnt++;
}
if (cur_fmt == RESFMT_TABLE_CALCLEN) {
cur_fmt = RESFMT_TABLE; goto one_more_time; /* ... this time with feeling */
}
return0;
}
staticbool is_stat_filter_matched(struct filter *f, conststruct verif_stats *stats)
{ long value = stats->stats[f->stat_id];
if (f->abs)
value = value < 0 ? -value : value;
switch (f->op) { case OP_EQ: return value == f->value; case OP_NEQ: return value != f->value; case OP_LT: return value < f->value; case OP_LE: return value <= f->value; case OP_GT: return value > f->value; case OP_GE: return value >= f->value;
}
fprintf(stderr, "BUG: unknown filter op %d!\n", f->op); returnfalse;
}
staticbool should_output_stats(conststruct verif_stats *stats)
{ struct filter *f; int i, allow_cnt = 0;
for (i = 0; i < env.deny_filter_cnt; i++) {
f = &env.deny_filters[i]; if (f->kind != FILTER_STAT) continue;
if (is_stat_filter_matched(f, stats)) returnfalse;
}
for (i = 0; i < env.allow_filter_cnt; i++) {
f = &env.allow_filters[i]; if (f->kind != FILTER_STAT) continue;
allow_cnt++;
if (is_stat_filter_matched(f, stats)) returntrue;
}
/* if there are no stat allowed filters, pass everything through */ return allow_cnt == 0;
}
staticvoid output_prog_stats(void)
{ conststruct verif_stats *stats; int i, last_stat_idx = 0, cnt = 0;
if (env.out_fmt == RESFMT_TABLE) { /* calculate column widths */
output_headers(RESFMT_TABLE_CALCLEN); for (i = 0; i < env.prog_stat_cnt; i++) {
stats = &env.prog_stats[i]; if (!should_output_stats(stats)) continue;
output_stats(stats, RESFMT_TABLE_CALCLEN, false);
last_stat_idx = i;
}
}
/* actually output the table */
output_headers(env.out_fmt); for (i = 0; i < env.prog_stat_cnt; i++) {
stats = &env.prog_stats[i]; if (!should_output_stats(stats)) continue; if (env.top_n && cnt >= env.top_n) break;
output_stats(stats, env.out_fmt, i == last_stat_idx);
cnt++;
}
}
staticint handle_verif_mode(void)
{ int i, err = 0;
if (env.filename_cnt == 0) {
fprintf(stderr, "Please provide path to BPF object file!\n\n");
argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); return -EINVAL;
}
create_stat_cgroup(); for (i = 0; i < env.filename_cnt; i++) {
err = process_obj(env.filenames[i]); if (err) {
fprintf(stderr, "Failed to process '%s': %d\n", env.filenames[i], err); goto out;
}
}
int main(int argc, char **argv)
{ int err = 0, i, j;
if (argp_parse(&argp, argc, argv, 0, NULL, NULL)) return1;
if (env.show_version) {
printf("%s\n", argp_program_version); return0;
}
if (env.verbose && env.quiet) {
fprintf(stderr, "Verbose and quiet modes are incompatible, please specify just one or neither!\n\n");
argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); return1;
} if (env.verbose && env.log_level == 0)
env.log_level = 1;
if (env.output_spec.spec_cnt == 0) { if (env.out_fmt == RESFMT_CSV)
env.output_spec = default_csv_output_spec; else
env.output_spec = default_output_spec;
} if (env.sort_spec.spec_cnt == 0)
env.sort_spec = default_sort_spec;
if (env.comparison_mode && env.replay_mode) {
fprintf(stderr, "Can't specify replay and comparison mode at the same time!\n\n");
argp_help(&argp, stderr, ARGP_HELP_USAGE, "veristat"); return1;
}
free_verif_stats(env.prog_stats, env.prog_stat_cnt);
free_verif_stats(env.baseline_stats, env.baseline_stat_cnt);
free(env.join_stats); for (i = 0; i < env.filename_cnt; i++)
free(env.filenames[i]);
free(env.filenames); for (i = 0; i < env.allow_filter_cnt; i++) {
free(env.allow_filters[i].any_glob);
free(env.allow_filters[i].file_glob);
free(env.allow_filters[i].prog_glob);
}
free(env.allow_filters); for (i = 0; i < env.deny_filter_cnt; i++) {
free(env.deny_filters[i].any_glob);
free(env.deny_filters[i].file_glob);
free(env.deny_filters[i].prog_glob);
}
free(env.deny_filters); for (i = 0; i < env.npresets; ++i) {
free(env.presets[i].full_name); for (j = 0; j < env.presets[i].atom_count; ++j) { switch (env.presets[i].atoms[j].type) { case FIELD_NAME:
free(env.presets[i].atoms[j].name); break; case ARRAY_INDEX: if (env.presets[i].atoms[j].index.type == ENUMERATOR)
free(env.presets[i].atoms[j].index.svalue); break;
}
}
free(env.presets[i].atoms);
}
free(env.presets); return -err;
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.84Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-12)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.