/****************************************************************************** * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. * iSulad licensed under the Mulan PSL v1. * You can use this software according to the terms and conditions of the Mulan PSL v1. * You may obtain a copy of Mulan PSL v1 at: * http://license.coscl.org.cn/MulanPSL * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR * PURPOSE. * See the Mulan PSL v1 for more details. * Author: tanyifeng * Create: 2017-11-22 * Description: provide command functions ******************************************************************************/ #define _GNU_SOURCE #include "commander.h" #include #include #include #include #include #include "libisulad.h" #include "utils.h" #include "log.h" void command_help_isulad_head() { fprintf(stdout, "isulad\n\nlightweight container runtime daemon\n"); } int compare_options(const void *s1, const void *s2) { return strcmp((*(const command_option_t *)s1).large, (*(const command_option_t *)s2).large); } void print_options(int options_len, const command_option_t *options) { int i = 0; int max_opt_len = 0; for (i = 0; i < options_len; i++) { command_option_t option = options[i]; // -s int len = 2; // -s, --large, 6 is the length of "-s, --". if (option.large != NULL) { len = (int)(strlen(option.large) + 6); } if (len > max_opt_len) { max_opt_len = len; } } // format: " -s, --large description" // 6 is the total length of black before "-s" and "description" max_opt_len += 6; for (i = 0; i < options_len; i++) { command_option_t option = options[i]; int curindex = 0; int space_left = 0; curindex = fprintf(stdout, " "); if (option.small) { curindex += fprintf(stdout, "-%c", (char)option.small); } if (option.large != NULL) { if (option.small) { curindex += fprintf(stdout, ", --%s", option.large); } else { curindex += fprintf(stdout, " --%s", option.large); } } if (curindex <= max_opt_len) { space_left = max_opt_len - curindex; } fprintf(stdout, "%*s%s\n", space_left, "", option.description); } fputc('\n', stdout); } void command_help(command_t *self) { const char *progname = strrchr(self->name, '/'); if (progname == NULL) { progname = self->name; } else { progname++; } if (self->type != NULL && strcmp(self->type, "isulad") == 0) { command_help_isulad_head(); } fprintf(stdout, "\nUsage: %s %s\n\n", progname, self->usage); fprintf(stdout, "%s\n\n", self->description); qsort(self->options, (size_t)self->option_count, sizeof(self->options[0]), compare_options); print_options(self->option_count, self->options); } int command_valid_socket(command_option_t *option, const char *arg) { if (!util_validate_socket(arg)) { COMMAND_ERROR("Invalid socket name : %s", arg); return -1; } return 0; } void command_init(command_t *self, command_option_t *opts, int opts_len, int argc, const char **argv, const char *description, const char *usage) { (void)memset(self, 0, sizeof(command_t)); self->name = argv[0]; self->argc = argc - 2; self->argv = argv + 2; self->usage = usage; self->description = description; self->options = opts; self->option_count = opts_len; } void command_option(command_t *self, command_option_type_t type, void *data, int small, const char *large, const char *desc, command_callback_t cb) { if (self->option_count == COMMANDER_MAX_OPTIONS) { COMMAND_ERROR("Maximum option definitions exceeded"); exit(EINVALIDARGS); } int n = self->option_count++; command_option_t *opt = &self->options[n]; opt->type = type; opt->data = data; opt->cb = cb; opt->small = small; opt->description = desc; opt->large = large; } static int read_option_arg(command_t *self, command_option_t *opt, const char **opt_arg, const char **readed) { if (self == NULL || opt == NULL || opt_arg == NULL) { return -1; } if (opt->hasdata) { *readed = *opt_arg; *opt_arg = NULL; } if (!opt->hasdata && self->argc > 1) { opt->hasdata = true; *readed = *++self->argv; self->argc--; } if (!opt->hasdata) { COMMAND_ERROR("Flag needs an argument: --%s", opt->large); return -1; } return 0; } static int command_get_bool_option_data(command_option_t *option, const char **opt_arg) { bool converted_bool = (option->type == CMD_OPT_TYPE_BOOL) ? true : false; if (option->hasdata) { int ret = util_str_to_bool(*opt_arg, &converted_bool); if (ret != 0) { COMMAND_ERROR("Invalid boolean value \"%s\" for flag --%s", *opt_arg, option->large); return -1; } *opt_arg = NULL; } *(bool *)option->data = converted_bool; return 0; } static int command_get_string_option_data(command_t *self, command_option_t *option, const char **opt_arg) { if (read_option_arg(self, option, opt_arg, (const char **)option->data)) { return -1; } if (option->cb != NULL) { return option->cb(option, *(char **)option->data); } return 0; } static int command_get_string_dup_option_data(command_t *self, command_option_t *option, const char **opt_arg) { const char *readed_item = NULL; if (read_option_arg(self, option, opt_arg, &readed_item) != 0) { return -1; } if (*(char **)option->data != NULL) { free(*(char **)option->data); } *(char **)option->data = util_strdup_s(readed_item); if (option->cb != NULL) { return option->cb(option, readed_item); } return 0; } static int command_get_callback_option_data(command_t *self, command_option_t *option, const char **opt_arg) { const char *readed_item = NULL; if (read_option_arg(self, option, opt_arg, &readed_item)) { return -1; } if (option->cb == NULL) { COMMAND_ERROR("Must specify callback for type array"); return -1; } return option->cb(option, readed_item); } static int command_get_option_data(command_t *self, command_option_t *option, const char **opt_arg) { if (option == NULL) { return -1; } switch (option->type) { case CMD_OPT_TYPE_BOOL: case CMD_OPT_TYPE_BOOL_FALSE: return command_get_bool_option_data(option, opt_arg); case CMD_OPT_TYPE_STRING: return command_get_string_option_data(self, option, opt_arg); case CMD_OPT_TYPE_STRING_DUP: return command_get_string_dup_option_data(self, option, opt_arg); case CMD_OPT_TYPE_CALLBACK: return command_get_callback_option_data(self, option, opt_arg); default: COMMAND_ERROR("Unkown command option type:%d", (int)(option->type)); return -1; } } int have_short_options(command_t *self, char arg) { int i; for (i = 0; i < self->option_count; i++) { if (self->options[i].small == arg) { return 0; } } return -1; } static int command_parse_options(command_t *self, const char **opt_arg, bool *found) { int j = 0; for (j = 0; j < self->option_count; ++j) { command_option_t *opt = &self->options[j]; opt->hasdata = false; if (opt->small != (*opt_arg)[0]) { continue; } // match flag *found = true; if ((*opt_arg)[1]) { if ((*opt_arg)[1] == '=') { *opt_arg = *opt_arg + 2; opt->hasdata = true; } else { *opt_arg = *opt_arg + 1; } } else { *opt_arg = NULL; } if (command_get_option_data(self, opt, opt_arg)) { return -1; } break; } return 0; } static int command_parse_short_arg(command_t *self, const char *arg) { bool found = false; const char *opt_arg = arg; do { found = false; if (command_parse_options(self, &opt_arg, &found)) { return -1; } } while (found && opt_arg != NULL); if (opt_arg != NULL) { COMMAND_ERROR("Unkown flag found:'%c'", opt_arg[0]); exit(EINVALIDARGS); } return 0; } static int command_parse_long_arg(command_t *self, const char *arg) { int j = 0; if (strcmp(arg, "help") == 0) { command_help(self); exit(0); } for (j = 0; j < self->option_count; ++j) { command_option_t *opt = &self->options[j]; const char *opt_arg = NULL; opt->hasdata = false; if (opt->large == NULL) { continue; } opt_arg = str_skip_str(arg, opt->large); if (opt_arg == NULL) { continue; } if (opt_arg[0]) { if (opt_arg[0] != '=') { continue; } opt_arg = opt_arg + 1; opt->hasdata = true; } else { opt_arg = NULL; } if (command_get_option_data(self, opt, &opt_arg)) { return -1; } return 0; } COMMAND_ERROR("Unkown flag found:'--%s'\n", arg); exit(EINVALIDARGS); } int command_parse_args(command_t *self, int *argc, char * const **argv) { int ret = 0; for (; self->argc; self->argc--, self->argv++) { const char *arg_opt = self->argv[0]; if (arg_opt[0] != '-' || !arg_opt[1]) { break; } // short option if (arg_opt[1] != '-') { arg_opt = arg_opt + 1; ret = command_parse_short_arg(self, arg_opt); if (!ret) { continue; } break; } // -- if (!arg_opt[2]) { self->argc--; self->argv++; break; } // long option arg_opt = arg_opt + 2; ret = command_parse_long_arg(self, arg_opt); if (ret == 0) { continue; } break; } if (self->argc > 0) { *argc = self->argc; *argv = (char * const *)self->argv; } return ret; } int command_valid_socket_append_array(command_option_t *option, const char *arg) { int len; char **pos = NULL; if (option == NULL) { return -1; } if (!util_validate_socket(arg)) { COMMAND_ERROR("Invalid socket name : %s", arg); return -1; } for (pos = *(char ***)(option->data), len = 0; pos != NULL && *pos != NULL; pos++, len++) { if (strcmp(*pos, arg) == 0) { break; } } if (pos != NULL && *pos != NULL) { return 0; } if (util_array_append(option->data, arg) != 0) { ERROR("merge hosts config failed"); return -1; } len++; if (len > MAX_HOSTS) { COMMAND_ERROR("Too many hosts, the max number is %d", MAX_HOSTS); return -1; } return 0; } static int check_default_ulimit_input(const char *val) { int ret = 0; if (val == NULL || strcmp(val, "") == 0) { COMMAND_ERROR("ulimit argument can't be empty"); ret = -1; goto out; } if (val[0] == '=' || val[strlen(val) - 1] == '=') { COMMAND_ERROR("Invalid ulimit argument: \"%s\", delimiter '=' can't" " be the first or the last character", val); ret = -1; } out: return ret; } static void get_default_ulimit_split_parts(const char *val, char ***parts, size_t *parts_len, char deli) { *parts = util_string_split_multi(val, deli); if (*parts == NULL) { ERROR("Out of memory"); return; } *parts_len = util_array_len((const char **)(*parts)); } static int parse_soft_hard_default_ulimit(const char *val, char **limitvals, size_t limitvals_len, int64_t *soft, int64_t *hard) { int ret = 0; // parse soft ret = util_safe_llong(limitvals[0], (long long *)soft); if (ret < 0) { COMMAND_ERROR("Invalid ulimit soft value: \"%s\", parse int64 failed: %s", val, strerror(-ret)); ret = -1; goto out; } // parse hard if exists if (limitvals_len > 1) { ret = util_safe_llong(limitvals[1], (long long *)hard); if (ret < 0) { COMMAND_ERROR("Invalid ulimit hard value: \"%s\", parse int64 failed: %s", val, strerror(-ret)); ret = -1; goto out; } if (*soft > *hard) { COMMAND_ERROR("Ulimit soft limit must be less than or equal to hard limit: %lld > %lld", (long long int)(*soft), (long long int)(*hard)); ret = -1; goto out; } } else { *hard = *soft; // default to soft in case no hard was set } out: return ret; } int check_default_ulimit_type(const char *type) { int ret = 0; char **tmptype = NULL; char *ulimit_valid_type[] = { // "as", // Disabled since this doesn't seem usable with the way Docker inits a container. "core", "cpu", "data", "fsize", "locks", "memlock", "msgqueue", "nice", "nofile", "nproc", "rss", "rtprio", "rttime", "sigpending", "stack", NULL }; for (tmptype = ulimit_valid_type; *tmptype != NULL; tmptype++) { if (strcmp(type, *tmptype) == 0) { break; } } if (*tmptype == NULL) { COMMAND_ERROR("Invalid ulimit type: %s", type); ret = -1; } return ret; } static host_config_ulimits_element *parse_default_ulimit(const char *val) { int ret = 0; int64_t soft = 0; int64_t hard = 0; size_t parts_len = 0; size_t limitvals_len = 0; char **parts = NULL; char **limitvals = NULL; host_config_ulimits_element *ulimit = NULL; ret = check_default_ulimit_input(val); if (ret != 0) { return NULL; } get_default_ulimit_split_parts(val, &parts, &parts_len, '='); if (parts == NULL) { ERROR("Out of memory"); return NULL; } else if (parts_len != 2) { COMMAND_ERROR("Invalid ulimit argument: %s", val); ret = -1; goto out; } ret = check_default_ulimit_type(parts[0]); if (ret != 0) { ret = -1; goto out; } if (parts[1][0] == ':' || parts[1][strlen(parts[1]) - 1] == ':') { COMMAND_ERROR("Invalid ulimit value: \"%s\", delimiter ':' can't be the first" " or the last character", val); ret = -1; goto out; } // parse value get_default_ulimit_split_parts(parts[1], &limitvals, &limitvals_len, ':'); if (limitvals == NULL) { ret = -1; goto out; } if (limitvals_len > 2) { COMMAND_ERROR("Too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1]); ret = -1; goto out; } ret = parse_soft_hard_default_ulimit(val, limitvals, limitvals_len, &soft, &hard); if (ret < 0) { goto out; } ulimit = util_common_calloc_s(sizeof(host_config_ulimits_element)); if (ulimit == NULL) { ret = -1; goto out; } ulimit->name = util_strdup_s(parts[0]); ulimit->hard = hard; ulimit->soft = soft; out: util_free_array(parts); util_free_array(limitvals); if (ret != 0) { free_host_config_ulimits_element(ulimit); ulimit = NULL; } return ulimit; } int command_default_ulimit_append(command_option_t *option, const char *arg) { int ret = 0; size_t ulimit_len; host_config_ulimits_element *tmp = NULL; host_config_ulimits_element **pos = NULL; if (option == NULL) { ret = -1; goto out; } tmp = parse_default_ulimit(arg); if (tmp == NULL) { ERROR("parse default ulimit from arg failed"); ret = -1; goto out; } for (pos = *(host_config_ulimits_element ***)(option->data); pos != NULL && *pos != NULL; pos++) { if (strcmp((*pos)->name, tmp->name) == 0) { break; } } if (pos != NULL && *pos != NULL) { (*pos)->hard = tmp->hard; (*pos)->soft = tmp->soft; goto out; } ulimit_len = ulimit_array_len(*(host_config_ulimits_element ***)(option->data)); if (ulimit_array_append(option->data, tmp, ulimit_len) != 0) { ERROR("default ulimit append failed"); ret = -1; } out: free_host_config_ulimits_element(tmp); return ret; } int command_append_array(command_option_t *option, const char *arg) { if (option == NULL) { return -1; } char ***array = option->data; return util_array_append(array, arg); } int command_convert_u16(command_option_t *option, const char *arg) { int ret = 0; if (option == NULL) { return -1; } ret = util_safe_u16(arg, option->data); if (ret != 0) { COMMAND_ERROR("Invalid value \"%s\" for flag --%s: %s", arg, option->large, strerror(-ret)); return EINVALIDARGS; } return 0; } int command_convert_llong(command_option_t *opt, const char *arg) { int ret; if (opt == NULL) { return -1; } ret = util_safe_llong(arg, opt->data); if (ret != 0) { COMMAND_ERROR("Invalid value \"%s\" for flag --%s: %s", arg, opt->large, strerror(-ret)); return EINVALIDARGS; } return 0; } int command_convert_uint(command_option_t *opt, const char *arg) { int ret; if (opt == NULL) { return -1; } ret = util_safe_uint(arg, opt->data); if (ret != 0) { COMMAND_ERROR("Invalid value \"%s\" for flag --%s: %s", arg, opt->large, strerror(-ret)); return EINVALIDARGS; } return 0; } int command_convert_int(command_option_t *option, const char *arg) { int ret = 0; if (option == NULL) { return -1; } ret = util_safe_int(arg, option->data); if (ret != 0) { COMMAND_ERROR("Invalid value \"%s\" for flag --%s: %s", arg, option->large, strerror(-ret)); return EINVALIDARGS; } return 0; } int command_convert_nanoseconds(command_option_t *option, const char *arg) { if (option == NULL) { return -1; } if (util_parse_time_str_to_nanoseconds(arg, option->data)) { COMMAND_ERROR("Invalid value \"%s\" for flag --%s", arg, option->large); return EINVALIDARGS; } return 0; } int command_convert_membytes(command_option_t *option, const char *arg) { if (option == NULL) { return -1; } if (util_parse_byte_size_string(arg, option->data) || (*(int64_t *)(option->data)) < 0) { COMMAND_ERROR("Invalid value \"%s\" for flag --%s", arg, option->large); return EINVALIDARGS; } return 0; } int command_convert_memswapbytes(command_option_t *option, const char *arg) { if (option == NULL) { return -1; } if (strcmp(arg, "-1") == 0) { *(int64_t *)(option->data) = -1; return 0; } if (command_convert_membytes(option, arg)) { return EINVALIDARGS; } return 0; } size_t ulimit_array_len(host_config_ulimits_element **default_ulimit) { size_t len = 0; host_config_ulimits_element **pos = NULL; for (pos = default_ulimit; pos != NULL && *pos != NULL; pos++) { len++; } return len; } int ulimit_array_append(host_config_ulimits_element ***ulimit_array, const host_config_ulimits_element *element, const size_t len) { int ret; size_t old_size, new_size; host_config_ulimits_element *new_element = NULL; host_config_ulimits_element **new_ulimit_array = NULL; if (ulimit_array == NULL || element == NULL) { return -1; } // let new len to len + 2 for element and null if (len > SIZE_MAX / sizeof(host_config_ulimits_element *) - 2) { ERROR("Too many ulimit elements!"); return -1; } new_size = (len + 2) * sizeof(host_config_ulimits_element *); old_size = len * sizeof(host_config_ulimits_element *); ret = mem_realloc((void **)(&new_ulimit_array), new_size, (void *)*ulimit_array, old_size); if (ret != 0) { ERROR("Failed to realloc memory for append ulimit"); return -1; } *ulimit_array = new_ulimit_array; new_element = util_common_calloc_s(sizeof(host_config_ulimits_element)); if (new_element == NULL) { ERROR("Out of memory"); free_default_ulimit(*ulimit_array); *ulimit_array = NULL; return -1; } new_element->name = util_strdup_s(element->name); new_element->hard = element->hard; new_element->soft = element->soft; new_ulimit_array[len] = new_element; return 0; } void free_default_ulimit(host_config_ulimits_element **default_ulimit) { host_config_ulimits_element **p = NULL; for (p = default_ulimit; p != NULL && *p != NULL; p++) { free_host_config_ulimits_element(*p); } free(default_ulimit); }