/****************************************************************************** * Copyright (c) Huawei Technologies Co., Ltd. 2019. All rights reserved. * clibcni licensed under the Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: * http://license.coscl.org.cn/MulanPSL2 * 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 v2 for more details. * Author: tanyifeng * Create: 2019-04-25 * Description: provide exec functions *********************************************************************************/ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "exec.h" #include "utils.h" #include "tools.h" #include "invoke_errno.h" #include "log.h" static int raw_exec(const char *plugin_path, const char *stdin_data, char * const environs[], char **stdout_str, exec_error **err); static char *str_exec_error(const exec_error *e_err) { char *result = NULL; int ret = 0; if (e_err == NULL) { ERROR("Argument is NULL"); return result; } ret = asprintf(&result, "%s%s", e_err->msg ? e_err->msg : "", e_err->details ? e_err->details : ""); if (ret < 0) { ERROR("Sprintf failed"); return NULL; } return result; } static int do_parse_exec_stdout_str(int exec_ret, const char *net_conf_json, const exec_error *e_err, const char *stdout_str, struct result **result, char **err) { int ret = exec_ret; char *version = NULL; if (exec_ret != 0) { if (e_err != NULL) { *err = str_exec_error(e_err); } else { *err = clibcni_util_strdup_s("raw exec fail"); } } else { version = cniversion_decode(net_conf_json, err); if (version == NULL) { ret = -1; ERROR("Decode cni version failed: %s", *err != NULL ? *err : ""); goto out; } if (clibcni_is_null_or_empty(stdout_str)) { ERROR("Get empty stdout message"); goto out; } *result = new_result(version, stdout_str, err); if (*result == NULL) { ERROR("Parse result failed: %s", *err != NULL ? *err : ""); ret = -1; } } out: free(version); return ret; } static inline bool check_exec_plugin_with_result_args(const char *net_conf_json, struct result * const *result, char * const *err) { return (net_conf_json == NULL || result == NULL || err == NULL); } int exec_plugin_with_result(const char *plugin_path, const char *net_conf_json, const struct cni_args *cniargs, struct result **result, char **err) { char **envs = NULL; char *stdout_str = NULL; exec_error *e_err = NULL; int ret = 0; if (check_exec_plugin_with_result_args(net_conf_json, result, err)) { ERROR("Invalid arguments"); return -1; } if (cniargs != NULL) { envs = as_env(cniargs); if (envs == NULL) { *err = clibcni_util_strdup_s("As env failed"); ret = -1; goto out; } } ret = raw_exec(plugin_path, net_conf_json, envs, &stdout_str, &e_err); DEBUG("Raw exec \"%s\" result: %d", plugin_path, ret); ret = do_parse_exec_stdout_str(ret, net_conf_json, e_err, stdout_str, result, err); out: free(stdout_str); clibcni_util_free_array(envs); free_exec_error(e_err); return ret; } int exec_plugin_without_result(const char *plugin_path, const char *net_conf_json, const struct cni_args *cniargs, char **err) { char **envs = NULL; exec_error *e_err = NULL; int ret = 0; bool invalid_arg = (net_conf_json == NULL || err == NULL); if (invalid_arg) { ERROR("Invalid arguments"); return -1; } if (cniargs != NULL) { envs = as_env(cniargs); if (envs == NULL) { *err = clibcni_util_strdup_s("As env failed"); goto out; } } ret = raw_exec(plugin_path, net_conf_json, envs, NULL, &e_err); if (ret != 0) { if (e_err != NULL) { *err = str_exec_error(e_err); } else { *err = clibcni_util_strdup_s("raw exec fail"); } } DEBUG("Raw exec \"%s\" result: %d", plugin_path, ret); out: clibcni_util_free_array(envs); free_exec_error(e_err); return ret; } static int do_parse_get_version_errmsg(int exec_ret, const exec_error *e_err, struct plugin_info **result, char **err) { char *str_err = NULL; if (exec_ret == 0) { return 0; } str_err = str_exec_error(e_err); if (str_err != NULL && strcmp(str_err, "unknown CNI_COMMAND: VERSION") == 0) { const char *default_supports[] = { "0.1.0", NULL }; *result = plugin_supports(default_supports, 1, err); if (*result == NULL) { ERROR("Parse result failed: %s", *err != NULL ? *err : ""); goto free_out; } } *err = str_err; str_err = NULL; free_out: free(str_err); return -1; } int raw_get_version_info(const char *plugin_path, struct plugin_info **result, char **err) { int ret = 0; struct cni_args args = { .command = "VERSION", .netns = "dummy", .ifname = "dummy", .path = "dummy", .container_id = NULL, .plugin_args = NULL, .plugin_args_len = 0, .plugin_args_str = NULL }; char *stdin_data = NULL; char *stdout_str = NULL; const char *version = current(); size_t len = 0; char **envs = NULL; exec_error *e_err = NULL; bool invalid_arg = (result == NULL || err == NULL); if (invalid_arg) { ERROR("Invalid arguments"); return -1; } envs = as_env(&args); if (envs == NULL) { ret = -1; *err = clibcni_util_strdup_s("As env failed"); goto free_out; } len = strlen("{\"cniVersion\":}") + strlen(version) + 1; stdin_data = clibcni_util_common_calloc_s(len); if (stdin_data == NULL) { ERROR("Out of memory"); ret = -1; goto free_out; } ret = snprintf(stdin_data, len, "{\"cniVersion\":%s}", version); if (ret < 0 || (size_t)ret >= len) { ERROR("Sprintf failed"); *err = clibcni_util_strdup_s("Sprintf failed"); goto free_out; } ret = raw_exec(plugin_path, stdin_data, envs, &stdout_str, &e_err); DEBUG("Raw exec \"%s\" result: %d", plugin_path, ret); ret = do_parse_get_version_errmsg(ret, e_err, result, err); if (ret != 0) { goto free_out; } *result = plugin_info_decode(stdout_str, err); if (*result == NULL) { ret = -1; } free_out: free_exec_error(e_err); clibcni_util_free_array(envs); free(stdin_data); free(stdout_str); return ret; } static int prepare_child(int pipe_stdin, int pipe_stdout) { sigset_t mask; int ecode = 0; int ret = 0; if (pipe_stdin != STDIN_FILENO) { ret = dup2(pipe_stdin, STDIN_FILENO); } else { ret = fcntl(pipe_stdin, F_SETFD, 0); } if (ret != 0) { ecode = EXIT_FAILURE; goto child_err_out; } (void)close(pipe_stdin); pipe_stdin = -1; if (pipe_stdout != STDOUT_FILENO) { ret = dup2(pipe_stdout, STDOUT_FILENO); } else { ret = fcntl(pipe_stdout, F_SETFD, 0); } if (ret < 0) { ecode = EXIT_FAILURE; goto child_err_out; } (void)close(pipe_stdout); pipe_stdout = -1; { /* * unblock all signal * */ ret = sigfillset(&mask); if (ret < 0) { ecode = EXIT_FAILURE; goto child_err_out; } ret = sigprocmask(SIG_UNBLOCK, &mask, NULL); if (ret < 0) { ecode = EXIT_FAILURE; goto child_err_out; } } child_err_out: return ecode; } static void child_fun(const char *plugin_path, int pipe_stdin, int pipe_stdout, char * const environs[], size_t envs_len) { char *argv[2] = { NULL }; int ecode = 0; argv[0] = clibcni_util_strdup_s(plugin_path); ecode = prepare_child(pipe_stdin, pipe_stdout); if (ecode != 0) { goto child_err_out; } if (envs_len > 0) { ecode = execvpe(plugin_path, argv, environs); } else { ecode = execvp(plugin_path, argv); } (void)fprintf(stdout, "Execv: %s failed %s", plugin_path, strerror(errno)); child_err_out: free(argv[0]); if (ecode == 0) { ecode = 127; } if (pipe_stdin != -1) { (void)close(pipe_stdin); } if (pipe_stdout != -1) { (void)close(pipe_stdout); } exit(ecode); } static inline bool check_prepare_raw_exec_args(const char *plugin_path) { return (plugin_path == NULL || clibcni_util_validate_absolute_path(plugin_path)); } static int prepare_raw_exec(const char *plugin_path, int pipe_stdin[2], int pipe_stdout[2], char *errmsg, size_t len) { int ret = 0; if (check_prepare_raw_exec_args(plugin_path)) { ret = snprintf(errmsg, len, "Empty or not absolute path: %s", plugin_path); if (ret < 0 || (size_t)ret >= len) { ERROR("Sprintf failed"); } return -1; } ret = pipe2(pipe_stdin, O_CLOEXEC | O_NONBLOCK); if (ret < 0) { ret = snprintf(errmsg, len, "Pipe stdin failed: %s", strerror(errno)); if (ret < 0 || (size_t)ret >= len) { ERROR("Sprintf failed"); } return -1; } ret = pipe2(pipe_stdout, O_CLOEXEC | O_NONBLOCK); if (ret < 0) { ret = snprintf(errmsg, len, "Pipe stdout failed: %s", strerror(errno)); if (ret < 0 || (size_t)ret >= len) { ERROR("Sprintf failed"); } return -1; } return 0; } static int write_stdin_data_to_child(int pipe_stdin[2], const char *stdin_data, char *errmsg, size_t errmsg_len) { int ret = 0; size_t len = 0; if (stdin_data == NULL) { goto close_pipe; } len = strlen(stdin_data); if (clibcni_util_write_nointr(pipe_stdin[1], stdin_data, len) != (ssize_t)len) { ret = snprintf(errmsg, errmsg_len, "Write stdin data failed: %s", strerror(errno)); if (ret < 0 || (size_t)ret >= errmsg_len) { ERROR("Sprintf failed"); } ret = -1; } close_pipe: (void)close(pipe_stdin[1]); pipe_stdin[1] = -1; return ret; } static int read_child_stdout_msg(const int pipe_stdout[2], char *errmsg, size_t errmsg_len, char **stdout_str) { int ret = 0; if (errmsg == NULL) { return 0; } if (stdout_str != NULL) { char buffer[CLIBCNI_BUFFER_SIZE] = { 0 }; ssize_t tmp_len = clibcni_util_read_nointr(pipe_stdout[0], buffer, CLIBCNI_BUFFER_SIZE - 1); if (tmp_len < 0) { ret = snprintf(errmsg, errmsg_len, "%s; read stdout failed: %s", strlen(errmsg) > 0 ? errmsg : "", strerror(errno)); if (ret < 0 || (size_t)ret >= errmsg_len) { ERROR("Sprintf failed"); } ret = -1; } else if (tmp_len > 0) { *stdout_str = clibcni_util_strdup_s(buffer); } } return ret; } static int wait_pid_for_raw_exec_child(pid_t child_pid, const int pipe_stdout[2], char **stdout_str, char *errmsg, size_t errmsg_len, bool *parse_exec_err) { pid_t wait_pid = 0; int wait_status = 0; int ret = 0; if (errmsg == NULL) { return -1; } do { wait_pid = waitpid(child_pid, &wait_status, 0); } while (wait_pid < 0 && errno == EINTR); ret = read_child_stdout_msg(pipe_stdout, errmsg, errmsg_len, stdout_str); if (wait_pid < 0) { ret = snprintf(errmsg, errmsg_len, "%s; waitpid failed: %s", strlen(errmsg) > 0 ? errmsg : "", strerror(errno)); if (ret < 0 || (size_t)ret >= errmsg_len) { ERROR("Sprintf failed"); } ret = -1; goto err_free_out; } else if (WIFEXITED(wait_status) && WEXITSTATUS(wait_status)) { ret = snprintf(errmsg, errmsg_len, "%s; get child status: %d", strlen(errmsg) > 0 ? errmsg : "", WEXITSTATUS(wait_status)); if (ret < 0 || (size_t)ret >= errmsg_len) { ERROR("Sprintf failed"); } ret = WEXITSTATUS(wait_status); *parse_exec_err = true; goto err_free_out; } else if (WIFSIGNALED(wait_status)) { ret = snprintf(errmsg, errmsg_len, "%s; child get signal: %d", strlen(errmsg) > 0 ? errmsg : "", WTERMSIG(wait_status)); if (ret < 0 || (size_t)ret >= errmsg_len) { ERROR("Sprintf failed"); } ret = INK_ERR_TERM_BY_SIG; *parse_exec_err = true; goto err_free_out; } err_free_out: return ret; } static void close_raw_exec_pipes(int pipe_stdin[2], int pipe_stdout[2]) { if (pipe_stdout[0] >= 0) { (void)close(pipe_stdout[0]); pipe_stdout[0] = -1; } if (pipe_stdout[1] >= 0) { (void)close(pipe_stdout[1]); pipe_stdout[1] = -1; } if (pipe_stdin[0] >= 0) { (void)close(pipe_stdin[0]); pipe_stdin[0] = -1; } if (pipe_stdin[1] >= 0) { (void)close(pipe_stdin[1]); pipe_stdin[1] = -1; } } static inline bool check_make_err_message_args(bool parse_exec_err, char * const *stdout_str) { return (parse_exec_err && stdout_str != NULL && *stdout_str != NULL); } static void make_err_message(const char *plugin_path, char **stdout_str, int ret, bool parse_exec_err, char *errmsg, size_t errmsg_len, exec_error **err) { int nret = ret; bool get_err_msg = false; if (errmsg == NULL) { return; } if (check_make_err_message_args(parse_exec_err, stdout_str)) { parser_error json_err = NULL; *err = exec_error_parse_data(*stdout_str, NULL, &json_err); if (*err == NULL) { nret = snprintf(errmsg, errmsg_len, "exec \'%s\': %s; parse failed: %s", plugin_path, strlen(errmsg) > 0 ? errmsg : "", json_err); if (nret < 0 || (size_t)nret >= errmsg_len) { ERROR("Sprintf failed"); } nret = INK_ERR_PARSE_JSON_TO_OBJECT_FAILED; } free(json_err); } get_err_msg = (nret != 0 && *err == NULL && strlen(errmsg) > 0); if (get_err_msg) { *err = clibcni_util_common_calloc_s(sizeof(exec_error)); if (*err != NULL) { char *tmp_err = NULL; nret = asprintf(&tmp_err, "exec \'%s\' failed: %s", plugin_path, errmsg); if (nret < 0) { tmp_err = clibcni_util_strdup_s(errmsg); } (*err)->msg = tmp_err; (*err)->code = 1; } } } static int do_parent_waitpid(int pipe_stdin[2], const int pipe_stdout[2], pid_t child_pid, char *errmsg, size_t errmsg_len, const char *stdin_data, char **stdout_str, bool *parse_exec_err) { int ret = 0; if (errmsg == NULL) { return -1; } /* write stdin_data into stdin of child process */ if (write_stdin_data_to_child(pipe_stdin, stdin_data, errmsg, errmsg_len) != 0) { ERROR("Write stdin data failed: %s", errmsg); ret = -1; } /* wait child exit, and deal with exitcode */ if (wait_pid_for_raw_exec_child(child_pid, pipe_stdout, stdout_str, errmsg, errmsg_len, parse_exec_err) != 0) { ERROR("Wait pid for child failed: %s", errmsg); ret = -1; } return ret; } static int raw_exec(const char *plugin_path, const char *stdin_data, char * const environs[], char **stdout_str, exec_error **err) { int ret = 0; int pipe_stdout[2] = { -1, -1 }; int pipe_stdin[2] = { -1, -1 }; pid_t child_pid = 0; char errmsg[CLIBCNI_BUFFER_SIZE] = { 0 }; bool parse_exec_err = false; if (prepare_raw_exec(plugin_path, pipe_stdin, pipe_stdout, errmsg, sizeof(errmsg)) != 0) { ret = -1; goto err_free_out; } child_pid = fork(); if (child_pid < 0) { ret = snprintf(errmsg, sizeof(errmsg), "Fork failed: %s", strerror(errno)); if (ret < 0 || (size_t)ret >= sizeof(errmsg)) { ERROR("Sprintf failed"); } ret = -1; goto err_free_out; } if (child_pid == 0) { (void)close(pipe_stdin[1]); pipe_stdin[1] = -1; (void)close(pipe_stdout[0]); pipe_stdout[0] = -1; size_t envs_len = 0; envs_len = clibcni_util_array_len((const char * const *)environs); child_fun(plugin_path, pipe_stdin[0], pipe_stdout[1], environs, envs_len); /* exit in child_fun */ } (void)close(pipe_stdout[1]); pipe_stdout[1] = -1; (void)close(pipe_stdin[0]); pipe_stdin[0] = -1; ret = do_parent_waitpid(pipe_stdin, pipe_stdout, child_pid, errmsg, sizeof(errmsg), stdin_data, stdout_str, &parse_exec_err); err_free_out: /* parse error json message */ make_err_message(plugin_path, stdout_str, ret, parse_exec_err, errmsg, sizeof(errmsg), err); if (ret != 0 && stdout_str != NULL) { free(*stdout_str); *stdout_str = NULL; } close_raw_exec_pipes(pipe_stdin, pipe_stdout); return ret; }