summaryrefslogtreecommitdiff
path: root/0024-2170-isula-image-pull.patch
diff options
context:
space:
mode:
Diffstat (limited to '0024-2170-isula-image-pull.patch')
-rw-r--r--0024-2170-isula-image-pull.patch1815
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 &gtimestamp);
+
++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
+