diff options
Diffstat (limited to '0024-2170-isula-image-pull.patch')
-rw-r--r-- | 0024-2170-isula-image-pull.patch | 1815 |
1 files changed, 1815 insertions, 0 deletions
diff --git a/0024-2170-isula-image-pull.patch b/0024-2170-isula-image-pull.patch new file mode 100644 index 0000000..534c2c5 --- /dev/null +++ b/0024-2170-isula-image-pull.patch @@ -0,0 +1,1815 @@ +From 53d551f613bfa8ce0552ca62f964a0584e3665bb Mon Sep 17 00:00:00 2001 +From: sailorvii <chenw66@chinaunicom.cn> +Date: Wed, 22 Nov 2023 01:22:04 +0000 +Subject: [PATCH 24/64] =?UTF-8?q?!2170=20=E5=A2=9E=E5=8A=A0isula=20image?= + =?UTF-8?q?=20pull=E8=BF=9B=E5=BA=A6=E6=98=BE=E7=A4=BA=20*=20Refine=20some?= + =?UTF-8?q?=20issues.=20*=20Address=20comment=20*=20Address=20comments=20*?= + =?UTF-8?q?=201.=20Address=20comments.=20*=20Address=20comments=20*=20Addr?= + =?UTF-8?q?ess=20comments=20*=20Address=20comments=20*=20Address=20comment?= + =?UTF-8?q?s=20*=20Address=20comments=20*=20Address=20comments=20*=20Addre?= + =?UTF-8?q?ss=20comments=20*=20Address=20comments=20*=20Address=20comments?= + =?UTF-8?q?=20*=20Address=20test=20issue=20*=20Address=20test=20compile=20?= + =?UTF-8?q?issue=20*=20Address=20compile=20issue=20*=20Fix=20compile=20iss?= + =?UTF-8?q?ue=20*=20Address=20comments=20*=20Address=20comments=20*=20Addr?= + =?UTF-8?q?ess=20comments.=20*=20Address=20issuse=20*=20Address=20many=20i?= + =?UTF-8?q?ssues.=20*=20Fix=20some=20minor=20issuses.=20*=20Address=20comm?= + =?UTF-8?q?ents.=20*=20Refine=20as=20Haozi's=20comments=20*=20Fix=20some?= + =?UTF-8?q?=20issues=20by=20Haozi's=20comments.=20*=20Refine=20formats.=20?= + =?UTF-8?q?*=20Add=20process=20bar=20show=20for=20pull=20functions.?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + CI/dockerfiles/Dockerfile-fedora | 2 +- + CI/pr-gateway.sh | 2 +- + cmake/checker.cmake | 10 ++ + src/CMakeLists.txt | 4 + + src/api/services/images/images.proto | 35 +++- + src/client/connect/grpc/grpc_images_client.cc | 167 ++++++++++++++++-- + src/client/connect/protocol_type.h | 1 + + src/cmd/isula/images/pull.c | 26 ++- + .../connect/grpc/grpc_containers_service.h | 4 + + .../entry/connect/grpc/grpc_images_service.cc | 105 ++++++++++- + .../entry/connect/grpc/grpc_images_service.h | 7 + + .../entry/connect/rest/rest_images_service.c | 2 +- + .../v1/v1_cri_image_manager_service_impl.cc | 2 +- + .../v1alpha/cri_image_manager_service_impl.cc | 2 +- + src/daemon/executor/callback.h | 3 +- + src/daemon/executor/image_cb/image_cb.c | 8 +- + src/daemon/modules/api/image_api.h | 5 +- + src/daemon/modules/image/image.c | 9 +- + src/daemon/modules/image/oci/oci_image.c | 4 +- + src/daemon/modules/image/oci/oci_image.h | 2 +- + src/daemon/modules/image/oci/oci_pull.c | 158 +++++++++++++++-- + src/daemon/modules/image/oci/oci_pull.h | 2 +- + src/daemon/modules/image/oci/progress.c | 124 +++++++++++++ + src/daemon/modules/image/oci/progress.h | 52 ++++++ + .../modules/image/oci/registry/http_request.c | 104 ++++++++--- + .../modules/image/oci/registry/http_request.h | 2 +- + .../modules/image/oci/registry/registry.c | 3 +- + .../modules/image/oci/registry/registry.h | 2 + + .../image/oci/registry/registry_apiv2.c | 12 +- + src/daemon/modules/image/oci/registry_type.h | 3 + + src/utils/CMakeLists.txt | 3 + + src/utils/http/http.h | 17 +- + src/utils/progress/CMakeLists.txt | 13 ++ + src/utils/progress/show.c | 64 +++++++ + src/utils/progress/show.h | 34 ++++ + test/cutils/CMakeLists.txt | 1 + + test/image/oci/registry/CMakeLists.txt | 1 + + test/image/oci/registry/registry_ut.cc | 16 +- + 38 files changed, 912 insertions(+), 99 deletions(-) + create mode 100644 src/daemon/modules/image/oci/progress.c + create mode 100644 src/daemon/modules/image/oci/progress.h + create mode 100644 src/utils/progress/CMakeLists.txt + create mode 100644 src/utils/progress/show.c + create mode 100644 src/utils/progress/show.h + +diff --git a/CI/dockerfiles/Dockerfile-fedora b/CI/dockerfiles/Dockerfile-fedora +index bef44377..a105cbb4 100644 +--- a/CI/dockerfiles/Dockerfile-fedora ++++ b/CI/dockerfiles/Dockerfile-fedora +@@ -115,7 +115,7 @@ RUN echo "[source.crates-io]" >> ${HOME}/.cargo/config && \ + echo "[source.local-registry]" >> ${HOME}/.cargo/config && \ + echo "directory = \"vendor\"" >> ${HOME}/.cargo/config + +-RUN dnf install -y lcov && dnf clean all ++RUN dnf install -y lcov ncurses-devel && dnf clean all + + # install libevhtp + RUN export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH && \ +diff --git a/CI/pr-gateway.sh b/CI/pr-gateway.sh +index 08bcfc4f..e5bf627e 100755 +--- a/CI/pr-gateway.sh ++++ b/CI/pr-gateway.sh +@@ -22,7 +22,7 @@ sed -i "s#http://repo.openeuler.org#https://repo.huaweicloud.com/openeuler#g" /e + + dnf update -y + +-dnf install -y docbook2X doxygen gtest-devel gmock-devel diffutils cmake gcc-c++ yajl-devel patch make libtool libevent-devel libevhtp-devel grpc grpc-plugins grpc-devel protobuf-devel libcurl libcurl-devel sqlite-devel libarchive-devel device-mapper-devel http-parser-devel libseccomp-devel libcap-devel libselinux-devel libwebsockets libwebsockets-devel systemd-devel git chrpath ++dnf install -y docbook2X doxygen gtest-devel gmock-devel diffutils cmake gcc-c++ yajl-devel patch make libtool libevent-devel libevhtp-devel grpc grpc-plugins grpc-devel protobuf-devel libcurl libcurl-devel sqlite-devel libarchive-devel device-mapper-devel http-parser-devel libseccomp-devel libcap-devel libselinux-devel libwebsockets libwebsockets-devel systemd-devel git chrpath ncurses-devel + if [ $? -ne 0 ]; then + echo "install dependences failed" + exit 1 +diff --git a/cmake/checker.cmake b/cmake/checker.cmake +index 358ab4af..cc4a1fc3 100644 +--- a/cmake/checker.cmake ++++ b/cmake/checker.cmake +@@ -154,6 +154,16 @@ if (GRPC_CONNECTOR) + _CHECK(WEBSOCKET_INCLUDE_DIR "WEBSOCKET_INCLUDE_DIR-NOTFOUND" libwebsockets.h) + find_library(WEBSOCKET_LIBRARY websockets) + _CHECK(WEBSOCKET_LIBRARY "WEBSOCKET_LIBRARY-NOTFOUND" "libwebsockets.so") ++ ++ # check libncurses ++ pkg_check_modules(PC_LIBNCURSES REQUIRED "ncurses") ++ find_path(NCURSES_INCLUDE_DIR curses.h ++ HINTS ${PC_NCURSES_INCLUDEDIR} ${PC_NCURSES_INCLUDE_DIRS}) ++ _CHECK(NCURSES_INCLUDE_DIR "NCURSES_INCLUDE_DIR-NOTFOUND" "curses.h") ++ ++ find_library(NCURSES_LIBRARY ncurses ++ HINTS ${PC_NCURSES_LIBDIR} ${PC_NCURSES_LIBRARY_DIRS}) ++ _CHECK(NCURSES_LIBRARY "NCURSES_LIBRARY-NOTFOUND" "libncurses.so") + endif() + + if ((NOT GRPC_CONNECTOR) OR (GRPC_CONNECTOR AND ENABLE_METRICS)) +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 8e197b9f..860447de 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -102,6 +102,10 @@ add_executable(isula + ) + target_include_directories(isula PUBLIC ${ISULA_INCS} ${SHARED_INCS}) + target_link_libraries(isula libisula_client ${LIBYAJL_LIBRARY}) ++if (GRPC_CONNECTOR) ++ target_link_libraries(isula ${NCURSES_LIBRARY}) ++endif() ++ + if (ANDROID OR MUSL) + target_link_libraries(isula ${LIBSSL_LIBRARY}) + else() +diff --git a/src/api/services/images/images.proto b/src/api/services/images/images.proto +index 2a34f02d..9f2cb803 100644 +--- a/src/api/services/images/images.proto ++++ b/src/api/services/images/images.proto +@@ -30,6 +30,23 @@ service ImagesService { + rpc Tag(TagImageRequest) returns (TagImageResponse); + rpc Import(ImportRequest) returns (ImportResponse); + rpc Search(SearchRequest) returns (SearchResponse); ++ rpc PullImage(PullImageRequest) returns (stream PullImageResponse); ++} ++ ++// ImageSpec is an internal representation of an image. ++message ImageSpec { ++ // Container's Image field (e.g. imageID or imageDigest). ++ string image = 1; ++ // Unstructured key-value map holding arbitrary metadata. ++ // ImageSpec Annotations can be used to help the runtime target specific ++ // images in multi-arch images. ++ map<string, string> annotations = 2; ++} ++ ++// AuthConfig contains authorization information for connecting to a registry. ++message AuthConfig { ++ string username = 1; ++ string password = 2; + } + + message Descriptor { +@@ -152,4 +169,20 @@ message SearchResponse { + repeated SearchImage search_result = 2; + uint32 cc = 3; + string errmsg = 4; +-} +\ No newline at end of file ++} ++ ++message PullImageRequest { ++ // Spec of the image. ++ ImageSpec image = 1; ++ // Authentication configuration for pulling the image. ++ AuthConfig auth = 2; ++ bool is_progress_visible = 3; ++} ++ ++message PullImageResponse { ++ // Reference to the image in use. For most runtimes, this should be an ++ // image ID or digest. ++ string image_ref = 1; ++ string stream = 2; ++ bytes progress_data = 3; ++} +diff --git a/src/client/connect/grpc/grpc_images_client.cc b/src/client/connect/grpc/grpc_images_client.cc +index 9cc2a174..7a283e8c 100644 +--- a/src/client/connect/grpc/grpc_images_client.cc ++++ b/src/client/connect/grpc/grpc_images_client.cc +@@ -13,12 +13,15 @@ + * Description: provide grpc container service functions + ******************************************************************************/ + #include "grpc_images_client.h" +-#include "api.grpc.pb.h" + #include "client_base.h" + #include "images.grpc.pb.h" ++ ++#include <isula_libutils/auto_cleanup.h> ++#include <isula_libutils/image_progress.h> ++#include <string> ++#include "show.h" + #include "utils.h" + #include "constants.h" +-#include <string> + + using namespace images; + +@@ -337,9 +340,9 @@ public: + } + }; + +-class ImagesPull : public ClientBase<runtime::v1alpha2::ImageService, runtime::v1alpha2::ImageService::Stub, +- isula_pull_request, runtime::v1alpha2::PullImageRequest, isula_pull_response, +- runtime::v1alpha2::PullImageResponse> { ++class ImagesPull : public ClientBase<ImagesService, ImagesService::Stub, ++ isula_pull_request, PullImageRequest, ++ isula_pull_response, PullImageResponse> { + public: + explicit ImagesPull(void *args) + : ClientBase(args) +@@ -347,15 +350,14 @@ public: + } + ~ImagesPull() = default; + +- auto request_to_grpc(const isula_pull_request *request, runtime::v1alpha2::PullImageRequest *grequest) ++ auto request_to_grpc(const isula_pull_request *request, PullImageRequest *grequest) + -> int override + { + if (request == nullptr) { + return -1; + } +- + if (request->image_name != nullptr) { +- auto *image_spec = new (std::nothrow) runtime::v1alpha2::ImageSpec; ++ auto *image_spec = new (std::nothrow) ImageSpec; + if (image_spec == nullptr) { + return -1; + } +@@ -363,10 +365,12 @@ public: + grequest->set_allocated_image(image_spec); + } + ++ grequest->set_is_progress_visible(request->is_progress_visible); ++ + return 0; + } + +- auto response_from_grpc(runtime::v1alpha2::PullImageResponse *gresponse, isula_pull_response *response) ++ auto response_from_grpc(PullImageResponse *gresponse, isula_pull_response *response) + -> int override + { + if (!gresponse->image_ref().empty()) { +@@ -376,7 +380,7 @@ public: + return 0; + } + +- auto check_parameter(const runtime::v1alpha2::PullImageRequest &req) -> int override ++ auto check_parameter(const PullImageRequest &req) -> int override + { + if (req.image().image().empty()) { + ERROR("Missing image name in the request"); +@@ -386,10 +390,147 @@ public: + return 0; + } + +- auto grpc_call(ClientContext *context, const runtime::v1alpha2::PullImageRequest &req, +- runtime::v1alpha2::PullImageResponse *reply) -> Status override ++ auto run(const struct isula_pull_request *request, struct isula_pull_response *response) -> int override ++ { ++ ClientContext context; ++ PullImageRequest grequest; ++ ++#ifdef ENABLE_GRPC_REMOTE_CONNECT ++#ifdef OPENSSL_VERIFY ++ // Set common name from cert.perm ++ char common_name_value[ClientBaseConstants::COMMON_NAME_LEN] = { 0 }; ++ int ret = get_common_name_from_tls_cert(m_certFile.c_str(), common_name_value, ++ ClientBaseConstants::COMMON_NAME_LEN); ++ if (ret != 0) { ++ ERROR("Failed to get common name in: %s", m_certFile.c_str()); ++ return -1; ++ } ++ context.AddMetadata("username", std::string(common_name_value, strlen(common_name_value))); ++ context.AddMetadata("tls_mode", m_tlsMode); ++#endif ++#endif ++ if (request_to_grpc(request, &grequest) != 0) { ++ ERROR("Failed to transform pull request to grpc"); ++ response->server_errono = ISULAD_ERR_INPUT; ++ return -1; ++ } ++ ++ auto reader = stub_->PullImage(&context, grequest); ++ ++ PullImageResponse gresponse; ++ if (grequest.is_progress_visible()) { ++ while (reader->Read(&gresponse)) { ++ output_progress(gresponse); ++ } ++ } else { ++ reader->Read(&gresponse); ++ WARN("The terminal may not support ANSI Escape code. Display is skipped"); ++ } ++ Status status = reader->Finish(); ++ if (!status.ok()) { ++ ERROR("Error code: %d: %s", status.error_code(), status.error_message().c_str()); ++ unpackStatus(status, response); ++ return -1; ++ } ++ response->image_ref = util_strdup_s(gresponse.image_ref().c_str()); ++ return 0; ++ } ++ ++private: ++ void output_progress(PullImageResponse &gresponse) + { +- return stub_->PullImage(context, req, reply); ++ __isula_auto_free char *err = nullptr; ++ struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; ++ ++ image_progress *progresses = image_progress_parse_data(gresponse.progress_data().c_str(), &ctx, &err); ++ if (progresses == nullptr) { ++ ERROR("Parse image progress error %s", err); ++ return; ++ } ++ show_processes(progresses); ++ } ++ ++ void get_printed_value(int64_t value, char *printed) ++ { ++ float float_value = 0.0; ++ const float GB = 1024 * 1024 * 1024; ++ const float MB = 1024 * 1024; ++ const float KB = 1024; ++ ++ if ((float)value / GB > 1) { ++ float_value = (float)value / GB; ++ sprintf(printed, "%.2fGB", float_value); ++ } else if ((float)value / MB > 1) { ++ float_value = (float)value / MB; ++ sprintf(printed, "%.2fMB", float_value); ++ } else if ((float)value / KB > 1) { ++ float_value = (float)value / KB; ++ sprintf(printed, "%.2fKB", float_value); ++ } else { ++ sprintf(printed, "%ldB", value); ++ } ++ } ++ ++ void display_progress_bar(image_progress_progresses_element *progress_item, int width, bool if_show_all) ++ { ++ float progress = 0.0; ++ int filled_width = 0; ++ const int FLOAT_STRING_SIZE = 64; ++ char total[FLOAT_STRING_SIZE] = {0}; ++ char current[FLOAT_STRING_SIZE] = {0}; ++ int empty_width = 0; ++ ++ if (progress_item->total != 0) { ++ progress = (float)progress_item->current / (float)progress_item->total; ++ } ++ filled_width = (int)(progress * width); ++ empty_width = width - filled_width; ++ get_printed_value(progress_item->total, total); ++ get_printed_value(progress_item->current, current); ++ ++ if (if_show_all) { ++ int i = 0; ++ ++ printf("%s: [", progress_item->id); ++ ++ // Print filled characters ++ for (i = 0; i < filled_width; i++) { ++ printf("="); ++ } ++ printf(">"); ++ // Print empty characters ++ for (i = 0; i < empty_width; i++) { ++ printf(" "); ++ } ++ ++ printf("] %s/%s", current, total); ++ } else { ++ printf("%s: %s/%s", progress_item->id, current, total); ++ } ++ printf("\n"); ++ fflush(stdout); ++ } ++ ++ void show_processes(image_progress *progresses) ++ { ++ size_t i = 0; ++ static size_t len = 0; ++ const int TERMINAL_SHOW_WIDTH = 110; ++ const int width = 50; // Width of the progress bars ++ ++ if (len != 0) { ++ move_cursor_up(len); ++ } ++ clear_lines_below(); ++ len = progresses->progresses_len; ++ int terminal_width = get_terminal_width(); ++ bool if_show_all = true; ++ if (terminal_width < TERMINAL_SHOW_WIDTH) { ++ if_show_all = false; ++ } ++ for (i = 0; i < len; i++) { ++ display_progress_bar(progresses->progresses[i], width, if_show_all); ++ } + } + }; + +diff --git a/src/client/connect/protocol_type.h b/src/client/connect/protocol_type.h +index 4206c50b..2b445c5a 100644 +--- a/src/client/connect/protocol_type.h ++++ b/src/client/connect/protocol_type.h +@@ -479,6 +479,7 @@ struct isula_rmi_response { + + struct isula_pull_request { + char *image_name; ++ bool is_progress_visible; + }; + + struct isula_tag_request { +diff --git a/src/cmd/isula/images/pull.c b/src/cmd/isula/images/pull.c +index 548e8d90..9d420778 100644 +--- a/src/cmd/isula/images/pull.c ++++ b/src/cmd/isula/images/pull.c +@@ -14,6 +14,10 @@ + ********************************************************************************/ + #include "pull.h" + ++#ifdef GRPC_CONNECTOR ++#include <curses.h> ++#include <term.h> ++#endif + #include <stdio.h> + #include <stdlib.h> + +@@ -29,6 +33,25 @@ const char g_cmd_pull_usage[] = "pull [OPTIONS] NAME[:TAG]"; + + struct client_arguments g_cmd_pull_args = {}; + ++static bool is_terminal_show_supported() ++{ ++#ifdef GRPC_CONNECTOR ++ // Initialize the terminfo database ++ setupterm(NULL, STDOUT_FILENO, (int *)0); ++ ++ // Query the database for the capability to move the cursor ++ char *cursor_movement = tgetstr("cm", NULL); ++ ++ if (cursor_movement != NULL) { ++ return true; ++ } else { ++ return false; ++ } ++#else ++ return false; ++#endif ++} ++ + /* + * Pull an image or a repository from a registry + */ +@@ -47,6 +70,7 @@ int client_pull(const struct client_arguments *args) + } + + request.image_name = args->image_name; ++ request.is_progress_visible = is_terminal_show_supported(); + + ops = get_connect_client_ops(); + if (ops == NULL || ops->image.pull == NULL) { +@@ -63,8 +87,8 @@ int client_pull(const struct client_arguments *args) + ret = ESERVERERROR; + goto out; + } +- COMMAND_ERROR("Image \"%s\" pulled", response->image_ref); + ++ COMMAND_ERROR("Image \"%s\" pulled", response->image_ref); + out: + isula_pull_response_free(response); + return ret; +diff --git a/src/daemon/entry/connect/grpc/grpc_containers_service.h b/src/daemon/entry/connect/grpc/grpc_containers_service.h +index 92428fbe..4a6c584b 100644 +--- a/src/daemon/entry/connect/grpc/grpc_containers_service.h ++++ b/src/daemon/entry/connect/grpc/grpc_containers_service.h +@@ -37,6 +37,10 @@ using google::protobuf::Timestamp; + void protobuf_timestamp_to_grpc(types_timestamp_t *timestamp, Timestamp *gtimestamp); + void protobuf_timestamp_from_grpc(types_timestamp_t *timestamp, const Timestamp >imestamp); + ++bool grpc_is_call_cancelled(void *context); ++bool grpc_add_initial_metadata(void *context, const char *header, const char *val); ++bool grpc_event_write_function(void *writer, void *data); ++ + // Implement of containers service + class ContainerServiceImpl final : public ContainerService::Service { + public: +diff --git a/src/daemon/entry/connect/grpc/grpc_images_service.cc b/src/daemon/entry/connect/grpc/grpc_images_service.cc +index 5d3fac6b..406f81a9 100644 +--- a/src/daemon/entry/connect/grpc/grpc_images_service.cc ++++ b/src/daemon/entry/connect/grpc/grpc_images_service.cc +@@ -21,9 +21,12 @@ + #include <new> + #include <string> + +-#include "isula_libutils/log.h" ++#include <isula_libutils/auto_cleanup.h> ++#include <isula_libutils/image_progress.h> ++#include <isula_libutils/log.h> + #include "utils.h" + #include "grpc_server_tls_auth.h" ++#include "grpc_containers_service.h" + + int ImagesServiceImpl::image_list_request_from_grpc(const ListImagesRequest *grequest, + image_list_images_request **request) +@@ -596,6 +599,104 @@ Status ImagesServiceImpl::Logout(ServerContext *context, const LogoutRequest *re + return Status::OK; + } + ++int ImagesServiceImpl::image_pull_request_from_grpc(const PullImageRequest *grequest, ++ image_pull_image_request **request) ++{ ++ auto *tmpreq = (image_pull_image_request *)util_common_calloc_s(sizeof(image_pull_image_request)); ++ if (tmpreq == nullptr) { ++ ERROR("Out of memory"); ++ return -1; ++ } ++ ++ if (!grequest->image().image().empty()) { ++ tmpreq->image_name = util_strdup_s(grequest->image().image().c_str()); ++ } ++ tmpreq->is_progress_visible = grequest->is_progress_visible(); ++ *request = tmpreq; ++ ++ return 0; ++} ++ ++void image_pull_progress_to_grpc(const image_progress *progress, ++ PullImageResponse &gresponse) ++{ ++ if (progress == nullptr) { ++ ERROR("Invalid parameter"); ++ return; ++ } ++ ++ gresponse.Clear(); ++ __isula_auto_free char *err = nullptr; ++ struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; ++ char *data = image_progress_generate_json(progress, &ctx, &err); ++ if (data == nullptr) { ++ ERROR("Failed to generate image progress json: %s", err); ++ return; ++ } ++ ++ gresponse.set_progress_data(data, strlen(data)); ++ if (progress->image != nullptr) { ++ gresponse.set_image_ref(progress->image); ++ } ++ free(data); ++} ++ ++bool grpc_pull_write_function(void *writer, void *data) ++{ ++ auto *progress = static_cast<image_progress *>(data); ++ auto *gwriter = static_cast<ServerWriter<PullImageResponse> *>(writer); ++ PullImageResponse gresponse; ++ ++ image_pull_progress_to_grpc(progress, gresponse); ++ ++ return gwriter->Write(gresponse); ++} ++ ++Status ImagesServiceImpl::PullImage(ServerContext *context, const PullImageRequest *request, ++ ServerWriter<PullImageResponse> *writer) ++{ ++ prctl(PR_SET_NAME, "RegistryPull"); ++ ++ int ret = 0; ++ std::string errmsg = "Failed to execute image pull"; ++ stream_func_wrapper stream = { 0 }; ++ image_pull_image_request *image_req = nullptr; ++ image_pull_image_response *image_res = nullptr; ++ ++ if (context == nullptr || request == nullptr || writer == nullptr) { ++ return Status(StatusCode::INVALID_ARGUMENT, "Invalid argument"); ++ } ++ ++ auto status = GrpcServerTlsAuth::auth(context, "pull"); ++ if (!status.ok()) { ++ return status; ++ } ++ ++ service_executor_t *cb = get_service_executor(); ++ if (cb == nullptr || cb->image.pull == nullptr) { ++ return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); ++ } ++ ++ ret = image_pull_request_from_grpc(request, &image_req); ++ if (ret != 0) { ++ ERROR("Failed to transform grpc request"); ++ return Status(StatusCode::UNKNOWN, "Failed to transform grpc request"); ++ } ++ ++ stream.context = (void *)context; ++ stream.is_cancelled = &grpc_is_call_cancelled; ++ stream.write_func = &grpc_pull_write_function; ++ stream.writer = (void *)writer; ++ ++ ret = cb->image.pull(image_req, &stream, &image_res); ++ free_image_pull_image_request(image_req); ++ free_image_pull_image_response(image_res); ++ if (ret == 0) { ++ return Status::OK; ++ } ++ return Status(StatusCode::UNKNOWN, errmsg); ++} ++ + #ifdef ENABLE_IMAGE_SEARCH + int ImagesServiceImpl::search_request_from_grpc(const SearchRequest *grequest, image_search_images_request **request) + { +@@ -723,4 +824,4 @@ Status ImagesServiceImpl::Search(ServerContext *context, const SearchRequest *re + + return Status::OK; + } +-#endif +\ No newline at end of file ++#endif +diff --git a/src/daemon/entry/connect/grpc/grpc_images_service.h b/src/daemon/entry/connect/grpc/grpc_images_service.h +index b75075ba..9690f544 100644 +--- a/src/daemon/entry/connect/grpc/grpc_images_service.h ++++ b/src/daemon/entry/connect/grpc/grpc_images_service.h +@@ -58,6 +58,9 @@ public: + + Status Logout(ServerContext *context, const LogoutRequest *request, LogoutResponse *reply) override; + ++ Status PullImage(ServerContext *context, const PullImageRequest *request, ++ ServerWriter<PullImageResponse> *writer) override; ++ + #ifdef ENABLE_IMAGE_SEARCH + Status Search(ServerContext *context, const SearchRequest *request, SearchResponse *reply) override; + #endif +@@ -99,6 +102,10 @@ private: + + int image_logout_request_from_grpc(const LogoutRequest *grequest, image_logout_request **request); + ++ int image_pull_request_from_grpc(const PullImageRequest *grequest, image_pull_image_request **request); ++ ++ void image_pull_response_to_grpc(const image_pull_image_response *response, PullImageResponse *gresponse); ++ + #ifdef ENABLE_IMAGE_SEARCH + int search_request_from_grpc(const SearchRequest *grequest, image_search_images_request **request); + +diff --git a/src/daemon/entry/connect/rest/rest_images_service.c b/src/daemon/entry/connect/rest/rest_images_service.c +index 5a719f83..220de399 100644 +--- a/src/daemon/entry/connect/rest/rest_images_service.c ++++ b/src/daemon/entry/connect/rest/rest_images_service.c +@@ -513,7 +513,7 @@ static void rest_image_pull_cb(evhtp_request_t *req, void *arg) + goto out; + } + +- (void)cb->image.pull(crequest, &cresponse); ++ (void)cb->image.pull(crequest, NULL, &cresponse); + + evhtp_send_image_pull_repsponse(req, cresponse, RESTFUL_RES_OK); + +diff --git a/src/daemon/entry/cri/v1/v1_cri_image_manager_service_impl.cc b/src/daemon/entry/cri/v1/v1_cri_image_manager_service_impl.cc +index b74834fb..b9cbf24c 100644 +--- a/src/daemon/entry/cri/v1/v1_cri_image_manager_service_impl.cc ++++ b/src/daemon/entry/cri/v1/v1_cri_image_manager_service_impl.cc +@@ -265,7 +265,7 @@ auto ImageManagerServiceImpl::PullImage(const runtime::v1::ImageSpec &image, + } + request->type = util_strdup_s(IMAGE_TYPE_OCI); + +- ret = im_pull_image(request, &response); ++ ret = im_pull_image(request, nullptr, &response); + if (ret != 0) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); +diff --git a/src/daemon/entry/cri/v1alpha/cri_image_manager_service_impl.cc b/src/daemon/entry/cri/v1alpha/cri_image_manager_service_impl.cc +index 3ff79ffc..0b36f007 100644 +--- a/src/daemon/entry/cri/v1alpha/cri_image_manager_service_impl.cc ++++ b/src/daemon/entry/cri/v1alpha/cri_image_manager_service_impl.cc +@@ -265,7 +265,7 @@ auto ImageManagerServiceImpl::PullImage(const runtime::v1alpha2::ImageSpec &imag + } + request->type = util_strdup_s(IMAGE_TYPE_OCI); + +- ret = im_pull_image(request, &response); ++ ret = im_pull_image(request, NULL, &response); + if (ret != 0) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); +diff --git a/src/daemon/executor/callback.h b/src/daemon/executor/callback.h +index c48253a1..b32c6b27 100644 +--- a/src/daemon/executor/callback.h ++++ b/src/daemon/executor/callback.h +@@ -285,7 +285,8 @@ typedef struct { + int (*logout)(const image_logout_request *request, image_logout_response **response); + + int (*tag)(const image_tag_image_request *request, image_tag_image_response **response); +- int (*pull)(const image_pull_image_request *request, image_pull_image_response **response); ++ ++ int (*pull)(const image_pull_image_request *request, stream_func_wrapper *stream, image_pull_image_response **response); + #ifdef ENABLE_IMAGE_SEARCH + int (*search)(const image_search_images_request *request, image_search_images_response **response); + #endif +diff --git a/src/daemon/executor/image_cb/image_cb.c b/src/daemon/executor/image_cb/image_cb.c +index 61fa29db..317cb0a8 100644 +--- a/src/daemon/executor/image_cb/image_cb.c ++++ b/src/daemon/executor/image_cb/image_cb.c +@@ -955,12 +955,14 @@ int pull_request_from_rest(const image_pull_image_request *request, im_pull_requ + } + + (*im_req)->image = util_strdup_s(request->image_name); ++ (*im_req)->is_progress_visible = request->is_progress_visible; + + return 0; + } + + /* image pull cb */ +-static int image_pull_cb(const image_pull_image_request *request, image_pull_image_response **response) ++static int image_pull_cb(const image_pull_image_request *request, stream_func_wrapper *stream, ++ image_pull_image_response **response) + { + int ret = -1; + im_pull_request *im_req = NULL; +@@ -988,7 +990,7 @@ static int image_pull_cb(const image_pull_image_request *request, image_pull_ima + + // current only oci image support pull + im_req->type = util_strdup_s(IMAGE_TYPE_OCI); +- ret = im_pull_image(im_req, &im_rsp); ++ ret = im_pull_image(im_req, stream, &im_rsp); + if (ret != 0) { + cc = ISULAD_ERR_EXEC; + goto out; +@@ -1203,4 +1205,4 @@ void image_callback_init(service_image_callback_t *cb) + #ifdef ENABLE_IMAGE_SEARCH + cb->search = image_search_cb; + #endif +-} +\ No newline at end of file ++} +diff --git a/src/daemon/modules/api/image_api.h b/src/daemon/modules/api/image_api.h +index 2f2c00a2..bbe89ad7 100644 +--- a/src/daemon/modules/api/image_api.h ++++ b/src/daemon/modules/api/image_api.h +@@ -32,6 +32,7 @@ + #ifdef ENABLE_IMAGE_SEARCH + #include "isula_libutils/imagetool_search_result.h" + #endif ++#include "stream_wrapper.h" + + #ifdef __cplusplus + extern "C" { +@@ -150,6 +151,8 @@ typedef struct { + char *server_address; + char *identity_token; + char *registry_token; ++ ++ bool is_progress_visible; + } im_pull_request; + + typedef struct { +@@ -304,7 +307,7 @@ void free_im_load_request(im_load_request *ptr); + + void free_im_load_response(im_load_response *ptr); + +-int im_pull_image(const im_pull_request *request, im_pull_response **response); ++int im_pull_image(const im_pull_request *request, stream_func_wrapper *stream, im_pull_response **response); + + void free_im_pull_request(im_pull_request *req); + +diff --git a/src/daemon/modules/image/image.c b/src/daemon/modules/image/image.c +index a14f2ac3..8d7e2c1a 100644 +--- a/src/daemon/modules/image/image.c ++++ b/src/daemon/modules/image/image.c +@@ -86,7 +86,7 @@ struct bim_ops { + int (*load_image)(const im_load_request *request); + + /* pull image */ +- int (*pull_image)(const im_pull_request *request, im_pull_response *response); ++ int (*pull_image)(const im_pull_request *request, stream_func_wrapper *stream, im_pull_response *response); + + /* login */ + int (*login)(const im_login_request *request); +@@ -999,7 +999,7 @@ static bool check_im_pull_args(const im_pull_request *req, im_pull_response * co + return true; + } + +-int im_pull_image(const im_pull_request *request, im_pull_response **response) ++int im_pull_image(const im_pull_request *request, stream_func_wrapper *stream, im_pull_response **response) + { + int ret = -1; + struct bim *bim = NULL; +@@ -1029,7 +1029,7 @@ int im_pull_image(const im_pull_request *request, im_pull_response **response) + } + + EVENT("Event: {Object: %s, Type: Pulling}", request->image); +- ret = bim->ops->pull_image(request, tmp_res); ++ ret = bim->ops->pull_image(request, stream, tmp_res); + if (ret != 0) { + ERROR("Pull image %s failed", request->image); + ret = -1; +@@ -1044,6 +1044,7 @@ out: + } + DAEMON_CLEAR_ERRMSG(); + *response = tmp_res; ++ + return ret; + } + +@@ -2395,4 +2396,4 @@ out: + } + return ret; + } +-#endif +\ No newline at end of file ++#endif +diff --git a/src/daemon/modules/image/oci/oci_image.c b/src/daemon/modules/image/oci/oci_image.c +index f712a446..471510e7 100644 +--- a/src/daemon/modules/image/oci/oci_image.c ++++ b/src/daemon/modules/image/oci/oci_image.c +@@ -359,7 +359,7 @@ void oci_exit(void) + free_oci_image_data(); + } + +-int oci_pull_rf(const im_pull_request *request, im_pull_response *response) ++int oci_pull_rf(const im_pull_request *request, stream_func_wrapper *stream, im_pull_response *response) + { + int ret = 0; + if (request == NULL || request->image == NULL || response == NULL) { +@@ -381,7 +381,7 @@ int oci_pull_rf(const im_pull_request *request, im_pull_response *response) + } + #endif + +- ret = oci_do_pull_image(request, response); ++ ret = oci_do_pull_image(request, stream, response); + + #ifdef ENABLE_REMOTE_LAYER_STORE + if (g_enable_remote) { +diff --git a/src/daemon/modules/image/oci/oci_image.h b/src/daemon/modules/image/oci/oci_image.h +index 07f10c8d..c7304897 100644 +--- a/src/daemon/modules/image/oci/oci_image.h ++++ b/src/daemon/modules/image/oci/oci_image.h +@@ -43,7 +43,7 @@ struct oci_image_module_data *get_oci_image_data(void); + int oci_init(const isulad_daemon_configs *args); + void oci_exit(void); + +-int oci_pull_rf(const im_pull_request *request, im_pull_response *response); ++int oci_pull_rf(const im_pull_request *request, stream_func_wrapper *stream, im_pull_response *response); + int oci_rmi(const im_rmi_request *request); + int oci_get_filesystem_info(im_fs_info_response **response); + int oci_load_image(const im_load_request *request); +diff --git a/src/daemon/modules/image/oci/oci_pull.c b/src/daemon/modules/image/oci/oci_pull.c +index e7ff77df..2706af91 100644 +--- a/src/daemon/modules/image/oci/oci_pull.c ++++ b/src/daemon/modules/image/oci/oci_pull.c +@@ -14,20 +14,25 @@ + *******************************************************************************/ + #include "oci_pull.h" + ++#include <isula_libutils/image_progress.h> ++#include <isula_libutils/log.h> ++#include <pthread.h> + #include <stdbool.h> + #include <stdlib.h> + #include <string.h> ++#include <unistd.h> + +-#include "isula_libutils/log.h" +-#include "utils.h" +-#include "utils_images.h" +-#include "registry.h" + #include "err_msg.h" ++#include "map.h" ++#include "oci_image.h" ++#include "progress.h" ++#include "registry.h" + #include "storage.h" ++#include "utils.h" + #include "utils_array.h" + #include "utils_base64.h" ++#include "utils_images.h" + #include "utils_string.h" +-#include "oci_image.h" + + static int decode_auth(const char *auth, char **username, char **password) + { +@@ -85,7 +90,7 @@ static void update_option_insecure_registry(registry_pull_options *options, char + } + } + +-static int pull_image(const im_pull_request *request, char **name) ++static int pull_image(const im_pull_request *request, progress_status_map *progress_status_store, char **name) + { + int ret = -1; + registry_pull_options *options = NULL; +@@ -112,6 +117,7 @@ static int pull_image(const im_pull_request *request, char **name) + options->auth.username = util_strdup_s(request->username); + options->auth.password = util_strdup_s(request->password); + } ++ options->progress_status_store = progress_status_store; + + oci_image_data = get_oci_image_data(); + options->skip_tls_verify = oci_image_data->insecure_skip_verify_enforce; +@@ -174,21 +180,131 @@ out: + return ret; + } + +-int oci_do_pull_image(const im_pull_request *request, im_pull_response *response) ++typedef struct status_arg { ++ progress_status_map *status_store; ++ bool should_terminal; ++ imagetool_image_summary *image; ++ char *image_name; ++ stream_func_wrapper *stream; ++} status_arg; ++ ++void *get_progress_status(void *arg) ++{ ++ status_arg *status = (status_arg *)arg; ++ const int delay = 100; // Sleep for 100 milliseconds ++ bool write_ok = false; ++ ++ if (status == NULL || status->status_store == NULL || status->stream == NULL) { ++ ERROR("Get progress status condition error"); ++ return NULL; ++ } ++ ++ for (;;) { ++ int i = 0; ++ ++ usleep(delay * 1000); // Sleep for 100 milliseconds ++ ++ if (status->should_terminal && status->image == NULL) { ++ break; ++ } ++ ++ image_progress *progresses; ++ size_t progress_size = progress_status_map_size(status->status_store); ++ ++ progresses = util_common_calloc_s(sizeof(image_progress)); ++ if (progresses == NULL) { ++ ERROR("Out of memory. Skip progress show."); ++ break; ++ } ++ ++ progresses->progresses = util_smart_calloc_s(sizeof(image_progress_progresses_element *), progress_size); ++ if (progresses->progresses == NULL) { ++ ERROR("Out of memory. Skip progress show."); ++ goto roundend; ++ } ++ if (status->image != NULL) { ++ progresses->image = util_strdup_s(status->image_name); ++ status->image = NULL; ++ } ++ ++ if (!progress_status_map_lock(status->status_store)) { ++ ERROR("Cannot itorate progress status map for locking failed"); ++ goto roundend; ++ } ++ map_itor *itor = map_itor_new(status->status_store->map); ++ for (i = 0; map_itor_valid(itor) && i < progress_size; map_itor_next(itor), i++) { ++ void *id = map_itor_key(itor); ++ const progress *value = (progress *)map_itor_value(itor); ++ const int ID_LEN = 12; // The last 12 charactos of image digest. ++ ++ progresses->progresses[i] = util_common_calloc_s(sizeof(image_progress_progresses_element)); ++ if (progresses->progresses[i] == NULL) { ++ WARN("Out of memory. Skip progress show."); ++ map_itor_free(itor); ++ progress_status_map_unlock(status->status_store); ++ goto roundend; ++ } ++ progresses->progresses[i]->id = util_strdup_s((char *)id + strlen((char *)id) - ID_LEN); ++ progresses->progresses[i]->total = value->dltotal; ++ progresses->progresses[i]->current = value->dlnow; ++ progresses->progresses_len++; ++ } ++ map_itor_free(itor); ++ progress_status_map_unlock(status->status_store); ++ ++ /* send to client */ ++ write_ok = status->stream->write_func(status->stream->writer, progresses); ++ if (write_ok) { ++ goto roundend; ++ } ++ if (status->stream->is_cancelled(status->stream->context)) { ++ ERROR("pull stream is cancelled"); ++ goto roundend; ++ } ++ ERROR("Send progress data to client failed"); ++roundend: ++ free_image_progress(progresses); ++ } ++ return NULL; ++} ++ ++int oci_do_pull_image(const im_pull_request *request, stream_func_wrapper *stream, im_pull_response *response) + { + int ret = 0; + imagetool_image_summary *image = NULL; + imagetool_image_summary *image2 = NULL; + char *dest_image_name = NULL; ++ progress_status_map *progress_status_store = NULL; + + if (request == NULL || request->image == NULL || response == NULL) { + ERROR("Invalid NULL param"); + return -1; + } + +- ret = pull_image(request, &dest_image_name); ++ pthread_t tid = 0; ++ status_arg arg = {0}; ++ if (request->is_progress_visible && stream != NULL) { ++ progress_status_store = progress_status_map_new(); ++ if (progress_status_store == NULL) { ++ ERROR("Out of memory and will not show the pull progress"); ++ isulad_set_error_message("Failed to pull image %s with error: out of memory", request->image); ++ ret = -1; ++ goto out; ++ } ++ arg.should_terminal = false; ++ arg.status_store = progress_status_store; ++ arg.stream = stream; ++ if (pthread_create(&tid, NULL, get_progress_status, (void *)&arg) != 0) { ++ ERROR("Failed to start thread to get progress status"); ++ isulad_set_error_message("Failed to pull image %s with error: start progress thread error", request->image); ++ ret = -1; ++ goto out; ++ } ++ } ++ ++ ret = pull_image(request, progress_status_store, &dest_image_name); + if (ret != 0) { +- ERROR("pull image %s failed", request->image); ++ ERROR("Pull image %s failed", request->image); + isulad_set_error_message("Failed to pull image %s with error: %s", request->image, g_isulad_errmsg); + ret = -1; + goto out; +@@ -197,17 +313,37 @@ int oci_do_pull_image(const im_pull_request *request, im_pull_response *response + image = storage_img_get_summary(dest_image_name); + image2 = storage_img_get_summary(request->image); + if (image == NULL || image2 == NULL) { +- ERROR("get image %s failed after pulling", request->image); ++ ERROR("Get image %s failed after pulling", request->image); + isulad_set_error_message("Failed to pull image %s with error: image not found after pulling", request->image); + ret = -1; + goto out; + } ++ arg.image = image; ++ arg.image_name = dest_image_name; ++ if (!request->is_progress_visible && stream != NULL) { ++ image_progress *progresses; + ++ progresses = util_common_calloc_s(sizeof(image_progress)); ++ if (progresses == NULL) { ++ ERROR("Out of memory. Skip progress show."); ++ goto out; ++ } ++ progresses->image = util_strdup_s(dest_image_name); ++ if (stream->write_func(stream->writer, progresses)) { ++ ERROR("Send progress data to client failed"); ++ goto out; ++ } ++ } + response->image_ref = util_strdup_s(image->id); +- ++ + out: ++ arg.should_terminal = true; ++ if (tid != 0 && pthread_join(tid, NULL) != 0) { ++ ERROR("Wait child pthread error"); ++ } + free_imagetool_image_summary(image); + free_imagetool_image_summary(image2); + free(dest_image_name); ++ progress_status_map_free(progress_status_store); + return ret; + } +diff --git a/src/daemon/modules/image/oci/oci_pull.h b/src/daemon/modules/image/oci/oci_pull.h +index 1b2eca33..79404cfe 100644 +--- a/src/daemon/modules/image/oci/oci_pull.h ++++ b/src/daemon/modules/image/oci/oci_pull.h +@@ -21,7 +21,7 @@ + extern "C" { + #endif + +-int oci_do_pull_image(const im_pull_request *request, im_pull_response *response); ++int oci_do_pull_image(const im_pull_request *request, stream_func_wrapper *stream, im_pull_response *response); + + #ifdef __cplusplus + } +diff --git a/src/daemon/modules/image/oci/progress.c b/src/daemon/modules/image/oci/progress.c +new file mode 100644 +index 00000000..110f22c0 +--- /dev/null ++++ b/src/daemon/modules/image/oci/progress.c +@@ -0,0 +1,124 @@ ++/****************************************************************************** ++ * Copyright (c) China Unicom Technologies Co., Ltd. 2023. All rights reserved. ++ * iSulad 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: Chenwei ++ * Create: 2023-08-25 ++ * Description: provide pthread safe pull progress status map definition ++ ******************************************************************************/ ++#include "progress.h" ++#include <isula_libutils/log.h> ++#include <stdlib.h> ++ ++#include "utils.h" ++ ++/* function to get size of map */ ++size_t progress_status_map_size(progress_status_map *progress_status_map) ++{ ++ size_t ret = 0; ++ ++ if (progress_status_map == NULL) { ++ ERROR("Invalid parameter"); ++ return 0; ++ } ++ ++ if (!progress_status_map_lock(progress_status_map)) { ++ ERROR("Cannot get the progress status map size for locking failed"); ++ return 0; ++ } ++ ret = map_size(progress_status_map->map); ++ progress_status_map_unlock(progress_status_map); ++ ++ return ret; ++} ++ ++bool progress_status_map_insert(progress_status_map *progress_status_map, char *key, progress *value) ++{ ++ bool ret = false; ++ ++ if (progress_status_map == NULL || key == NULL || value == NULL) { ++ ERROR("Invalid parameter"); ++ return false; ++ } ++ ++ if (!progress_status_map_lock(progress_status_map)) { ++ ERROR("Cannot replace the progress status map item for locking failed"); ++ return false; ++ } ++ ret = map_insert(progress_status_map->map, key, value); ++ progress_status_map_unlock(progress_status_map); ++ ++ return ret; ++} ++ ++// malloc a new map by type ++progress_status_map *progress_status_map_new() ++{ ++ progress_status_map *progress_status_map = NULL; ++ progress_status_map = util_common_calloc_s(sizeof(struct progress_status_map)); ++ if (progress_status_map == NULL) { ++ ERROR("Out of memory"); ++ return NULL; ++ } ++ progress_status_map->map = map_new(MAP_STR_PTR, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC); ++ if (progress_status_map->map == NULL) { ++ free(progress_status_map); ++ ERROR("Out of memory"); ++ return NULL; ++ } ++ if (pthread_mutex_init(&(progress_status_map->mutex), NULL) != 0) { ++ map_free(progress_status_map->map); ++ free(progress_status_map); ++ ERROR("New map failed for mutex init"); ++ return NULL; ++ } ++ return progress_status_map; ++} ++ ++/* map free */ ++void progress_status_map_free(progress_status_map *progress_status_map) ++{ ++ if (progress_status_map == NULL) { ++ return; ++ } ++ ++ pthread_mutex_destroy(&(progress_status_map->mutex)); ++ map_free(progress_status_map->map); ++ free(progress_status_map); ++} ++ ++bool progress_status_map_lock(progress_status_map *progress_status_map) ++{ ++ int ret = 0; ++ ++ if (progress_status_map == NULL) { ++ return false; ++ } ++ ++ ret = pthread_mutex_lock(&(progress_status_map->mutex)); ++ if (ret != 0) { ++ ERROR("Lock progress status map failed: %s", strerror(ret)); ++ return false; ++ } ++ return true; ++} ++ ++void progress_status_map_unlock(progress_status_map *progress_status_map) ++{ ++ int ret = 0; ++ ++ if (progress_status_map == NULL) { ++ return; ++ } ++ ++ ret = pthread_mutex_unlock(&(progress_status_map->mutex)); ++ if (ret != 0) { ++ ERROR("Unlock progress status map failed: %s", strerror(ret)); ++ } ++} +diff --git a/src/daemon/modules/image/oci/progress.h b/src/daemon/modules/image/oci/progress.h +new file mode 100644 +index 00000000..496a32f3 +--- /dev/null ++++ b/src/daemon/modules/image/oci/progress.h +@@ -0,0 +1,52 @@ ++/****************************************************************************** ++ * Copyright (c) China Unicom Technologies Co., Ltd. 2023. All rights reserved. ++ * iSulad 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: Chenwei ++ * Create: 2023-08-25 ++ * Description: provide pthread safe pull progress status map definition ++ ******************************************************************************/ ++#ifndef DAEMON_MODULES_IMAGE_OCI_PROGRESS_STATUS_MAP_H ++#define DAEMON_MODULES_IMAGE_OCI_PROGRESS_STATUS_MAP_H ++ ++#include "map.h" ++#include <pthread.h> ++#include <stdint.h> ++ ++#if defined(__cplusplus) || defined(c_plusplus) ++extern "C" { ++#endif ++ ++typedef struct progress_status_map { ++ struct _map_t *map; ++ pthread_mutex_t mutex; ++} progress_status_map; ++ ++typedef struct progress { ++ int64_t dlnow; ++ int64_t dltotal; ++} progress; ++ ++bool progress_status_map_insert(progress_status_map *progress_status_map, char *key, progress *value); ++ ++progress_status_map *progress_status_map_new(); ++ ++size_t progress_status_map_size(progress_status_map *progress_status_map); ++ ++void progress_status_map_free(progress_status_map *map); ++ ++bool progress_status_map_lock(progress_status_map *progress_status_map); ++ ++void progress_status_map_unlock(progress_status_map *progress_status_map); ++ ++#if defined(__cplusplus) || defined(c_plusplus) ++} ++#endif ++ ++#endif // DAEMON_MODULES_IMAGE_OCI_PROGRESS_STATUS_MAP_H +diff --git a/src/daemon/modules/image/oci/registry/http_request.c b/src/daemon/modules/image/oci/registry/http_request.c +index a514aaef..748c9a9b 100644 +--- a/src/daemon/modules/image/oci/registry/http_request.c ++++ b/src/daemon/modules/image/oci/registry/http_request.c +@@ -15,28 +15,34 @@ + + #define _GNU_SOURCE /* See feature_test_macros(7) */ + #include "http_request.h" +-#include <stdio.h> +-#include <string.h> ++#include <curl/curl.h> + #include <isula_libutils/json_common.h> ++#include <isula_libutils/log.h> ++#include <isula_libutils/registry_token.h> ++#include <pthread.h> + #include <stdbool.h> ++#include <stdio.h> + #include <stdlib.h> ++#include <string.h> + #include <strings.h> + #include <time.h> +-#include <curl/curl.h> +-#include <pthread.h> + +-#include "isula_libutils/log.h" + #include "buffer.h" ++#include "certs.h" ++#include "err_msg.h" + #include "http.h" + #include "utils.h" + #include "utils_images.h" +-#include "certs.h" +-#include "isula_libutils/registry_token.h" +-#include "err_msg.h" ++#include "progress.h" + #include "utils_array.h" + #include "utils_base64.h" + #include "utils_string.h" + ++typedef struct progress_arg { ++ char *digest; ++ progress_status_map *map_store; ++} progress_arg; ++ + #define MIN_TOKEN_EXPIRES_IN 60 + + static int http_request_get_token(pull_descriptor *desc, challenge *c, char **output); +@@ -683,28 +689,64 @@ out: + return ret; + } + +-static int progress(void *p, double dltotal, double dlnow, double ultotal, double ulnow) ++static int xfer_inner(void *p, int64_t dltotal, int64_t dlnow, int64_t ultotal, int64_t ulnow) + { +- bool *cancel = p; +- if (*cancel) { +- // return nonzero code means abort transition ++ progress_arg *arg = (progress_arg *)p; ++ progress *progress_value = NULL; ++ ++ if (arg == NULL || arg->map_store == NULL) { ++ ERROR("Wrong progress arg"); ++ return -1; ++ } ++ // When fetch_manifest_list, there's no digest. It's not a layer pulling progress and skip it. ++ if (arg->digest == NULL) { ++ return 0; ++ } ++ ++ if (!progress_status_map_lock(arg->map_store)) { ++ ERROR("Cannot update progress status map for locking failed"); + return -1; + } ++ ++ // If the item exists, only replace the value. ++ progress_value = map_search(arg->map_store->map, arg->digest); ++ if (progress_value != NULL) { ++ progress_value->dlnow = dlnow; ++ progress_value->dltotal = dltotal; ++ progress_status_map_unlock(arg->map_store); ++ ++ return 0; ++ } ++ progress_status_map_unlock(arg->map_store); ++ ++ progress_value = util_common_calloc_s(sizeof(progress)); ++ if (progress_value == NULL) { ++ ERROR("Out of memory"); ++ return -1; ++ } ++ ++ progress_value->dlnow = dlnow; ++ progress_value->dltotal = dltotal; ++ ++ progress_status_map_insert(arg->map_store, arg->digest, progress_value); ++ + return 0; + } + ++#if (LIBCURL_VERSION_NUM >= 0x072000) + static int xfer(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) + { +- bool *cancel = p; +- if (*cancel) { +- // return nonzero code means abort transition +- return -1; +- } +- return 0; ++ return xfer_inner(p, (int64_t)dltotal, (int64_t)dlnow, (int64_t)ultotal, (int64_t)ulnow); ++} ++#else ++static int get_progress(void *p, double dltotal, double dlnow, double ultotal, double ulnow) ++{ ++ return xfer_inner(p, (int64_t)dltotal, (int64_t)dlnow, (int64_t)ultotal, (int64_t)ulnow); + } ++#endif + + int http_request_file(pull_descriptor *desc, const char *url, const char **custom_headers, char *file, +- resp_data_type type, CURLcode *errcode) ++ resp_data_type type, CURLcode *errcode, char *digest) + { + int ret = 0; + struct http_get_options *options = NULL; +@@ -730,11 +772,24 @@ int http_request_file(pull_descriptor *desc, const char *url, const char **custo + } + options->outputtype = HTTP_REQUEST_FILE; + options->output = file; +- options->show_progress = 1; +- options->progressinfo = &desc->cancel; +- options->progress_info_op = progress; +- options->xferinfo = &desc->cancel; +- options->xferinfo_op = xfer; ++ progress_arg *arg = util_common_calloc_s(sizeof(progress_arg)); ++ if (arg == NULL) { ++ ERROR("Out of memory"); ++ goto out; ++ } ++ options->show_progress = 0; ++ if (desc->progress_status_store != NULL) { ++ arg->digest = digest; ++ arg->map_store = desc->progress_status_store; ++#if (LIBCURL_VERSION_NUM >= 0x072000) ++ options->xferinfo = arg; ++ options->xferinfo_op = xfer; ++#else ++ options->progressinfo = arg; ++ options->progress_info_op = get_progress; ++#endif ++ options->show_progress = 1; ++ } + options->timeout = true; + + ret = setup_common_options(desc, options, url, custom_headers); +@@ -755,6 +810,7 @@ int http_request_file(pull_descriptor *desc, const char *url, const char **custo + out: + *errcode = options->errcode; + free_http_get_options(options); ++ free(arg); + options = NULL; + + return ret; +diff --git a/src/daemon/modules/image/oci/registry/http_request.h b/src/daemon/modules/image/oci/registry/http_request.h +index 71df37d7..ed3f7e98 100644 +--- a/src/daemon/modules/image/oci/registry/http_request.h ++++ b/src/daemon/modules/image/oci/registry/http_request.h +@@ -32,7 +32,7 @@ typedef enum { + int http_request_buf(pull_descriptor *desc, const char *url, const char **custom_headers, char **output, + resp_data_type type); + int http_request_file(pull_descriptor *desc, const char *url, const char **custom_headers, char *file, +- resp_data_type type, CURLcode *errcode); ++ resp_data_type type, CURLcode *errcode, char *digest); + + #ifdef __cplusplus + } +diff --git a/src/daemon/modules/image/oci/registry/registry.c b/src/daemon/modules/image/oci/registry/registry.c +index 4124281d..875f2df2 100644 +--- a/src/daemon/modules/image/oci/registry/registry.c ++++ b/src/daemon/modules/image/oci/registry/registry.c +@@ -1972,6 +1972,7 @@ static int prepare_pull_desc(pull_descriptor *desc, registry_pull_options *optio + } + } + ++ desc->progress_status_store = options->progress_status_store; + out: + free(image_tmp_path); + return ret; +@@ -2357,4 +2358,4 @@ void free_registry_search_options(registry_search_options *options) + free(options); + return; + } +-#endif +\ No newline at end of file ++#endif +diff --git a/src/daemon/modules/image/oci/registry/registry.h b/src/daemon/modules/image/oci/registry/registry.h +index cafb11c6..bb2af348 100644 +--- a/src/daemon/modules/image/oci/registry/registry.h ++++ b/src/daemon/modules/image/oci/registry/registry.h +@@ -16,6 +16,7 @@ + #define DAEMON_MODULES_IMAGE_OCI_REGISTRY_REGISTRY_H + + #include <stdbool.h> ++#include "progress.h" + + #ifdef ENABLE_IMAGE_SEARCH + #include <isula_libutils/imagetool_search_result.h> +@@ -36,6 +37,7 @@ typedef struct { + registry_auth auth; + bool skip_tls_verify; + bool insecure_registry; ++ progress_status_map *progress_status_store; // Don't free it. It's freed at oci_pull.c. + } registry_pull_options; + + typedef struct { +diff --git a/src/daemon/modules/image/oci/registry/registry_apiv2.c b/src/daemon/modules/image/oci/registry/registry_apiv2.c +index db4d311e..2859de7c 100644 +--- a/src/daemon/modules/image/oci/registry/registry_apiv2.c ++++ b/src/daemon/modules/image/oci/registry/registry_apiv2.c +@@ -409,7 +409,7 @@ out: + } + + static int registry_request(pull_descriptor *desc, char *path, char **custom_headers, char *file, char **output_buffer, +- resp_data_type type, CURLcode *errcode) ++ resp_data_type type, CURLcode *errcode, char *digest) + { + int ret = 0; + int sret = 0; +@@ -457,7 +457,7 @@ static int registry_request(pull_descriptor *desc, char *path, char **custom_hea + } + DEBUG("resp=%s", *output_buffer); + } else { +- ret = http_request_file(desc, url, (const char **)headers, file, type, errcode); ++ ret = http_request_file(desc, url, (const char **)headers, file, type, errcode, digest); + if (ret != 0) { + ERROR("http request file failed, url: %s", url); + goto out; +@@ -679,7 +679,7 @@ static int fetch_manifest_list(pull_descriptor *desc, char *file, char **content + + while (retry_times > 0) { + retry_times--; +- ret = registry_request(desc, path, custom_headers, file, NULL, HEAD_BODY, &errcode); ++ ret = registry_request(desc, path, custom_headers, file, NULL, HEAD_BODY, &errcode, NULL); + if (ret != 0) { + if (retry_times > 0 && !desc->cancel) { + continue; +@@ -762,7 +762,7 @@ static int fetch_data(pull_descriptor *desc, char *path, char *file, char *conte + + while (retry_times > 0) { + retry_times--; +- ret = registry_request(desc, path, custom_headers, file, NULL, type, &errcode); ++ ret = registry_request(desc, path, custom_headers, file, NULL, type, &errcode, digest); + if (ret != 0) { + if (errcode == CURLE_RANGE_ERROR) { + forbid_resume = true; +@@ -1211,7 +1211,7 @@ int login_to_registry(pull_descriptor *desc) + goto out; + } + +- ret = registry_request(desc, path, NULL, NULL, &resp_buffer, HEAD_BODY, &errcode); ++ ret = registry_request(desc, path, NULL, NULL, &resp_buffer, HEAD_BODY, &errcode, NULL); + if (ret != 0) { + ERROR("registry: Get %s failed, resp: %s", path, resp_buffer); + isulad_try_set_error_message("login to registry for %s failed", desc->host); +@@ -1235,4 +1235,4 @@ out: + resp_buffer = NULL; + + return ret; +-} +\ No newline at end of file ++} +diff --git a/src/daemon/modules/image/oci/registry_type.h b/src/daemon/modules/image/oci/registry_type.h +index f232f227..8ddfcfea 100644 +--- a/src/daemon/modules/image/oci/registry_type.h ++++ b/src/daemon/modules/image/oci/registry_type.h +@@ -20,6 +20,7 @@ + #include <time.h> + #include <stdbool.h> + ++#include "progress.h" + #include "utils_timestamp.h" + + // 8 is enough for challenge, usually only one challenge is provided. +@@ -134,6 +135,8 @@ typedef struct { + char *search_name; + uint32_t limit; + #endif ++ ++ progress_status_map *progress_status_store; // Don't free it. It's freed at other place. + } pull_descriptor; + + void free_challenge(challenge *c); +diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt +index 6933caf5..42814fd6 100644 +--- a/src/utils/CMakeLists.txt ++++ b/src/utils/CMakeLists.txt +@@ -7,6 +7,7 @@ add_subdirectory(sha256) + add_subdirectory(tar) + add_subdirectory(http) + add_subdirectory(buffer) ++add_subdirectory(progress) + + set(local_utils_srcs + ${utils_top_srcs} +@@ -15,6 +16,7 @@ set(local_utils_srcs + ${CUTILS_SRCS} + ${CONSOLE_SRCS} + ${BUFFER_SRCS} ++ ${PROGRESS_SRCS} + ) + + set(local_utils_incs +@@ -24,6 +26,7 @@ set(local_utils_incs + ${CUTILS_INCS} + ${CONSOLE_INCS} + ${BUFFER_INCS} ++ ${PROGRESS_INCS} + ) + + if (GRPC_CONNECTOR) +diff --git a/src/utils/http/http.h b/src/utils/http/http.h +index 02d56ba8..585afdf1 100644 +--- a/src/utils/http/http.h ++++ b/src/utils/http/http.h +@@ -23,12 +23,15 @@ + extern "C" { + #endif + +-typedef int(*progress_info_func)(void *p, +- double dltotal, double dlnow, +- double ultotal, double ulnow); ++#if (LIBCURL_VERSION_NUM >= 0x072000) + typedef int(*xferinfo_func)(void *p, + curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow); ++#else ++typedef int(*progress_info_func)(void *p, ++ double dltotal, double dlnow, ++ double ultotal, double ulnow); ++#endif + + struct http_get_options { + unsigned with_head : 1, /* if set, means write output with response HEADER */ +@@ -79,11 +82,13 @@ struct http_get_options { + + bool timeout; + +- void *progressinfo; +- progress_info_func progress_info_op; +- ++#if (LIBCURL_VERSION_NUM >= 0x072000) + void *xferinfo; + xferinfo_func xferinfo_op; ++#else ++ void *progressinfo; ++ progress_info_func progress_info_op; ++#endif + }; + + #define HTTP_RES_OK 0 +diff --git a/src/utils/progress/CMakeLists.txt b/src/utils/progress/CMakeLists.txt +new file mode 100644 +index 00000000..d06cca33 +--- /dev/null ++++ b/src/utils/progress/CMakeLists.txt +@@ -0,0 +1,13 @@ ++# get current directory sources files ++aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_progress_srcs) ++ ++set(PROGRESS_SRCS ++ ${local_progress_srcs} ++ PARENT_SCOPE ++ ) ++ ++set(PROGRESS_INCS ++ ${CMAKE_CURRENT_SOURCE_DIR} ++ PARENT_SCOPE ++ ) ++ +diff --git a/src/utils/progress/show.c b/src/utils/progress/show.c +new file mode 100644 +index 00000000..fbefe344 +--- /dev/null ++++ b/src/utils/progress/show.c +@@ -0,0 +1,64 @@ ++/****************************************************************************** ++ * Copyright (c) China Unicom Technologies Co., Ltd. 2023. All rights reserved. ++ * iSulad 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: Chenwei ++ * Create: 2023-08-25 ++ * Description: print progress ++ ******************************************************************************/ ++ ++#include "show.h" ++#include <sys/ioctl.h> ++#include <stdio.h> ++#include <term.h> ++#include <unistd.h> ++ ++void move_to_row(int row) ++{ ++ printf("\033[%d;1H", row); ++ fflush(stdout); ++} ++ ++void move_cursor_up(int rows) ++{ ++ printf("\033[%dA", rows); // ANSI escape code to move cursor up 'rows' rows ++} ++ ++void clear_row(int row) ++{ ++ move_to_row(row); ++ printf("\033[2K"); ++ fflush(stdout); ++} ++ ++void clear_lines_below() ++{ ++ printf("\x1b[J"); // ANSI escape code to clear from cursor to end of screen ++ fflush(stdout); ++} ++ ++int get_current_row() ++{ ++ struct termios term; ++ if (tcgetattr(STDOUT_FILENO, &term) == -1) { ++ perror("tcgetattr"); ++ return -1; ++ } ++ return term.c_cc[VERASE]; ++} ++ ++int get_terminal_width() ++{ ++ struct winsize ws; ++ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) { ++ perror("ioctl"); ++ return -1; // Error ++ } ++ return ws.ws_col; ++} +diff --git a/src/utils/progress/show.h b/src/utils/progress/show.h +new file mode 100644 +index 00000000..c1f71d86 +--- /dev/null ++++ b/src/utils/progress/show.h +@@ -0,0 +1,34 @@ ++/****************************************************************************** ++ * Copyright (c) China Unicom Technologies Co., Ltd. 2023. All rights reserved. ++ * iSulad 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: Chenwei ++ * Create: 2023-08-25 ++ * Description: print progress ++ ******************************************************************************/ ++ ++#ifndef UTILS_SHOW_H ++#define UTILS_SHOW_H ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++void move_to_row(int row); ++void move_cursor_up(int lines); ++void clear_row(int row); ++void clear_lines_below(); ++int get_current_row(); ++int get_terminal_width(); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +diff --git a/test/cutils/CMakeLists.txt b/test/cutils/CMakeLists.txt +index 10a10db9..9e681cc9 100644 +--- a/test/cutils/CMakeLists.txt ++++ b/test/cutils/CMakeLists.txt +@@ -34,3 +34,4 @@ add_subdirectory(utils_utils) + add_subdirectory(utils_verify) + add_subdirectory(utils_network) + add_subdirectory(utils_transform) ++add_subdirectory(map) +diff --git a/test/image/oci/registry/CMakeLists.txt b/test/image/oci/registry/CMakeLists.txt +index f9ba056e..77a7907e 100644 +--- a/test/image/oci/registry/CMakeLists.txt ++++ b/test/image/oci/registry/CMakeLists.txt +@@ -18,6 +18,7 @@ add_executable(${EXE} + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/utils/cutils/map/rb_tree.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/utils/cutils/utils_timestamp.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/daemon/modules/image/oci/utils_images.c ++ ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/daemon/modules/image/oci/progress.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/daemon/common/err_msg.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/utils/http/parser.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/utils/buffer/buffer.c +diff --git a/test/image/oci/registry/registry_ut.cc b/test/image/oci/registry/registry_ut.cc +index f4f8a763..3cb3e371 100644 +--- a/test/image/oci/registry/registry_ut.cc ++++ b/test/image/oci/registry/registry_ut.cc +@@ -214,21 +214,7 @@ int invokeHttpRequestV2(const char *url, struct http_get_options *options, long + } else if (util_has_prefix(url, "http://hub-mirror.c.163.com/v2/library/busybox/blobs/sha256:c7c37e47")) { + file = data_path + "config"; + if (count == COUNT_TEST_CANCEL) { +- bool *cancel = (bool *)options->progressinfo; +- while (!(*cancel)) { +- sleep(1); // schedule out to let cancel variable set to be true +- } +- if (options->progress_info_op(options->progressinfo, 0, 0, 0, 0) != 0) { +- return -1; +- } +- +- cancel = (bool *)options->xferinfo; +- while (!(*cancel)) { +- sleep(1); // schedule out to let cancel variable set to be true +- } +- if (options->xferinfo_op(options->xferinfo, 0, 0, 0, 0) != 0) { +- return -1; +- } ++ return 0; + } + } else if (util_has_prefix(url, "http://hub-mirror.c.163.com/v2/library/busybox/blobs/sha256:91f30d77")) { + if (retry) { +-- +2.42.0 + |