diff --git a/adaptived/doc/internal/list-of-built-in-causes.md b/adaptived/doc/internal/list-of-built-in-causes.md index 7daaea8..cf678c0 100644 --- a/adaptived/doc/internal/list-of-built-in-causes.md +++ b/adaptived/doc/internal/list-of-built-in-causes.md @@ -5,6 +5,7 @@ Parameters that accept long long or float also support some human-readable forma | Cause | Trigger | Schema | Examples | Notes | | ----- | ------- | ------ | -------- | ----- | | [always](../../src/causes/always.c) | Will trigger every single time it's run. Likely only useful for testing and debugging | | [ftest 021](../../tests/ftests/021-effect-cgroup_setting_sub_int.json) | | +| [cgroup_data](../../src/causes/cgroup_data.c) | Always triggers but can be used in conjunction with other causes to limit its trigger rate. Gathers a cgroup hierarchy's settings and values. Data is shared with effects in the same rule using the shared_data mechanism | | [ftest 071](../../tests/ftests/071-cause-cgroup_data.json)
[ftest 072](../../tests/ftests/072-cause-cgroup_data2.json.token) | | | [cgroup_setting](../../src/causes/cgroup_setting.c) | Will trigger when a cgroup setting exceeds the specified threshold. (Note - will work on any file that contains a float or long long) | | [ftest 029](../../tests/ftests/029-cause-cgroup_setting_ll_gt.json)
[ftest 030](../../tests/ftests/030-cause-cgroup_setting_ll_lt.json)
[ftest 031](../../tests/ftests/031-cause-cgroup_setting_float_gt.json)
[ftest 032](../../tests/ftests/032-cause-cgroup_setting_float_lt.json) | | | [days_of_the_week](../../src/causes/days_of_the_week.c) | Will trigger when today matches one of the specified day(s) (Monday, Tuesday, etc.) in the config file | | [Jimmy Buffett Example](../examples/jimmy-buffett-config.json)
[ftest 004](../../tests/ftests/004-register_plugin_effect.json) | | | [meminfo](../../src/causes/meminfo.c) | Will trigger when a field in /proc/meminfo exceeds the specified threshold | | [ftest 048](../../tests/ftests/048-cause-meminfo_gt.json)
[ftest 049](../../tests/ftests/049-cause-meminfo_lt.json)
[ftest 050](../../tests/ftests/050-cause-meminfo_eq.json) | | diff --git a/adaptived/doc/internal/list-of-built-in-effects.md b/adaptived/doc/internal/list-of-built-in-effects.md index a8fe1db..4de0855 100644 --- a/adaptived/doc/internal/list-of-built-in-effects.md +++ b/adaptived/doc/internal/list-of-built-in-effects.md @@ -12,7 +12,7 @@ Parameters that accept long long or float also support some human-readable forma | [kill_cgroup_by_psi](../../src/effects/kill_cgroup_by_psi.c) | Walk a cgroup tree, and kill the processes in the cgroup with the highest PSI utilization | | [ftest 024](../../tests/ftests/024-effect-kill_cgroup_by_psi.json) | | | [kill_processes](../../src/effects/kill_processes.c) | Kill processes that match the specified process name(s) | | [ftest 067](../../tests/ftests/067-effect-kill_processes.json)
[ftest 068](../../tests/ftests/068-effect-kill_processes_rss.json) | | | [logger](../../src/effects/logger.c) | Given an array of files, write their contents to "logfile" | | [ftest 043](../../tests/ftests/043-effect-logger-no-separators.json)
[ftest 044](../../tests/ftests/044-effect-logger-date-format.json) | | -| [print](../../src/effects/print.c) | Print a message to a file | | [Jimmy Buffett Example](../examples/jimmy-buffett-config.json) | | +| [print](../../src/effects/print.c) | Print a message to a file | | [Jimmy Buffett Example](../examples/jimmy-buffett-config.json)
[ftest 071](../../tests/ftests/071-cause-cgroup_data.json)
[ftest 072](../../tests/ftests/072-cause-cgroup_data2.json.token) | | | [print_schedstat](../../src/effects/print_schedstat.c) | Print schedstat to a file | | [ftest 054](../../tests/ftests/054-effect-print_schedstat.json) | | | [sd_bus_setting](../../src/effects/sd_bus_setting.c) | Operate on sd_bus properties | | [ftest 1000](../../tests/ftests/1000-sudo-effect-sd_bus_setting_set_int.json)
[ftest 1001](../../tests/ftests/1001-sudo-effect-sd_bus_setting_add_int.json)
[ftest 1002](../../tests/ftests/1002-sudo-effect-sd_bus_setting_sub_int.json)
[ftest 1003](../../tests/ftests/1003-sudo-effect-sd_bus_setting-CPUQuota.json)
[ftest 1004](../../tests/ftests/1004-sudo-effect-sd_bus_setting_add_int_infinity.json)
[ftest 1005](../../tests/ftests/1005-sudo-effect-sd_bus_setting_sub_infinity.json)
[ftest 1006](../../tests/ftests/1006-sudo-effect-sd_bus_setting_set_int_scope.json)
[ftest 1007](../../tests/ftests/1007-sudo-effect-sd_bus_setting_set_str.json) | | | [setting](../../src/effects/cgroup_setting.c) | Write to a setting file | | [ftest 055](../../tests/ftests/055-effect-setting_set_int.json)
[ftest 056](../../tests/ftests/056-effect-setting_add_int.json)
[ftest 057](../../tests/ftests/057-effect-setting_sub_int.json) | Shares a code base with the cgroup effect code | diff --git a/adaptived/include/adaptived.h b/adaptived/include/adaptived.h index d2afb4f..9cd9d06 100644 --- a/adaptived/include/adaptived.h +++ b/adaptived/include/adaptived.h @@ -70,8 +70,9 @@ enum adaptived_sdata_type { */ ADAPTIVED_SDATA_CUSTOM = 0, ADAPTIVED_SDATA_STR, - ADAPTIVED_SDATA_CGROUP, - ADAPTIVED_SDATA_NAME_VALUE, + ADAPTIVED_SDATA_CGROUP, /* struct adaptived_cgroup_value */ + ADAPTIVED_SDATA_NAME_VALUE, /* struct adaptived_name_and_value */ + ADAPTIVED_SDATA_CGROUP_SETTING_VALUE, /* struct adaptived_cgroup_setting_and_value */ ADAPTIVED_SDATA_CNT }; @@ -117,6 +118,12 @@ struct adaptived_name_and_value { struct adaptived_cgroup_value *value; }; +struct adaptived_cgroup_setting_and_value { + char *cgroup_name; + char *setting; + struct adaptived_cgroup_value *value; +}; + struct adaptived_rule_stats { int cause_cnt; int effect_cnt; diff --git a/adaptived/src/Makefile.am b/adaptived/src/Makefile.am index c7e391c..bdd20cc 100644 --- a/adaptived/src/Makefile.am +++ b/adaptived/src/Makefile.am @@ -6,6 +6,7 @@ SUBDIRS = ${DIST_SUBDIRS} SOURCES = \ adaptived-internal.h \ causes/always.c \ + causes/cgroup_data.c \ causes/cgroup_setting.c \ causes/days_of_the_week.c \ causes/meminfo.c \ diff --git a/adaptived/src/adaptived-internal.h b/adaptived/src/adaptived-internal.h index 2f681f4..954aabd 100644 --- a/adaptived/src/adaptived-internal.h +++ b/adaptived/src/adaptived-internal.h @@ -148,6 +148,16 @@ int _sort_pid_list(const void *p1, const void *p2); struct adaptived_rule *rule_init(const char * const name); void rule_destroy(struct adaptived_rule ** rule); +/* + * shared_data.c functions + */ + +int write_sdata_cgroup_setting_value(struct adaptived_cause * const cse, + const char * const cgroup_name, + const char * const setting, + const struct adaptived_cgroup_value * const value, + uint32_t flags); + /* * mem_utils defines */ diff --git a/adaptived/src/cause.c b/adaptived/src/cause.c index 644bb5e..6dbc56b 100644 --- a/adaptived/src/cause.c +++ b/adaptived/src/cause.c @@ -55,6 +55,7 @@ const char * const cause_names[] = { "memory.stat", "top", "cgroup_memory_setting", + "cgroup_data", }; static_assert(ARRAY_SIZE(cause_names) == CAUSE_CNT, "cause_names[] must be same length as CAUSE_CNT"); @@ -81,6 +82,7 @@ const struct adaptived_cause_functions cause_fns[] = { {memorystat_init, memorystat_main, memorystat_exit}, {top_init, top_main, top_exit}, {cgset_memory_init, cgset_memory_main, cgset_exit}, + {cgroup_data_init, cgroup_data_main, cgroup_data_exit}, }; static_assert(ARRAY_SIZE(cause_fns) == CAUSE_CNT, "cause_fns[] must be same length as CAUSE_CNT"); diff --git a/adaptived/src/cause.h b/adaptived/src/cause.h index 413ee6b..d535913 100644 --- a/adaptived/src/cause.h +++ b/adaptived/src/cause.h @@ -63,6 +63,7 @@ enum cause_enum { MEMORYSTAT, TOP, CGROUP_MEMORY_SETTING, + CGROUP_DATA, CAUSE_CNT }; @@ -144,4 +145,8 @@ int top_init(struct adaptived_cause * const cse, struct json_object *args_obj, i int top_main(struct adaptived_cause * const cse, int time_since_last_run); void top_exit(struct adaptived_cause * const cse); +int cgroup_data_init(struct adaptived_cause * const cse, struct json_object *args_obj, int interval); +int cgroup_data_main(struct adaptived_cause * const cse, int time_since_last_run); +void cgroup_data_exit(struct adaptived_cause * const cse); + #endif /* __ADAPTIVED_CAUSE_H */ diff --git a/adaptived/src/causes/cgroup_data.c b/adaptived/src/causes/cgroup_data.c new file mode 100644 index 0000000..9d3a69b --- /dev/null +++ b/adaptived/src/causes/cgroup_data.c @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2024-2025, Oracle and/or its affiliates. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/** + * Cause that will gather cgroup data into a shared data structure + * + */ + +#include +#include +#include +#include + +#include +#include + +#include "adaptived-internal.h" +#include "shared_data.h" +#include "defines.h" + +static const char * const slash_path = "/"; + +struct cgroup_data_opts { + char *cgroup_path; + size_t cgroup_path_len; /* calculated from the original path provided */ + char **settings; + int settings_cnt; /* calculated from the length of the settings json array */ + int max_depth; /* optional */ + bool rel_paths; /* optional */ +}; + +static void free_opts(struct cgroup_data_opts * const opts) +{ + int i; + + if (!opts) + return; + + if (opts->cgroup_path) + free(opts->cgroup_path); + + for (i = 0; i < opts->settings_cnt; i++) { + if (opts->settings[i]) + free(opts->settings[i]); + } + + free(opts); +} + +int cgroup_data_init(struct adaptived_cause * const cse, struct json_object *args_obj, + int interval) +{ + struct json_object *settings_obj, *setting_obj; + const char *cgroup_path, *setting; + struct cgroup_data_opts *opts; + json_bool exists; + int i, ret = 0; + + opts = malloc(sizeof(struct cgroup_data_opts)); + if (!opts) { + ret = -ENOMEM; + goto error; + } + + memset(opts, 0, sizeof(struct cgroup_data_opts)); + + ret = adaptived_parse_string(args_obj, "cgroup", &cgroup_path); + if (ret) { + adaptived_err("Failed to parse the cgroup path: %d\n", ret); + goto error; + } + + /* +1 to add a trailing "/", +1 to add a trailing "*", and +1 for the NULL charactoer */ + opts->cgroup_path = malloc(sizeof(char) * (strlen(cgroup_path) + 3)); + if (!opts->cgroup_path) { + ret = -ENOMEM; + goto error; + } + + strcpy(opts->cgroup_path, cgroup_path); + opts->cgroup_path[strlen(cgroup_path)] = '\0'; + + /* Remove all trailing "/" characters */ + i = strlen(opts->cgroup_path) - 1; + while (opts->cgroup_path[i] == '/') { + opts->cgroup_path[i] = '\0'; + i--; + } + + opts->cgroup_path_len = strlen(opts->cgroup_path); + + /* + * Add a trailing '/' and '*' to the path. This instructs the path walk code to + * not list the starting directory. + */ + opts->cgroup_path[strlen(opts->cgroup_path)] = '/'; + opts->cgroup_path[strlen(opts->cgroup_path)] = '*'; + + exists = json_object_object_get_ex(args_obj, "settings", &settings_obj); + if (!exists || !settings_obj) { + ret = -EINVAL; + goto error; + } + + opts->settings_cnt = json_object_array_length(settings_obj); + + opts->settings = malloc(sizeof(char *) * opts->settings_cnt); + if (!opts->settings) { + ret = -ENOMEM; + goto error; + } + memset(opts->settings, 0, sizeof(char *) * opts->settings_cnt); + + for (i = 0; i < opts->settings_cnt; i++) { + setting_obj = json_object_array_get_idx(settings_obj, i); + if (!setting_obj) { + ret = -EINVAL; + goto error; + } + + ret = adaptived_parse_string(setting_obj, "setting", &setting); + if (ret) { + adaptived_err("Failed to parse the setting: %d\n", ret); + goto error; + } + + opts->settings[i] = malloc(sizeof(char) * (strlen(setting) + 1)); + if (!opts->settings[i]) { + ret = -ENOMEM; + goto error; + } + + strcpy(opts->settings[i], setting); + opts->settings[i][strlen(setting)] = '\0'; + } + + ret = adaptived_parse_int(args_obj, "max_depth", &opts->max_depth); + if (ret == -ENOENT) { + /* If unspecified, only enumerate the direct children */ + opts->max_depth = 0; + ret = 0; + } else if (ret) { + goto error; + } + + ret = adaptived_parse_bool(args_obj, "rel_paths", &opts->rel_paths); + if (ret == -ENOENT) { + opts->rel_paths = true; + ret = 0; + } else if (ret) { + adaptived_err("Failed to parse rel_paths arg: %d", ret); + goto error; + } + + ret = adaptived_cause_set_data(cse, (void *)opts); + if (ret) + goto error; + + return ret; + +error: + free_opts(opts); + return ret; +} + +static int read_settings(struct adaptived_cause * const cse, struct cgroup_data_opts * const opts, + const char * const cg_path) +{ + char *setting_path = NULL; + struct adaptived_cgroup_value val; + size_t setting_path_len; + const char *sdata_path; + int ret, i; + + for (i = 0; i < opts->settings_cnt; i++) { + /* Add 1 for the middle "/" and 1 for the null character */ + setting_path_len = sizeof(char) * (strlen(cg_path) + strlen(opts->settings[i]) + 2); + + setting_path = malloc(setting_path_len); + if (!setting_path) { + ret = -ENOMEM; + goto error; + } + + sprintf(setting_path, "%s/%s", cg_path, opts->settings[i]); + + val.type = ADAPTIVED_CGVAL_DETECT; + ret = adaptived_cgroup_get_value(setting_path, &val); + if (ret == -ENOENT) { + /* + * This cgroup doesn't have the requested file. It's likely that + * the particular controller isn't enabled for this cgroup, and + * thus this isn't a fatal error. Continue on + */ + free(setting_path); + setting_path = NULL; + ret = 0; + continue; + } else if (ret) { + goto error; + } + + if (opts->rel_paths) { + if (strlen(cg_path) <= opts->cgroup_path_len + 1) + /* + * Since we currently don't publish cgroup data for the root + * directory, this line should be impossible to hit, but I'm + * leaving it in out of an abundance of caution. + */ + sdata_path = slash_path; + else + sdata_path = &cg_path[opts->cgroup_path_len + 1]; + } else { + sdata_path = cg_path; + } + + ret = write_sdata_cgroup_setting_value(cse, sdata_path, opts->settings[i], &val, + ADAPTIVED_SDATAF_PERSIST); + if (ret) + goto error; + + free(setting_path); + setting_path = NULL; + } + +error: + if (setting_path) + free(setting_path); + + return ret; +} + +int cgroup_data_main(struct adaptived_cause * const cse, int time_since_last_run) +{ + struct cgroup_data_opts *opts = (struct cgroup_data_opts *)adaptived_cause_get_data(cse); + struct adaptived_path_walk_handle *handle = NULL; + char *cur_path = NULL; + int ret; + + free_shared_data(cse, true); + + ret = adaptived_path_walk_start(opts->cgroup_path, &handle, + ADAPTIVED_PATH_WALK_LIST_DIRS, opts->max_depth); + if (ret) + goto error; + + do { + ret = adaptived_path_walk_next(&handle, &cur_path); + if (ret) + goto error; + if (!cur_path) + /* We've reached the end of the tree */ + break; + + ret = read_settings(cse, opts, cur_path); + if (ret) + goto error; + + free(cur_path); + } while (true); + +error: + adaptived_path_walk_end(&handle); + if (cur_path) + free(cur_path); + + if (ret == 0) + /* Unless there is an error, this cause always triggers */ + return 1; + + return ret; +} + +void cgroup_data_exit(struct adaptived_cause * const cse) +{ + struct cgroup_data_opts *opts = (struct cgroup_data_opts *)adaptived_cause_get_data(cse); + + free_shared_data(cse, true); + free_opts(opts); +} diff --git a/adaptived/src/effects/print.c b/adaptived/src/effects/print.c index 2daa50f..16d7c4a 100644 --- a/adaptived/src/effects/print.c +++ b/adaptived/src/effects/print.c @@ -44,8 +44,10 @@ enum file_enum { struct print_opts { FILE *file; + bool close_file; /* internal flag to tell us to close the file on exit */ const struct adaptived_cause *cse; char *msg; + bool shared_data; }; int print_init(struct adaptived_effect * const eff, struct json_object *args_obj, @@ -86,10 +88,27 @@ int print_init(struct adaptived_effect * const eff, struct json_object *args_obj if (strncmp(file_str, "stderr", strlen("stderr")) == 0) { opts->file = stderr; + opts->close_file = false; } else if (strncmp(file_str, "stdout", strlen("stdout")) == 0) { opts->file = stdout; + opts->close_file = false; } else { - ret = -EINVAL; + opts->file = fopen(file_str, "w"); + if (!opts->file) { + adaptived_err("Failed to fopen %s\n", file_str); + ret = -EINVAL; + goto error; + } + + opts->close_file = true; + } + + ret = adaptived_parse_bool(args_obj, "shared_data", &opts->shared_data); + if (ret == -ENOENT) { + opts->shared_data = false; + ret = 0; + } else if (ret) { + adaptived_err("Failed to parse shared_data arg: %d\n", ret); goto error; } @@ -111,6 +130,128 @@ int print_init(struct adaptived_effect * const eff, struct json_object *args_obj return ret; } +static void print_custom(const struct print_opts * const opts, void *data) +{ + fprintf(opts->file, "Cause %s shared custom data 0x%p\n", opts->cse->name, data); +} + +static void print_str(const struct print_opts * const opts, char *data) +{ + fprintf(opts->file, "Cause %s shared string \"%s\"\n", opts->cse->name, data); +} + +static void print_cgroup(const struct print_opts * const opts, struct adaptived_cgroup_value *data) +{ + switch(data->type) { + case ADAPTIVED_CGVAL_STR: + fprintf(opts->file, "Cause %s shared string value \"%s\"\n", opts->cse->name, + data->value.str_value); + break; + case ADAPTIVED_CGVAL_LONG_LONG: + fprintf(opts->file, "Cause %s shared long long value \"%lld\"\n", opts->cse->name, + data->value.ll_value); + break; + case ADAPTIVED_CGVAL_FLOAT: + fprintf(opts->file, "Cause %s shared float value \"%f\"\n", opts->cse->name, + data->value.float_value); + break; + default: + adaptived_err("Unsupported cgroup type: %d\n", data->type); + } +} + +static void print_name_value(const struct print_opts * const opts, + struct adaptived_name_and_value *data) +{ + switch(data->value->type) { + case ADAPTIVED_CGVAL_STR: + fprintf(opts->file, "Cause %s shared name \"%s\" and string value \"%s\"\n", + opts->cse->name, data->name, data->value->value.str_value); + break; + case ADAPTIVED_CGVAL_LONG_LONG: + fprintf(opts->file, "Cause %s shared name \"%s\" and long long value \"%lld\"\n", + opts->cse->name, data->name, data->value->value.ll_value); + break; + case ADAPTIVED_CGVAL_FLOAT: + fprintf(opts->file, "Cause %s shared name \"%s\" and float value \"%f\"\n", + opts->cse->name, data->name, data->value->value.float_value); + break; + default: + adaptived_err("Unsupported cgroup type: %d\n", data->value->type); + } +} + +static void print_cgroup_setting_and_value(const struct print_opts * const opts, + struct adaptived_cgroup_setting_and_value *data) +{ + switch(data->value->type) { + case ADAPTIVED_CGVAL_STR: + fprintf(opts->file, + "Cause \"%s\" shared\n\tcgroup \"%s\"\n\tsetting \"%s\"\n\tstring value \"%s\"\n", + opts->cse->name, data->cgroup_name, data->setting, + data->value->value.str_value); + break; + case ADAPTIVED_CGVAL_LONG_LONG: + fprintf(opts->file, + "Cause \"%s\" shared\n\tcgroup \"%s\"\n\tsetting \"%s\"\n\tlong long value \"%lld\"\n", + opts->cse->name, data->cgroup_name, data->setting, + data->value->value.ll_value); + break; + case ADAPTIVED_CGVAL_FLOAT: + fprintf(opts->file, + "Cause \"%s\" shared\n\tcgroup \"%s\"\n\tsetting \"%s\"\n\tfloat value \"%f\"\n", + opts->cse->name, data->cgroup_name, data->setting, + data->value->value.float_value); + break; + default: + adaptived_err("Unsupported cgroup type: %d\n", data->value->type); + } +} + +static void print_shared_data(const struct print_opts * const opts) +{ + const struct adaptived_cause *cse = opts->cse; + enum adaptived_sdata_type type; + void *read_data; + int ret, cnt, i; + uint32_t flags; + + while (cse) { + cnt = adaptived_get_shared_data_cnt(cse); + + for (i = 0; i < cnt; i++) { + ret = adaptived_get_shared_data(cse, i, &type, &read_data, &flags); + if (ret) { + adaptived_err("Failed to get shared data: %d\n", ret); + cnt--; + continue; + } + + switch(type) { + case ADAPTIVED_SDATA_CUSTOM: + print_custom(opts, read_data); + break; + case ADAPTIVED_SDATA_STR: + print_str(opts, read_data); + break; + case ADAPTIVED_SDATA_CGROUP: + print_cgroup(opts, read_data); + break; + case ADAPTIVED_SDATA_NAME_VALUE: + print_name_value(opts, read_data); + break; + case ADAPTIVED_SDATA_CGROUP_SETTING_VALUE: + print_cgroup_setting_and_value(opts, read_data); + break; + default: + adaptived_err("Unsupported shared data type: %d\n", type); + } + } + + cse = cse->next; + } +} + int print_main(struct adaptived_effect * const eff) { struct print_opts *opts = (struct print_opts *)eff->data; @@ -129,6 +270,9 @@ int print_main(struct adaptived_effect * const eff) } } + if (opts->shared_data) + print_shared_data(opts); + return 0; } @@ -136,6 +280,8 @@ void print_exit(struct adaptived_effect * const eff) { struct print_opts *opts = (struct print_opts *)eff->data; + if (opts->close_file) + fclose(opts->file); if (opts->msg) free(opts->msg); free(opts); diff --git a/adaptived/src/shared_data.c b/adaptived/src/shared_data.c index c20bc09..94e7808 100644 --- a/adaptived/src/shared_data.c +++ b/adaptived/src/shared_data.c @@ -26,12 +26,66 @@ #include #include +#include #include #include "adaptived-internal.h" #include "shared_data.h" #include "cause.h" +int write_sdata_cgroup_setting_value(struct adaptived_cause * const cse, + const char * const cgroup_name, + const char * const setting, + const struct adaptived_cgroup_value * const value, + uint32_t flags) +{ + struct adaptived_cgroup_setting_and_value *sdata = NULL; + int ret; + + if (!cse || !cgroup_name || !setting || !value) + return -EINVAL; + + sdata = malloc(sizeof(struct adaptived_cgroup_setting_and_value)); + if (!sdata) + return -ENOMEM; + memset(sdata, 0, sizeof(struct adaptived_cgroup_setting_and_value)); + + sdata->cgroup_name = strdup(cgroup_name); + if (!sdata->cgroup_name) { + ret = -ENOMEM; + goto error; + } + + sdata->setting = strdup(setting); + if (!sdata->setting) { + ret = -ENOMEM; + goto error; + } + + sdata->value = malloc(sizeof(struct adaptived_cgroup_value)); + if (!sdata->value) { + ret = -ENOMEM; + goto error; + } + + memcpy(sdata->value, value, sizeof(struct adaptived_cgroup_value)); + + ret = adaptived_write_shared_data(cse, ADAPTIVED_SDATA_CGROUP_SETTING_VALUE, + sdata, NULL, flags); + + return ret; + +error: + if (sdata->cgroup_name) + free(sdata->cgroup_name); + if (sdata->setting) + free(sdata->setting); + if (sdata->value) + free(sdata->value); + + return ret; +} + /* * Method for a cause to share data with effect(s) in the same rule. * @@ -223,6 +277,16 @@ API void free_shared_data(struct adaptived_cause * const cse, bool force_delete) adaptived_free_cgroup_value(name_value->value); free(cur->data); break; + case ADAPTIVED_SDATA_CGROUP_SETTING_VALUE: + struct adaptived_cgroup_setting_and_value *cgsv; + + cgsv = (struct adaptived_cgroup_setting_and_value *)cur->data; + + free(cgsv->cgroup_name); + free(cgsv->setting); + adaptived_free_cgroup_value(cgsv->value); + free(cur->data); + break; default: free(cur->data); break; diff --git a/adaptived/src/utils/cgroup_utils.c b/adaptived/src/utils/cgroup_utils.c index b6583dc..38f1ff6 100644 --- a/adaptived/src/utils/cgroup_utils.c +++ b/adaptived/src/utils/cgroup_utils.c @@ -456,14 +456,23 @@ API int adaptived_cgroup_get_value(const char * const setting, adaptived_dbg("setting from %s is not a long long.\n", setting); } + ret = adaptived_cgroup_get_float(setting, &value->value.float_value); + if (ret == 0) { + value->type = ADAPTIVED_CGVAL_FLOAT; + return ret; + } else { + adaptived_dbg("setting from %s is not a float: %d\n", setting, ret); + } + ret = adaptived_cgroup_get_str(setting, &value->value.str_value); if (ret == 0) { value->type = ADAPTIVED_CGVAL_STR; - adaptived_dbg("setting from %s is a string: %s\n", setting, value->value.str_value); return ret; } else { adaptived_dbg("setting from %s is not a string: %d\n", setting, ret); } + + adaptived_err("Failed to detect setting type for %s\n", setting); break; default: adaptived_err("Invalid cgroup value type: %d\n", value->type); diff --git a/adaptived/tests/ftests/.gitignore b/adaptived/tests/ftests/.gitignore index 1afedd6..c13319f 100644 --- a/adaptived/tests/ftests/.gitignore +++ b/adaptived/tests/ftests/.gitignore @@ -1,5 +1,6 @@ __pycache__/ *.log +*.out *.pressure *.trs test[0-9]* diff --git a/adaptived/tests/ftests/071-cause-cgroup_data.c b/adaptived/tests/ftests/071-cause-cgroup_data.c new file mode 100644 index 0000000..efa9637 --- /dev/null +++ b/adaptived/tests/ftests/071-cause-cgroup_data.c @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024-2025, Oracle and/or its affiliates. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/** + * Test to exercise the cgroup_data cause + * + * Note that this test creates a fake cgroup hierarchy directly in the + * tests/ftests directory and operates on it. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ftests.h" + +#define EXPECTED_RET -ETIME + +static const char * const cgroup_dirs[] = { + "./test071cgroup", + "./test071cgroup/child1", + "./test071cgroup/child1/grandchild11/", + "./test071cgroup/child2", + "./test071cgroup/child3" +}; +static const int cgroup_dirs_cnt = ARRAY_SIZE(cgroup_dirs); + +static const char * const cgroup_files[] = { + "./test071cgroup/memory.current", + "./test071cgroup/cpu.uclamp.min", + "./test071cgroup/cpuset.cpus.effective", + "./test071cgroup/cpu.max", + + "./test071cgroup/child1/memory.current", + "./test071cgroup/child1/cpu.uclamp.min", + "./test071cgroup/child1/cpuset.cpus.effective", + "./test071cgroup/child1/cpu.max", + + "./test071cgroup/child1/grandchild11/memory.current", + "./test071cgroup/child1/grandchild11/cpu.uclamp.min", + "./test071cgroup/child1/grandchild11/cpuset.cpus.effective", + "./test071cgroup/child1/grandchild11/cpu.max", + + "./test071cgroup/child2/memory.current", + "./test071cgroup/child2/cpu.uclamp.min", + "./test071cgroup/child2/cpuset.cpus.effective", + "./test071cgroup/child2/cpu.max", + + "./test071cgroup/child3/memory.current", + "./test071cgroup/child3/cpu.uclamp.min", + "./test071cgroup/child3/cpuset.cpus.effective", + "./test071cgroup/child3/cpu.max", +}; +static const int cgroup_files_cnt = ARRAY_SIZE(cgroup_files); + +/* + * Write the file contents as strings since that's how adaptived will read out + * the data. adaptived will then try to determine the correct data type. + */ +static const char * const file_contents[] = { + "4096000", + "1.234", + "1-2,4-7,10,12", + "max 100000", + + "8192000", + "3.456", + "1-20", + "10000 100000", + + "1024000", + "0.000", + "1,3,5,7,9,11", + "250000 1000000", + + "0", + "9.876", + "5-8", + "max 1000000", + + "12345678", + "4.680", + "8,10", + "500000 1000000", +}; +static_assert(ARRAY_SIZE(file_contents) == ARRAY_SIZE(cgroup_files), + "file_contents should be the same size as cgroup_files_cnt"); + + +static void write_cgroup_files(void) +{ + int i; + + for (i = 0; i < cgroup_files_cnt; i++) + write_file(cgroup_files[i], file_contents[i]); +} + +int main(int argc, char *argv[]) +{ + char config_path[FILENAME_MAX], expected_file[FILENAME_MAX]; + struct adaptived_ctx *ctx = NULL; + int ret; + + snprintf(config_path, FILENAME_MAX - 1, "%s/071-cause-cgroup_data.json", + argv[1]); + config_path[FILENAME_MAX - 1] = '\0'; + + ctx = adaptived_init(config_path); + if (!ctx) + return AUTOMAKE_HARD_ERROR; + + ret = create_dirs(cgroup_dirs, cgroup_dirs_cnt); + if (ret) + goto err; + + write_cgroup_files(); + + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_MAX_LOOPS, 1); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_INTERVAL, 1234); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_SKIP_SLEEP, 1); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_LOG_LEVEL, LOG_EMERG); + if (ret) + goto err; + + ret = adaptived_loop(ctx, true); + if (ret != EXPECTED_RET) { + adaptived_err("Test 071 returned: %d, expected: %d\n", ret, EXPECTED_RET); + goto err; + } + + adaptived_release(&ctx); + ctx = NULL; + + snprintf(expected_file, FILENAME_MAX - 1, "%s/071-cause-cgroup_data.expected", argv[1]); + expected_file[FILENAME_MAX - 1] = '\0'; + + ret = compare_files_unsorted("071-cause-cgroup_data.out", expected_file); + if (ret) + goto err; + + delete_file("071-cause-cgroup_data.out"); + delete_files(cgroup_files, cgroup_files_cnt); + delete_dirs(cgroup_dirs, cgroup_dirs_cnt); + + return AUTOMAKE_PASSED; + +err: + delete_file("071-cause-cgroup_data.out"); + delete_files(cgroup_files, cgroup_files_cnt); + delete_dirs(cgroup_dirs, cgroup_dirs_cnt); + if (ctx) + adaptived_release(&ctx); + + return AUTOMAKE_HARD_ERROR; +} diff --git a/adaptived/tests/ftests/071-cause-cgroup_data.expected b/adaptived/tests/ftests/071-cause-cgroup_data.expected new file mode 100644 index 0000000..a754e2c --- /dev/null +++ b/adaptived/tests/ftests/071-cause-cgroup_data.expected @@ -0,0 +1,38 @@ +Print effect triggered by: + cgroup_data +Cause "cgroup_data" shared + cgroup "child1" + setting "memory.current" + long long value "8192000" +Cause "cgroup_data" shared + cgroup "child1" + setting "cpu.uclamp.min" + float value "3.456000" +Cause "cgroup_data" shared + cgroup "child1" + setting "cpuset.cpus.effective" + string value "1-20" +Cause "cgroup_data" shared + cgroup "child2" + setting "memory.current" + long long value "0" +Cause "cgroup_data" shared + cgroup "child2" + setting "cpu.uclamp.min" + float value "9.876000" +Cause "cgroup_data" shared + cgroup "child2" + setting "cpuset.cpus.effective" + string value "5-8" +Cause "cgroup_data" shared + cgroup "child3" + setting "memory.current" + long long value "12345678" +Cause "cgroup_data" shared + cgroup "child3" + setting "cpu.uclamp.min" + float value "4.680000" +Cause "cgroup_data" shared + cgroup "child3" + setting "cpuset.cpus.effective" + string value "8,10" diff --git a/adaptived/tests/ftests/071-cause-cgroup_data.json b/adaptived/tests/ftests/071-cause-cgroup_data.json new file mode 100644 index 0000000..61f8600 --- /dev/null +++ b/adaptived/tests/ftests/071-cause-cgroup_data.json @@ -0,0 +1,35 @@ +{ + "rules": [ + { + "name": "Gather cgroup data and print it out", + "causes": [ + { + "name": "cgroup_data", + "args": { + "cgroup": "./test071cgroup///", + "settings": [ + { + "setting": "memory.current" + }, + { + "setting": "cpu.uclamp.min" + }, + { + "setting": "cpuset.cpus.effective" + } + ] + } + } + ], + "effects": [ + { + "name": "print", + "args": { + "file": "071-cause-cgroup_data.out", + "shared_data": true + } + } + ] + } + ] +} diff --git a/adaptived/tests/ftests/072-cause-cgroup_data2.c b/adaptived/tests/ftests/072-cause-cgroup_data2.c new file mode 100644 index 0000000..46da095 --- /dev/null +++ b/adaptived/tests/ftests/072-cause-cgroup_data2.c @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2024-2025, Oracle and/or its affiliates. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/** + * Test to exercise the cgroup_data cause + * + * Note that this test creates a fake cgroup hierarchy directly in the + * tests/ftests directory and operates on it. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ftests.h" + +#define EXPECTED_RET -ETIME + +static const char * const cgroup_dirs[] = { + "./test072cgroup", + "./test072cgroup/child1", + "./test072cgroup/child1/grandchild11/", + "./test072cgroup/child2", + "./test072cgroup/child3" +}; +static const int cgroup_dirs_cnt = ARRAY_SIZE(cgroup_dirs); + +static const char * const cgroup_files[] = { + "./test072cgroup/memory.current", + "./test072cgroup/cpu.uclamp.min", + "./test072cgroup/cpuset.cpus.effective", + "./test072cgroup/cpu.max", + + "./test072cgroup/child1/memory.current", + "./test072cgroup/child1/cpu.uclamp.min", + /* Intentionally omit cpuset.cpus.effective from the child1 cgroup */ + "./test072cgroup/child1/cpu.max", + + "./test072cgroup/child1/grandchild11/memory.current", + "./test072cgroup/child1/grandchild11/cpu.uclamp.min", + "./test072cgroup/child1/grandchild11/cpuset.cpus.effective", + /* Intentionally omit cpu.max from grandchild11 */ + + "./test072cgroup/child2/memory.current", + "./test072cgroup/child2/cpu.uclamp.min", + "./test072cgroup/child2/cpuset.cpus.effective", + "./test072cgroup/child2/cpu.max", + + /* Intentionally omit memory.current from child3 */ + "./test072cgroup/child3/cpu.uclamp.min", + "./test072cgroup/child3/cpuset.cpus.effective", + "./test072cgroup/child3/cpu.max", +}; +static const int cgroup_files_cnt = ARRAY_SIZE(cgroup_files); + +/* + * Write the file contents as strings since that's how adaptived will read out + * the data. adaptived will then try to determine the correct data type. + */ +static const char * const file_contents[] = { + "4096000", + "1.234", + "1-2,4-7,10,12", + "max 100000", + + "8192000", + "3.456", + /* file omitted */ + "10000 100000", + + "1024000", + "0.000", + "1,3,5,7,9,11", + /* file omitted */ + + "0", + "9.876", + "5-8", + "max 1000000", + + /* file omitted */ + "4.680", + "8,10", + "500000 1000000", +}; +static_assert(ARRAY_SIZE(file_contents) == ARRAY_SIZE(cgroup_files), + "file_contents should be the same size as cgroup_files_cnt"); + +const char * const config_token_file = "072-cause-cgroup_data2.json.token"; +const char * const config_file = "072-cause-cgroup_data2.json"; +const char * const expected_token_file = "072-cause-cgroup_data2.expected.token"; +const char * const expected_file = "072-cause-cgroup_data2.expected"; +const char * const out_file = "072-cause-cgroup_data2.out"; + +static void write_cgroup_files(void) +{ + int i; + + for (i = 0; i < cgroup_files_cnt; i++) + write_file(cgroup_files[i], file_contents[i]); +} + +int main(int argc, char *argv[]) +{ + char *ftests_path, *config_token_path = NULL, *config_path = NULL, *out_path = NULL, + *expected_token_path = NULL, *expected_path = NULL; + struct adaptived_ctx *ctx = NULL; + ssize_t ftests_path_len, cwd_len; + char cwd[FILENAME_MAX]; + int ret; + + ftests_path = argv[1]; + ftests_path_len = strlen(ftests_path); + + getcwd(cwd, sizeof(cwd)); + cwd_len = strlen(cwd); + + config_token_path = calloc(sizeof(char), + (ftests_path_len + strlen(config_token_file) + 2)); + if (!config_token_path) + goto err; + sprintf(config_token_path, "%s/%s", ftests_path, config_token_file); + + config_path = calloc(sizeof(char), + (cwd_len + strlen(config_file) + 2)); + if (!config_path) + goto err; + sprintf(config_path, "%s/%s", cwd, config_file); + + out_path = calloc(sizeof(char), (cwd_len + strlen(out_file) + 2)); + if (!out_path) + goto err; + sprintf(out_path, "%s/%s", cwd, out_file); + + expected_token_path = calloc(sizeof(char), + (ftests_path_len + strlen(expected_token_file) + 2)); + if (!expected_token_path) + goto err; + sprintf(expected_token_path, "%s/%s", ftests_path, expected_token_file); + + expected_path = calloc(sizeof(char), + (cwd_len + strlen(expected_file) + 2)); + if (!expected_path) + goto err; + sprintf(expected_path, "%s/%s", cwd, expected_file); + + ret = parse_token_file(config_token_path, config_path); + if (ret) + goto err; + + ret = parse_token_file(expected_token_path, expected_path); + if (ret) + goto err; + + ctx = adaptived_init(config_path); + if (!ctx) + return AUTOMAKE_HARD_ERROR; + + ret = create_dirs(cgroup_dirs, cgroup_dirs_cnt); + if (ret) + goto err; + + write_cgroup_files(); + + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_MAX_LOOPS, 1); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_SKIP_SLEEP, 1); + if (ret) + goto err; + ret = adaptived_set_attr(ctx, ADAPTIVED_ATTR_LOG_LEVEL, LOG_EMERG); + if (ret) + goto err; + + ret = adaptived_loop(ctx, true); + if (ret != EXPECTED_RET) { + adaptived_err("Test 072 returned: %d, expected: %d\n", ret, EXPECTED_RET); + goto err; + } + + adaptived_release(&ctx); + ctx = NULL; + + ret = compare_files_unsorted(out_path, expected_path); + if (ret) + goto err; + + if (config_token_path) + free(config_token_path); + + if (expected_token_path) + free(expected_token_path); + + if (out_path) { + delete_file(out_path); + free(out_path); + } + + if (config_path) { + delete_file(config_path); + free(config_path); + } + + if (expected_path) { + delete_file(expected_path); + free(expected_path); + } + + delete_files(cgroup_files, cgroup_files_cnt); + delete_dirs(cgroup_dirs, cgroup_dirs_cnt); + + if (ctx) + adaptived_release(&ctx); + + return AUTOMAKE_PASSED; + +err: + if (config_token_path) + free(config_token_path); + + if (expected_token_path) + free(expected_token_path); + + if (out_path) { + delete_file(out_path); + free(out_path); + } + + if (config_path) { + delete_file(config_path); + free(config_path); + } + + if (expected_path) { + delete_file(expected_path); + free(expected_path); + } + + delete_files(cgroup_files, cgroup_files_cnt); + delete_dirs(cgroup_dirs, cgroup_dirs_cnt); + + if (ctx) + adaptived_release(&ctx); + + return AUTOMAKE_HARD_ERROR; +} diff --git a/adaptived/tests/ftests/072-cause-cgroup_data2.expected.token b/adaptived/tests/ftests/072-cause-cgroup_data2.expected.token new file mode 100644 index 0000000..81b7d81 --- /dev/null +++ b/adaptived/tests/ftests/072-cause-cgroup_data2.expected.token @@ -0,0 +1,42 @@ +Print effect triggered by: + cgroup_data +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1" + setting "memory.current" + long long value "8192000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1" + setting "cpu.uclamp.min" + float value "3.456000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1/grandchild11" + setting "memory.current" + long long value "1024000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1/grandchild11" + setting "cpu.uclamp.min" + float value "0.000000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child1/grandchild11" + setting "cpuset.cpus.effective" + string value "1,3,5,7,9,11" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child2" + setting "memory.current" + long long value "0" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child2" + setting "cpu.uclamp.min" + float value "9.876000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child2" + setting "cpuset.cpus.effective" + string value "5-8" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child3" + setting "cpu.uclamp.min" + float value "4.680000" +Cause "cgroup_data" shared + cgroup "<< pwd >>/test072cgroup/child3" + setting "cpuset.cpus.effective" + string value "8,10" diff --git a/adaptived/tests/ftests/072-cause-cgroup_data2.json.token b/adaptived/tests/ftests/072-cause-cgroup_data2.json.token new file mode 100644 index 0000000..ae343f6 --- /dev/null +++ b/adaptived/tests/ftests/072-cause-cgroup_data2.json.token @@ -0,0 +1,37 @@ +{ + "rules": [ + { + "name": "Gather cgroup data and print it out", + "causes": [ + { + "name": "cgroup_data", + "args": { + "cgroup": "<< pwd >>/test072cgroup", + "rel_paths": false, + "max_depth": 10, + "settings": [ + { + "setting": "memory.current" + }, + { + "setting": "cpu.uclamp.min" + }, + { + "setting": "cpuset.cpus.effective" + } + ] + } + } + ], + "effects": [ + { + "name": "print", + "args": { + "file": "072-cause-cgroup_data2.out", + "shared_data": true + } + } + ] + } + ] +} diff --git a/adaptived/tests/ftests/Makefile.am b/adaptived/tests/ftests/Makefile.am index 075a259..8516ba5 100644 --- a/adaptived/tests/ftests/Makefile.am +++ b/adaptived/tests/ftests/Makefile.am @@ -110,6 +110,8 @@ test067_SOURCES = 067-effect-kill_processes.c ftests.c test068_SOURCES = 068-effect-kill_processes_rss.c ftests.c test069_SOURCES = 069-effect-signal.c ftests.c test070_SOURCES = 070-rule-multiple_rules.c ftests.c +test071_SOURCES = 071-cause-cgroup_data.c ftests.c +test072_SOURCES = 072-cause-cgroup_data2.c ftests.c sudo1000_SOURCES = 1000-sudo-effect-sd_bus_setting_set_int.c ftests.c sudo1001_SOURCES = 1001-sudo-effect-sd_bus_setting_add_int.c ftests.c @@ -187,6 +189,8 @@ check_PROGRAMS = \ test068 \ test069 \ test070 \ + test071 \ + test072 \ sudo1000 \ sudo1001 \ sudo1002 \ @@ -282,7 +286,11 @@ EXTRA_DIST_CFGS = \ 067-effect-kill_processes.json \ 068-effect-kill_processes_rss.json \ 069-effect-signal.json \ - 070-rule-multiple_rules.json + 070-rule-multiple_rules.json \ + 071-cause-cgroup_data.json \ + 071-cause-cgroup_data.expected \ + 072-cause-cgroup_data2.json.token \ + 072-cause-cgroup_data2.expected.token EXTRA_DIST_H_FILES = \ ftests.h diff --git a/adaptived/tests/ftests/ftests.c b/adaptived/tests/ftests/ftests.c index c9ee428..77efd90 100644 --- a/adaptived/tests/ftests/ftests.c +++ b/adaptived/tests/ftests/ftests.c @@ -128,6 +128,95 @@ int compare_files(const char * const file1, const char * const file2) return ret; } +#define MAX_LINES 4096 +int compare_files_unsorted(const char * const file1, const char * const file2) +{ + char *f1_line = NULL, *f2_line = NULL; + int f1_line_cnt = 0, f2_line_cnt = 0; + bool used[MAX_LINES] = { false }; + FILE *f1 = NULL, *f2 = NULL; + size_t f1_sz, f2_sz; + bool found_line; + ssize_t read; + int ret, i; + + f1 = fopen(file1, "r"); + if (!f1) { + ret = -errno; + goto err; + } + + f2 = fopen(file2, "r"); + if (!f2) { + ret = -errno; + goto err; + } + + + while ((read = getline(&f1_line, &f1_sz, f1)) != -1) { + f1_line_cnt++; + rewind(f2); + i = 0; + + while ((read = getline(&f2_line, &f2_sz, f2)) != -1) { + if (i < MAX_LINES && used[i] == true) { + i++; + continue; + } + + if (f1_sz != f2_sz) { + i++; + continue; + } + + if (strncmp(f1_line, f2_line, f1_sz) == 0) { + found_line = true; + if (i < MAX_LINES) + used[i] = true; + break; + } + + i++; + } + + if (!found_line) { + ret = -ENOSTR; + goto err; + } + } + + for (i = 0; i < MAX_LINES && i < f1_line_cnt; i++) { + if (!used[i]) { + adaptived_err("Line %d in %s was unused\n", i, file2); + ret = -ENODATA; + goto err; + } + } + + rewind(f2); + while ((read = getline(&f2_line, &f2_sz, f2)) != -1) + f2_line_cnt++; + + if (f1_line_cnt != f2_line_cnt) { + ret = -ENODATA; + goto err; + } + + ret = 0; + +err: + if (f1_line) + free(f1_line); + if (f2_line) + free(f2_line); + if (f1) + fclose(f1); + if (f2) + fclose(f2); + + return ret; +} + int verify_int_file(const char * const filename, int expected_value) { char buf[1024] = { '\0' }; @@ -668,3 +757,127 @@ int start_unit(const char *unit_name, const char *cmd_to_run) return ret; } + +static int replace_token(FILE * const out_file, const char * const line, ssize_t line_len) +{ + char *start, *end, *token_str, *op = NULL; + ssize_t op_len, start_len, end_len; + char cwd[FILENAME_MAX]; + int ret = 0; + + start = strstr(line, "<< "); + end = strstr(line, " >>"); + + /* + * Subtract 2 for the <<, subtract 1 for the first whitespace padding, Add 1 for the + * NULL terminator + */ + op_len = end - start - 2 - 1 + 1; + + op = malloc(op_len * sizeof(char)); + if (!op) + return -ENOMEM; + + snprintf(op, op_len, "%s", &start[3]); + op[op_len - 1] = '\0'; + + if (strcmp(op, "pwd") == 0) { + getcwd(cwd, sizeof(cwd)); + token_str = cwd; + } else { + /* unsupported operation */ + ret = -EINVAL; + goto err; + } + + start_len = start - line; + + if (start_len) + (void)fwrite(line, sizeof(char), start_len, out_file); + + (void)fwrite(token_str, sizeof(char), strlen(token_str), out_file); + + /* + * We can't do strlen(line) because it may contain extra characters + * from a previous getline(). + * + * The first "-3" is the length of the start token, and the second + * "-3" is the length of the end token + */ + end_len = line_len - start_len - op_len - 3 - 3; + + if (end_len) + (void)fwrite(&end[3], sizeof(char), end_len, out_file); + + (void)fwrite("\n", sizeof(char), strlen("\n"), out_file); + +err: + if (op) + free(op); + + return ret; +} + +/* + * Parse the specified token file, e.g. test001.json.token, and output the + * parsed contents to the filename minus the ".token" suffix, e.g. + * test001.json + * + * It is the responsibility of the caller to delete the output file + */ +int parse_token_file(const char * const token_file, const char * const out_file) +{ + FILE *in = NULL, *out = NULL; + ssize_t chars_rd, chars_wr; + char *line = NULL; + size_t line_sz; + int ret; + + if (!token_file) + return -EINVAL; + + if (!strstr(token_file, ".token")) + return -EINVAL; + + in = fopen(token_file, "r"); + if (!in) { + adaptived_err("Failed to open: %s: \n", token_file, errno); + ret = -errno; + goto err; + } + + out = fopen(out_file, "w"); + if (!out) { + adaptived_err("Failed to open: %s: %d\n", out_file, errno); + ret = -errno; + goto err; + } + + while ((chars_rd = getline(&line, &line_sz, in)) != -1) { + if (strstr(line, "<<") && strstr(line, ">>")) { + ret = replace_token(out, line, chars_rd); + if (ret) + goto err; + } else { + chars_wr = fwrite(line, sizeof(char), chars_rd, out); + if (chars_wr != chars_rd) { + adaptived_err("Failed to write \"%s\" to %s\n", line, out_file); + ret = -EIO; + goto err; + } + } + } + + ret = 0; + +err: + if (in) + fclose(in); + if (out) + fclose(out); + + if (line) + free(line); + + return ret; +} diff --git a/adaptived/tests/ftests/ftests.h b/adaptived/tests/ftests/ftests.h index 387518f..22fa99a 100644 --- a/adaptived/tests/ftests/ftests.h +++ b/adaptived/tests/ftests/ftests.h @@ -58,6 +58,7 @@ void delete_file(const char * const filename); void delete_files(const char * const files[], int files_cnt); void write_file(const char * const filename, const char * const contents); int compare_files(const char * const file1, const char * const file2); +int compare_files_unsorted(const char * const file1, const char * const file2); int verify_int_file(const char * const filename, int expected_value); int verify_ll_file(const char * const filename, long long expected_value); int verify_char_file(const char * const filename, const char * const expected_contents); @@ -84,5 +85,6 @@ int get_cgroup_version(int * const version); int build_cgroup_path(const char * const controller, const char * const cgrp, char ** path); int build_systemd_cgroup_path(const char * const cgrp, char ** path); int build_systemd_memory_max_file(const char * const cgrp_path, char **file_path); +int parse_token_file(const char * const token_file, const char * const out_file); #endif /* __ADAPTIVED_FTESTS_H */