| /* |
| * teamd_config.c - Teamd configuration frontend |
| * Copyright (C) 2013-2015 Jiri Pirko <jiri@resnulli.us> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <string.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <jansson.h> |
| |
| #include "teamd.h" |
| #include "teamd_config.h" |
| #include "teamd_json.h" |
| |
| #define TEAMD_IMPLICIT_CONFIG "{}" |
| |
| int teamd_config_load(struct teamd_context *ctx) |
| { |
| json_error_t jerror; |
| size_t jflags = JSON_REJECT_DUPLICATES; |
| |
| if (!ctx->config_text && !ctx->config_file) { |
| ctx->config_text = strdup(TEAMD_IMPLICIT_CONFIG); |
| if (!ctx->config_text) |
| return -ENOMEM; |
| } |
| if (ctx->config_text) { |
| if (ctx->config_file) |
| teamd_log_warn("Command line config string is present, ignoring given config file."); |
| ctx->config_json = json_loads(ctx->config_text, jflags, |
| &jerror); |
| } else if (ctx->config_file) { |
| ctx->config_json = json_load_file(ctx->config_file, jflags, |
| &jerror); |
| } |
| if (!ctx->config_json) { |
| teamd_log_err("Failed to parse config: %s on line %d, column %d", |
| jerror.text, jerror.line, jerror.column); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| void teamd_config_free(struct teamd_context *ctx) |
| { |
| json_decref(ctx->config_json); |
| } |
| |
| int teamd_config_dump(struct teamd_context *ctx, char **p_config_dump) |
| { |
| char *dump; |
| |
| dump = json_dumps(ctx->config_json, TEAMD_JSON_DUMPS_FLAGS); |
| if (!dump) |
| return -ENOMEM; |
| *p_config_dump = dump; |
| return 0; |
| } |
| |
| static int get_port_obj(json_t **pport_obj, json_t *config_json, |
| const char *port_name) |
| { |
| int err; |
| json_t *ports_obj; |
| json_t *port_obj; |
| |
| err = json_unpack(config_json, "{s:o}", "ports", &ports_obj); |
| if (err) { |
| ports_obj = json_object(); |
| if (!ports_obj) |
| return -ENOMEM; |
| err = json_object_set_new(config_json, "ports", ports_obj); |
| if (err) { |
| json_decref(ports_obj); |
| return -ENOMEM; |
| } |
| } |
| err = json_unpack(ports_obj, "{s:o}", port_name, &port_obj); |
| if (err) { |
| port_obj = json_object(); |
| if (!port_obj) |
| return -ENOMEM; |
| err = json_object_set_new(ports_obj, port_name, port_obj); |
| if (err) { |
| json_decref(port_obj); |
| return -ENOMEM; |
| } |
| } |
| if (pport_obj) |
| *pport_obj = port_obj; |
| return 0; |
| } |
| |
| int teamd_config_actual_dump(struct teamd_context *ctx, char **p_config_dump) |
| { |
| json_t *actual_json; |
| struct teamd_port *tdport; |
| json_t *ports_obj; |
| void *iter; |
| char *dump; |
| int err; |
| |
| actual_json = json_deep_copy(ctx->config_json); |
| if (!actual_json) |
| return -ENOMEM; |
| |
| /* |
| * Create json objects for all present ports |
| */ |
| teamd_for_each_tdport(tdport, ctx) { |
| err = get_port_obj(NULL, actual_json, tdport->ifname); |
| if (err) |
| goto errout; |
| } |
| |
| /* |
| * Get rid of json object of ports which are not present |
| */ |
| err = json_unpack(actual_json, "{s:o}", "ports", &ports_obj); |
| if (!err) { |
| iter = json_object_iter(ports_obj); |
| while (iter) { |
| const char *port_name = json_object_iter_key(iter); |
| |
| iter = json_object_iter_next(ports_obj, iter); |
| if (!teamd_get_port_by_ifname(ctx, port_name)) |
| json_object_del(ports_obj, port_name); |
| } |
| } |
| |
| dump = json_dumps(actual_json, TEAMD_JSON_DUMPS_FLAGS); |
| json_decref(actual_json); |
| if (!dump) |
| return -ENOMEM; |
| *p_config_dump = dump; |
| return 0; |
| |
| errout: |
| json_decref(actual_json); |
| return err; |
| } |
| |
| static int teamd_config_port_set(struct teamd_context *ctx, const char *port_name, |
| json_t *port_obj) |
| { |
| struct teamd_port *tdport; |
| json_t *config; |
| int tmp, err; |
| |
| tdport = teamd_get_port_by_ifname(ctx, port_name); |
| if (!tdport) |
| return 0; |
| |
| config = json_object_get(port_obj, "prio"); |
| if (!json_is_integer(config)) { |
| teamd_log_err("%s: Failed to get integer for \"priority\".", |
| tdport->ifname); |
| return -ENOENT; |
| } |
| |
| tmp = json_integer_value(config); |
| err = team_set_port_priority(ctx->th, tdport->ifindex, tmp); |
| if (err) |
| teamd_log_err("%s: Failed to update \"priority\" to kernel", |
| tdport->ifname); |
| |
| return err; |
| } |
| |
| int teamd_config_port_update(struct teamd_context *ctx, const char *port_name, |
| const char *json_port_cfg_str) |
| { |
| int err; |
| json_t *port_obj; |
| json_t *port_new_obj; |
| json_error_t jerror; |
| |
| port_new_obj = json_loads(json_port_cfg_str, JSON_REJECT_DUPLICATES, |
| &jerror); |
| if (!port_new_obj) { |
| teamd_log_err("%s: Failed to parse port config string: " |
| "%s on line %d, column %d", port_name, |
| jerror.text, jerror.line, jerror.column); |
| return -EIO; |
| } |
| err = get_port_obj(&port_obj, ctx->config_json, port_name); |
| if (err) { |
| teamd_log_err("%s: Failed to obtain port config object", |
| port_name); |
| goto new_port_decref; |
| } |
| |
| /* replace existing object content */ |
| json_object_clear(port_obj); |
| err = json_object_update(port_obj, port_new_obj); |
| if (err) { |
| teamd_log_err("%s: Failed to update existing config " |
| "port object", port_name); |
| goto new_port_decref; |
| } |
| |
| err = teamd_config_port_set(ctx, port_name, port_new_obj); |
| |
| new_port_decref: |
| json_decref(port_new_obj); |
| return err; |
| } |
| |
| int teamd_config_port_dump(struct teamd_context *ctx, const char *port_name, |
| char **p_config_port_dump) |
| { |
| json_t *port_json; |
| char *dump; |
| int err; |
| |
| if (!teamd_get_port_by_ifname(ctx, port_name)) |
| return -ENODEV; |
| |
| err = json_unpack(ctx->config_json, "{s:{s:o}}", "ports", port_name, |
| &port_json); |
| if (err) |
| port_json = json_object(); |
| else |
| json_incref(port_json); |
| if (!port_json) |
| return -ENOMEM; |
| |
| dump = json_dumps(port_json, TEAMD_JSON_DUMPS_FLAGS); |
| |
| json_decref(port_json); |
| if (!dump) |
| return -ENOMEM; |
| *p_config_port_dump = dump; |
| return 0; |
| } |
| static int teamd_config_object_get(struct teamd_context *ctx, |
| json_t **p_json_obj, |
| const char *fmt, va_list ap) |
| { |
| int err; |
| |
| err = teamd_json_path_lite_va(p_json_obj, ctx->config_json, fmt, ap); |
| if (err) { |
| if (err == -EINVAL) |
| teamd_log_err("Failed to get value from config: Wrong path format"); |
| return err; |
| } |
| return 0; |
| } |
| |
| static int teamd_config_object_build_type_get(struct teamd_context *ctx, |
| json_t **p_json_obj, |
| json_type obj_type, |
| const char *fmt, va_list ap) |
| { |
| int err; |
| |
| err = teamd_json_path_lite_build_type_va(p_json_obj, ctx->config_json, |
| obj_type, fmt, ap); |
| if (err) { |
| if (err == -EINVAL) |
| teamd_log_err("Failed to get value from config: Wrong path format"); |
| return err; |
| } |
| return 0; |
| } |
| |
| struct teamd_config_path_cookie * |
| teamd_config_path_cookie_get(struct teamd_context *ctx, const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_get(ctx, &json_obj, fmt, ap); |
| va_end(ap); |
| if (err) |
| return NULL; |
| return (struct teamd_config_path_cookie *) json_obj; |
| } |
| |
| bool teamd_config_path_exists(struct teamd_context *ctx, const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_get(ctx, &json_obj, fmt, ap); |
| va_end(ap); |
| return err ? false : true; |
| } |
| |
| bool teamd_config_path_is_arr(struct teamd_context *ctx, const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_get(ctx, &json_obj, fmt, ap); |
| va_end(ap); |
| return !err && json_is_array(json_obj) ? true : false; |
| } |
| |
| int teamd_config_string_get(struct teamd_context *ctx, const char **p_str_val, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_get(ctx, &json_obj, fmt, ap); |
| va_end(ap); |
| if (err) |
| return err; |
| |
| if (!json_is_string(json_obj)) { |
| teamd_log_err("Failed to get string from non-string object"); |
| return -ENOENT; |
| } |
| *p_str_val = json_string_value(json_obj); |
| return 0; |
| } |
| |
| int teamd_config_string_set(struct teamd_context *ctx, const char *str_val, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| int ret; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_build_type_get(ctx, &json_obj, |
| JSON_STRING, fmt, ap); |
| va_end(ap); |
| if (err) |
| return err; |
| ret = json_string_set(json_obj, str_val); |
| if (ret == -1) |
| return -ENOMEM; |
| return 0; |
| } |
| |
| int teamd_config_int_get(struct teamd_context *ctx, int *p_int_val, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_get(ctx, &json_obj, fmt, ap); |
| va_end(ap); |
| if (err) |
| return err; |
| |
| if (!json_is_integer(json_obj)) { |
| teamd_log_err("Failed to get integer from non-integer object"); |
| return -ENOENT; |
| } |
| *p_int_val = json_integer_value(json_obj); |
| return 0; |
| } |
| |
| int teamd_config_int_set(struct teamd_context *ctx, int int_val, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_build_type_get(ctx, &json_obj, |
| JSON_INTEGER, fmt, ap); |
| va_end(ap); |
| if (err) |
| return err; |
| json_integer_set(json_obj, int_val); |
| return 0; |
| } |
| |
| int teamd_config_bool_get(struct teamd_context *ctx, bool *p_bool_val, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_get(ctx, &json_obj, fmt, ap); |
| va_end(ap); |
| if (err) |
| return err; |
| |
| if (!json_is_boolean(json_obj)) { |
| teamd_log_err("Failed to get boolean from non-boolean object"); |
| return -ENOENT; |
| } |
| *p_bool_val = json_is_true(json_obj) ? true : false; |
| return 0; |
| } |
| |
| const char *teamd_config_next_key(struct teamd_context *ctx, const char *key, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| void *iter; |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_get(ctx, &json_obj, fmt, ap); |
| va_end(ap); |
| if (err) |
| return NULL; |
| if (key) { |
| iter = json_object_key_to_iter(key); |
| iter = json_object_iter_next(json_obj, iter); |
| } else { |
| iter = json_object_iter(json_obj); |
| } |
| return json_object_iter_key(iter); |
| } |
| |
| size_t teamd_config_arr_size(struct teamd_context *ctx, const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_obj = NULL; /* gcc needs this initialized */ |
| int err; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_get(ctx, &json_obj, fmt, ap); |
| va_end(ap); |
| if (err) |
| return 0; |
| return json_array_size(json_obj); |
| } |
| |
| size_t teamd_config_arr_string_append(struct teamd_context *ctx, |
| const char *str_val, |
| const char *fmt, ...) |
| { |
| va_list ap; |
| json_t *json_arr = NULL; /* gcc needs this initialized */ |
| json_t *json_str; |
| int err; |
| int ret; |
| |
| va_start(ap, fmt); |
| err = teamd_config_object_build_type_get(ctx, &json_arr, |
| JSON_ARRAY, fmt, ap); |
| va_end(ap); |
| if (err) |
| return err; |
| json_str = json_string(str_val); |
| if (!json_str) |
| return -ENOMEM; |
| ret = json_array_append_new(json_arr, json_str); |
| if (ret == -1) |
| return -ENOMEM; |
| return 0; |
| } |