/****************************************************************************** * 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 container create callback function definition ********************************************************************************/ #include "execution_create.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "log.h" #include "engine.h" #include "console.h" #include "isulad_config.h" #include "config.h" #include "specs.h" #include "verify.h" #include "containers_store.h" #include "execution_network.h" #include "runtime.h" #include "plugin.h" #include "image.h" #include "utils.h" #include "error.h" #include "constants.h" #include "namespace.h" #include "collector.h" static int runtime_check(const char *name, bool *runtime_res) { int ret = 0; struct service_arguments *args = NULL; defs_map_string_object_runtimes *runtimes = NULL; if (isulad_server_conf_rdlock()) { ret = -1; goto out; } args = conf_get_server_conf(); if (args == NULL) { ERROR("Failed to get isulad server config"); ret = -1; goto unlock_out; } if (args->json_confs != NULL) { runtimes = args->json_confs->runtimes; } if (runtimes == NULL) { EVENT("isulad runtimes param is null"); goto unlock_out; } size_t runtime_nums = runtimes->len; size_t i; for (i = 0; i < runtime_nums; i++) { if (strcmp(name, runtimes->keys[i]) == 0) { *runtime_res = true; goto unlock_out; } } unlock_out: if (isulad_server_conf_unlock()) { ERROR("Failed to unlock isulad server config"); ret = -1; } out: if (strcmp(name, "runc") == 0 || strcmp(name, "lcr") == 0) { *runtime_res = true; } if (strcmp(name, "kata-runtime") == 0) { *runtime_res = true; } return ret; } static int create_request_check(const container_create_request *request) { int ret = 0; parser_error err = NULL; if (request == NULL) { ERROR("Receive NULL container id"); ret = -1; goto out; } if ((request->rootfs == NULL && request->image == NULL)) { ERROR("Container image or rootfs error"); ret = -1; goto out; } if (request->image != NULL && !util_valid_image_name(request->image)) { ERROR("invalid image name %s", request->image); isulad_set_error_message("Invalid image name '%s'", request->image); ret = -1; goto out; } if (request->hostconfig == NULL) { ERROR("Receive NULL Request hostconfig"); ret = -1; goto out; } if (request->customconfig == NULL) { ERROR("Receive NULL Request customconfig"); ret = -1; goto out; } out: free(err); return ret; } static host_config *get_host_spec_from_request(const container_create_request *request) { parser_error err = NULL; host_config *host_spec = NULL; host_spec = host_config_parse_data(request->hostconfig, NULL, &err); if (host_spec == NULL) { ERROR("Failed to parse host config data:%s", err); } free(err); return host_spec; } static int merge_external_rootfs_to_host_config(host_config *host_spec, const char *external_rootfs) { if (host_spec == NULL) { return -1; } host_spec->external_rootfs = external_rootfs != NULL ? util_strdup_s(external_rootfs) : NULL; return 0; } static host_config *get_host_spec(const container_create_request *request) { host_config *host_spec = NULL; host_spec = get_host_spec_from_request(request); if (host_spec == NULL) { return NULL; } if (merge_external_rootfs_to_host_config(host_spec, request->rootfs) != 0) { goto error_out; } if (verify_host_config_settings(host_spec, false)) { ERROR("Failed to verify host config settings"); goto error_out; } return host_spec; error_out: free_host_config(host_spec); return NULL; } static container_config *get_container_spec_from_request(const container_create_request *request) { parser_error err = NULL; container_config *container_spec = container_config_parse_data(request->customconfig, NULL, &err); if (container_spec == NULL) { ERROR("Failed to parse custom config data:%s", err); } free(err); return container_spec; } static int add_default_log_config_to_container_spec(const char *id, const char *runtime_root, container_config *container_spec) { int ret = 0; /* generate default log path */ if (container_spec->log_config == NULL) { container_spec->log_config = util_common_calloc_s(sizeof(container_config_log_config)); if (container_spec->log_config == NULL) { ERROR("Out of memory"); ret = -1; goto out; } } if (container_spec->log_config->log_file == NULL) { char default_path[PATH_MAX] = { 0 }; int nret = snprintf(default_path, PATH_MAX, "%s/%s/console.log", runtime_root, id); if (nret < 0 || nret >= PATH_MAX) { ERROR("Create default log path for container %s failed", id); ret = -1; goto out; } container_spec->log_config->log_file = util_strdup_s(default_path); } out: return ret; } static container_config *get_container_spec(const char *id, const char *runtime_root, const container_create_request *request) { container_config *container_spec = NULL; container_spec = get_container_spec_from_request(request); if (container_spec == NULL) { return NULL; } if (add_default_log_config_to_container_spec(id, runtime_root, container_spec)) { goto error_out; } return container_spec; error_out: free_container_config(container_spec); return NULL; } static oci_runtime_spec *generate_oci_config(host_config *host_spec, const char *real_rootfs, container_config_v2_common_config *v2_spec) { int ret = 0; oci_runtime_spec *oci_spec = NULL; oci_spec = default_spec(host_spec->system_container); if (oci_spec == NULL) { goto error_out; } ret = merge_all_specs(host_spec, real_rootfs, v2_spec, oci_spec); if (ret != 0) { ERROR("Failed to merge config"); goto error_out; } if (merge_global_config(oci_spec) != 0) { ERROR("Failed to merge global config"); goto error_out; } return oci_spec; error_out: free_oci_runtime_spec(oci_spec); return NULL; } static int merge_config_for_syscontainer(const container_create_request *request, const host_config *host_spec, const container_config *container_spec, const oci_runtime_spec *oci_spec) { int ret = 0; char *value = NULL; if (!host_spec->system_container) { return 0; } if (request->rootfs == NULL) { value = oci_spec->root->path; } else { value = request->rootfs; } if (append_json_map_string_string(oci_spec->annotations, "rootfs.mount", value)) { ERROR("Realloc annotations failed"); ret = -1; goto out; } if (request->rootfs != NULL && append_json_map_string_string(oci_spec->annotations, "external.rootfs", "true")) { ERROR("Realloc annotations failed"); ret = -1; goto out; } out: return ret; } static char *try_generate_id() { int i = 0; int max_time = 10; char *id = NULL; char *value = NULL; id = util_common_calloc_s(sizeof(char) * (CONTAINER_ID_MAX_LEN + 1)); if (id == NULL) { ERROR("Out of memory"); return NULL; } for (i = 0; i < max_time; i++) { if (util_generate_random_str(id, (size_t)CONTAINER_ID_MAX_LEN)) { ERROR("Generate id failed"); goto err_out; } value = name_index_get(id); if (value != NULL) { continue; } else { goto out; } } err_out: free(id); id = NULL; out: return id; } static int inspect_image(const char *image, imagetool_image **result) { int ret = 0; im_status_request *request = NULL; im_status_response *response = NULL; if (image == NULL) { ERROR("Empty image name or id"); return -1; } request = (im_status_request *)util_common_calloc_s(sizeof(im_status_request)); if (request == NULL) { ERROR("Out of memory"); return -1; } request->image.image = util_strdup_s(image); if (im_image_status(request, &response) != 0) { if (response != NULL && response->errmsg != NULL) { ERROR("failed to inspect inspect image info: %s", response->errmsg); } else { ERROR("Failed to call status image"); } ret = -1; goto cleanup; } if (response->image_info != NULL) { *result = response->image_info->image; response->image_info->image = NULL; } cleanup: free_im_status_request(request); free_im_status_response(response); return ret; } static int conf_get_image_id(const char *image, char **id) { int ret = 0; imagetool_image *ir = NULL; size_t len = 0; char *image_id = NULL; if (image == NULL || strcmp(image, "none") == 0) { *id = util_strdup_s("none"); return 0; } if (inspect_image(image, &ir) != 0) { ERROR("Failed to inspect image status"); ret = -1; goto out; } if (strlen(ir->id) > SIZE_MAX / sizeof(char) - strlen("sha256:")) { ERROR("Invalid image id"); ret = -1; goto out; } len = strlen("sha256:") + strlen(ir->id) + 1; image_id = (char *)util_common_calloc_s(len * sizeof(char)); if (image_id == NULL) { ERROR("Out of memory"); ret = -1; goto out; } int nret = snprintf(image_id, len, "sha256:%s", ir->id); if (nret < 0 || (size_t)nret >= len) { ERROR("Failed to sprintf string"); ret = -1; goto out; } *id = image_id; image_id = NULL; out: free_imagetool_image(ir); free(image_id); return ret; } static int register_new_container(const char *id, const char *runtime, host_config **host_spec, container_config_v2_common_config **v2_spec) { int ret = -1; bool registed = false; char *runtime_root = NULL; char *runtime_stat = NULL; char *image_id = NULL; container_t *cont = NULL; runtime_root = conf_get_routine_rootdir(runtime); if (runtime_root == NULL) { goto out; } runtime_stat = conf_get_routine_statedir(runtime); if (runtime_stat == NULL) { goto out; } if (strcmp((*v2_spec)->image_type, IMAGE_TYPE_OCI) == 0) { if (conf_get_image_id((*v2_spec)->image, &image_id) != 0) { goto out; } } cont = container_new(runtime, runtime_root, runtime_stat, image_id, host_spec, v2_spec); if (cont == NULL) { ERROR("Failed to create container '%s'", id); goto out; } if (container_to_disk_locking(cont)) { ERROR("Failed to save container '%s'", id); goto out; } registed = containers_store_add(id, cont); if (!registed) { ERROR("Failed to register container '%s'", id); goto out; } ret = 0; out: free(runtime_root); free(runtime_stat); free(image_id); if (ret != 0) { container_unref(cont); } return ret; } static int maintain_container_id(const container_create_request *request, char **out_id, char **out_name) { int ret = 0; char *id = NULL; char *name = NULL; id = try_generate_id(); if (id == NULL) { ERROR("Failed to generate container ID"); isulad_set_error_message("Failed to generate container ID"); ret = -1; goto out; } set_log_prefix(id); if (request->id != NULL) { name = util_strdup_s(request->id); } else { name = util_strdup_s(id); } if (!util_valid_container_name(name)) { ERROR("Invalid container name (%s), only [a-zA-Z0-9][a-zA-Z0-9_.-]+$ are allowed.", name); isulad_set_error_message("Invalid container name (%s), only [a-zA-Z0-9][a-zA-Z0-9_.-]+$ are allowed.", name); ret = -1; goto out; } EVENT("Event: {Object: %s, Type: Creating %s}", id, name); if (!name_index_add(name, id)) { ERROR("Name %s is in use", name); isulad_set_error_message("Conflict. The name \"%s\" is already in use by container %s. " "You have to remove (or rename) that container to be able to reuse that name.", name, name); ret = -1; goto out; } out: *out_id = id; *out_name = name; return ret; } static char *get_runtime_from_request(const container_create_request *request) { return strings_to_lower(request->runtime); } static void pack_create_response(container_create_response *response, const char *id, uint32_t cc) { response->cc = cc; if (g_isulad_errmsg != NULL) { response->errmsg = util_strdup_s(g_isulad_errmsg); DAEMON_CLEAR_ERRMSG(); } if (id != NULL) { response->id = util_strdup_s(id); } } static int prepare_host_channel(const host_config_host_channel *host_channel, const char *user_remap) { unsigned int host_uid = 0; unsigned int host_gid = 0; unsigned int size = 0; if (host_channel == NULL) { return 0; } if (util_dir_exists(host_channel->path_on_host)) { ERROR("Host path '%s' already exist", host_channel->path_on_host); return -1; } if (util_mkdir_p(host_channel->path_on_host, HOST_PATH_MODE)) { ERROR("Failed to create host path '%s'.", host_channel->path_on_host); return -1; } if (user_remap != NULL) { if (util_parse_user_remap(user_remap, &host_uid, &host_gid, &size)) { ERROR("Failed to split string '%s'.", user_remap); return -1; } if (chown(host_channel->path_on_host, host_uid, host_gid) != 0) { ERROR("Failed to chown host path '%s'.", host_channel->path_on_host); return -1; } } return 0; } void umount_host_channel(const host_config_host_channel *host_channel) { if (host_channel == NULL) { return; } if (detect_mount(host_channel->path_on_host)) { if (umount2(host_channel->path_on_host, MNT_DETACH)) { ERROR("Failed to umount the target: %s", host_channel->path_on_host); } } if (util_recursive_rmdir(host_channel->path_on_host, 0)) { ERROR("Failed to delete host path: %s", host_channel->path_on_host); } } void umount_share_shm(container_t *cont) { if (has_mount_for(cont, "/dev/shm")) { return; } if (cont->hostconfig == NULL) { return; } // ignore shm of system container if (cont->hostconfig->system_container) { return; } if (cont->hostconfig->ipc_mode == NULL || is_shareable(cont->hostconfig->ipc_mode)) { if (cont->common_config == NULL || cont->common_config->shm_path == NULL) { return; } INFO("Umounting share shm: %s", cont->common_config->shm_path); if (umount2(cont->common_config->shm_path, MNT_DETACH)) { SYSERROR("Failed to umount the target: %s", cont->common_config->shm_path); } } } static void umount_shm_by_configs(host_config *host_spec, container_config_v2_common_config *v2_spec) { container_t *cont = NULL; cont = util_common_calloc_s(sizeof(container_t)); if (cont == NULL) { ERROR("Out of memory"); return; } cont->common_config = v2_spec; cont->hostconfig = host_spec; umount_share_shm(cont); cont->common_config = NULL; cont->hostconfig = NULL; free(cont); } static int create_container_root_dir(const char *id, const char *runtime_root) { int ret = 0; int nret; char container_root[PATH_MAX] = { 0x00 }; mode_t mask = umask(S_IWOTH); nret = snprintf(container_root, sizeof(container_root), "%s/%s", runtime_root, id); if ((size_t)nret >= sizeof(container_root) || nret < 0) { ret = -1; goto out; } // create container dir nret = util_mkdir_p(container_root, CONFIG_DIRECTORY_MODE); if (nret != 0 && errno != EEXIST) { SYSERROR("Failed to create container path %s", container_root); ret = -1; goto out; } out: umask(mask); return ret; } static int delete_container_root_dir(const char *id, const char *runtime_root) { int ret = 0; char container_root[PATH_MAX] = { 0x00 }; ret = snprintf(container_root, sizeof(container_root), "%s/%s", runtime_root, id); if ((size_t)ret >= sizeof(container_root) || ret < 0) { ERROR("Failed to sprintf invalid root directory %s/%s", runtime_root, id); ret = -1; goto out; } ret = util_recursive_rmdir(container_root, 0); if (ret != 0) { ERROR("Failed to delete container's state directory %s", container_root); ret = -1; goto out; } out: return ret; } static host_config_host_channel *dup_host_channel(const host_config_host_channel *channel) { host_config_host_channel *dup_channel = NULL; if (channel == NULL) { return NULL; } dup_channel = (host_config_host_channel *)util_common_calloc_s(sizeof(host_config_host_channel)); if (dup_channel == NULL) { ERROR("Out of memory"); return NULL; } dup_channel->path_on_host = channel->path_on_host != NULL ? util_strdup_s(channel->path_on_host) : NULL; dup_channel->path_in_container = channel->path_in_container != NULL ? util_strdup_s(channel->path_in_container) : NULL; dup_channel->permissions = channel->permissions != NULL ? util_strdup_s(channel->permissions) : NULL; dup_channel->size = channel->size; return dup_channel; } static int response_allocate_memory(container_create_response **response) { if (response == NULL) { ERROR("Invalid NULL input"); return -1; } *response = util_common_calloc_s(sizeof(container_create_response)); if (*response == NULL) { ERROR("Out of memory"); return -1; } return 0; } static int get_request_container_info(const container_create_request *request, char **id, char **name, uint32_t *cc) { if (create_request_check(request) != 0) { ERROR("Invalid create container request"); *cc = ISULAD_ERR_INPUT; return -1; } if (maintain_container_id(request, id, name) != 0) { *cc = ISULAD_ERR_EXEC; return -1; } return 0; } static int get_request_image_info(const container_create_request *request, char **image_type, const char **ext_config_image, const char **image_name) { *image_type = im_get_image_type(request->image, request->rootfs); if (*image_type == NULL) { return -1; } if (request->rootfs != NULL) { *image_name = request->rootfs; // Do not use none image because none image has no config. if (strcmp(request->image, "none") && strcmp(request->image, "none:latest")) { *ext_config_image = request->image; } } else { *image_name = request->image; } // Check if config image exist if provided. if (*ext_config_image != NULL) { if (!im_config_image_exist(*ext_config_image)) { return -1; } } return 0; } static int preparate_runtime_environment(const container_create_request *request, const char *id, char **runtime, char **runtime_root, uint32_t *cc) { bool runtime_res = false; if (request->runtime) { *runtime = get_runtime_from_request(request); } else { *runtime = conf_get_default_runtime(); } if (*runtime == NULL) { *runtime = util_strdup_s(DEFAULT_RUNTIME_NAME); } if (runtime_check(*runtime, &runtime_res) != 0) { ERROR("Runtimes param check failed"); return -1; } if (!runtime_res) { ERROR("Invalid runtime name:%s", *runtime); isulad_set_error_message("Invalid runtime name (%s).", *runtime); return -1; } *runtime_root = conf_get_routine_rootdir(*runtime); if (*runtime_root == NULL) { *cc = ISULAD_ERR_EXEC; return -1; } if (create_container_root_dir(id, *runtime_root) != 0) { *cc = ISULAD_ERR_EXEC; return -1; } return 0; } static int get_basic_spec(const container_create_request *request, const char *id, const char *runtime_root, host_config **host_spec, container_config **container_spec) { *host_spec = get_host_spec(request); if (*host_spec == NULL) { return -1; } *container_spec = get_container_spec(id, runtime_root, request); if (*container_spec == NULL) { return -1; } return 0; } /* * request -> host_spec + container_spec * container_spec + image config * host_spec + container_spec + default_spec+ global_spec => oci_spec * verify oci_spec * register container(save v2_spec\host_spec\oci_spec) */ int container_create_cb(const container_create_request *request, container_create_response **response) { uint32_t cc = ISULAD_SUCCESS; char *real_rootfs = NULL; char *image_type = NULL; char *runtime_root = NULL; char *oci_config_data = NULL; char *runtime = NULL; char *name = NULL; char *id = NULL; const char *image_name = NULL; const char *ext_config_image = NULL; oci_runtime_spec *oci_spec = NULL; host_config *host_spec = NULL; container_config *container_spec = NULL; container_config_v2_common_config *v2_spec = NULL; host_config_host_channel *host_channel = NULL; int ret = 0; DAEMON_CLEAR_ERRMSG(); if (response_allocate_memory(response) != 0) { return -1; } if (get_request_container_info(request, &id, &name, &cc) != 0) { goto pack_response; } if (get_request_image_info(request, &image_type, &ext_config_image, &image_name) != 0) { cc = ISULAD_ERR_EXEC; goto clean_nameindex; } if (preparate_runtime_environment(request, id, &runtime, &runtime_root, &cc) != 0) { goto clean_nameindex; } if (get_basic_spec(request, id, runtime_root, &host_spec, &container_spec) != 0) { cc = ISULAD_ERR_INPUT; goto clean_container_root_dir; } // update runtime of host config free(host_spec->runtime); host_spec->runtime = util_strdup_s(runtime); v2_spec = util_common_calloc_s(sizeof(container_config_v2_common_config)); if (v2_spec == NULL) { ERROR("Failed to malloc container_config_v2_common_config"); cc = ISULAD_ERR_INPUT; goto clean_container_root_dir; } char timebuffer[TIME_STR_SIZE] = { 0 }; v2_spec->id = id ? util_strdup_s(id) : NULL; v2_spec->name = name ? util_strdup_s(name) : NULL; v2_spec->image = image_name ? util_strdup_s(image_name) : util_strdup_s("none"); v2_spec->image_type = image_type ? util_strdup_s(image_type) : NULL; (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); free(v2_spec->created); v2_spec->created = util_strdup_s(timebuffer); v2_spec->config = container_spec; if (init_container_network_confs(id, runtime_root, host_spec, v2_spec) != 0) { ERROR("Init Network files failed"); cc = ISULAD_ERR_INPUT; goto clean_container_root_dir; } ret = im_merge_image_config(id, image_type, image_name, ext_config_image, host_spec, v2_spec->config, &real_rootfs); if (ret != 0) { ERROR("Can not merge container_spec with image config"); cc = ISULAD_ERR_EXEC; goto clean_container_root_dir; } if (verify_health_check_parameter(v2_spec->config) != 0) { cc = ISULAD_ERR_EXEC; goto clean_rootfs; } oci_spec = generate_oci_config(host_spec, real_rootfs, v2_spec); if (oci_spec == NULL) { cc = ISULAD_ERR_EXEC; goto umount_shm; } ret = merge_oci_cgroups_path(id, oci_spec, host_spec); if (ret < 0) { goto umount_shm; } if (merge_config_for_syscontainer(request, host_spec, v2_spec->config, oci_spec) != 0) { ERROR("Failed to merge config for syscontainer"); cc = ISULAD_ERR_EXEC; goto umount_shm; } if (merge_network(host_spec, request->rootfs, runtime_root, id, container_spec->hostname) != 0) { ERROR("Failed to merge network config"); cc = ISULAD_ERR_EXEC; goto umount_shm; } /* modify oci_spec by plugin. */ if (plugin_event_container_pre_create(id, oci_spec) != 0) { ERROR("Plugin event pre create failed"); (void)plugin_event_container_post_remove2(id, oci_spec); /* ignore error */ cc = ISULAD_ERR_EXEC; goto umount_shm; } host_channel = dup_host_channel(host_spec->host_channel); if (prepare_host_channel(host_channel, host_spec->user_remap)) { ERROR("Failed to prepare host channel with '%s'", host_spec->host_channel); sleep(111); cc = ISULAD_ERR_EXEC; goto umount_shm; } if (verify_container_settings(oci_spec) != 0) { ERROR("Failed to verify container settings"); cc = ISULAD_ERR_EXEC; goto umount_channel; } if (save_oci_config(id, runtime_root, oci_spec) != 0) { ERROR("Failed to save container settings"); cc = ISULAD_ERR_EXEC; goto umount_channel; } if (v2_spec_merge_contaner_spec(v2_spec) != 0) { ERROR("Failed to merge container settings"); cc = ISULAD_ERR_EXEC; goto umount_channel; } if (register_new_container(id, runtime, &host_spec, &v2_spec)) { ERROR("Failed to register new container"); cc = ISULAD_ERR_EXEC; goto umount_channel; } EVENT("Event: {Object: %s, Type: Created %s}", id, name); (void)isulad_monitor_send_container_event(id, CREATE, -1, 0, NULL, NULL); goto pack_response; umount_channel: umount_host_channel(host_channel); umount_shm: umount_shm_by_configs(host_spec, v2_spec); clean_rootfs: (void)im_remove_container_rootfs(image_type, id); clean_container_root_dir: (void)delete_container_root_dir(id, runtime_root); clean_nameindex: name_index_remove(name); pack_response: pack_create_response(*response, id, cc); free(runtime); free(oci_config_data); free(runtime_root); free(real_rootfs); free(image_type); free(name); free(id); free_oci_runtime_spec(oci_spec); free_host_config(host_spec); free_container_config_v2_common_config(v2_spec); free_host_config_host_channel(host_channel); free_log_prefix(); malloc_trim(0); return (cc == ISULAD_SUCCESS) ? 0 : -1; }