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/65] =?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

