summaryrefslogtreecommitdiff
path: root/0197-coco-support-confidential-containers.patch
diff options
context:
space:
mode:
Diffstat (limited to '0197-coco-support-confidential-containers.patch')
-rw-r--r--0197-coco-support-confidential-containers.patch539
1 files changed, 539 insertions, 0 deletions
diff --git a/0197-coco-support-confidential-containers.patch b/0197-coco-support-confidential-containers.patch
new file mode 100644
index 0000000..4cbd469
--- /dev/null
+++ b/0197-coco-support-confidential-containers.patch
@@ -0,0 +1,539 @@
+From 2edae8a425ae3442ee73469ca3fd2f3bf9422301 Mon Sep 17 00:00:00 2001
+From: liuxu <liuxu156@huawei.com>
+Date: Mon, 16 Dec 2024 17:11:04 +0800
+Subject: [PATCH 197/198] coco:support confidential containers
+
+Signed-off-by: liuxu <liuxu156@huawei.com>
+---
+ cmake/options.cmake | 11 +++
+ src/common/constants.h | 6 ++
+ src/daemon/common/cri/v1/v1_cri_helpers.cc | 71 +++++++++++++---
+ src/daemon/common/cri/v1/v1_cri_helpers.h | 4 +
+ .../v1/v1_cri_container_manager_service.cc | 5 ++
+ .../v1/v1_cri_pod_sandbox_manager_service.cc | 14 +++-
+ .../executor/container_cb/execution_create.c | 12 +++
+ src/daemon/modules/api/image_api.h | 3 +
+ src/daemon/modules/image/CMakeLists.txt | 10 +++
+ src/daemon/modules/image/image.c | 46 +++++++++++
+ .../modules/image/remote/CMakeLists.txt | 13 +++
+ .../modules/image/remote/remote_image.c | 81 +++++++++++++++++++
+ .../modules/image/remote/remote_image.h | 40 +++++++++
+ .../sandbox/sandboxer/sandboxer_sandbox.cc | 10 ++-
+ 14 files changed, 308 insertions(+), 18 deletions(-)
+ create mode 100644 src/daemon/modules/image/remote/CMakeLists.txt
+ create mode 100644 src/daemon/modules/image/remote/remote_image.c
+ create mode 100644 src/daemon/modules/image/remote/remote_image.h
+
+diff --git a/cmake/options.cmake b/cmake/options.cmake
+index 018502d7..efbd0a52 100644
+--- a/cmake/options.cmake
++++ b/cmake/options.cmake
+@@ -62,6 +62,17 @@ if (ENABLE_SANDBOXER STREQUAL "ON")
+ endif()
+ endif()
+
++option(ENABLE_REMOTE_IMAGE "Enable remote image" OFF)
++if (ENABLE_REMOTE_IMAGE STREQUAL "ON")
++ if (ENABLE_SANDBOXER)
++ add_definitions(-DENABLE_REMOTE_IMAGE)
++ set(ENABLE_REMOTE_IMAGE 1)
++ message("${Green}-- Enable remote image${ColourReset}")
++ else()
++ message("${Yellow}-- Can not enable remote image, remote image need enable sandboxer first ${ColourReset}")
++ endif()
++endif()
++
+ option(ENABLE_OOM_MONITOR "Enable oom monitor" ON)
+ if (ENABLE_OOM_MONITOR STREQUAL "ON")
+ add_definitions(-DENABLE_OOM_MONITOR)
+diff --git a/src/common/constants.h b/src/common/constants.h
+index 8a6f86d8..7759896f 100644
+--- a/src/common/constants.h
++++ b/src/common/constants.h
+@@ -218,6 +218,12 @@ typedef enum { WAIT_CONDITION_STOPPED = 0, WAIT_CONDITION_REMOVED = 1 } wait_con
+ #define CRI_CONTAINER_TYPE_LABEL_KEY "cri.isulad.type"
+ #define CRI_CONTAINER_TYPE_LABEL_SANDBOX "podsandbox"
+
++#ifdef ENABLE_REMOTE_IMAGE
++// Note: Currently, remote image is used only in confidentail container, so the image is supposed to be encrypted
++#define SANDBOX_IMAGE_TYPE_REMOTE "remote"
++#define IMAGE_NAME_COCO "[Encrypted]"
++#endif
++
+ #ifdef __cplusplus
+ }
+ #endif
+diff --git a/src/daemon/common/cri/v1/v1_cri_helpers.cc b/src/daemon/common/cri/v1/v1_cri_helpers.cc
+index d71e3681..48dcfb45 100644
+--- a/src/daemon/common/cri/v1/v1_cri_helpers.cc
++++ b/src/daemon/common/cri/v1/v1_cri_helpers.cc
+@@ -325,10 +325,32 @@ void AddSecurityOptsToHostConfig(std::vector<std::string> &securityOpts, host_co
+ }
+
+ #ifdef ENABLE_SANDBOXER
++static defs_map_string_object_sandboxer_element *GetCRISandboxer(
++ const std::string &runtime, struct service_arguments *args)
++{
++ defs_map_string_object_sandboxer_element *criSandboxer = nullptr;
++ defs_map_string_object_sandboxer *criSandboxerList = nullptr;
++
++ criSandboxerList = args->json_confs->cri_sandboxers;
++ for (size_t i = 0; i < criSandboxerList->len; i++) {
++ if (criSandboxerList->keys[i] == nullptr || criSandboxerList->values[i] == nullptr ||
++ criSandboxerList->values[i]->name == nullptr) {
++ WARN("CRI runtimes key or value is null");
++ continue;
++ }
++
++ if (runtime == std::string(criSandboxerList->keys[i])) {
++ criSandboxer = criSandboxerList->values[i];
++ break;
++ }
++ }
++ return criSandboxer;
++}
++
+ std::string CRISandboxerConvert(const std::string &runtime)
+ {
+ std::string sandboxer;
+- defs_map_string_object_sandboxer *criSandboxerList = nullptr;
++ defs_map_string_object_sandboxer_element *criSandboxer = nullptr;
+
+ if (runtime.empty()) {
+ return DEFAULT_SANDBOXER_NAME;
+@@ -346,24 +368,47 @@ std::string CRISandboxerConvert(const std::string &runtime)
+ }
+
+ sandboxer = DEFAULT_SANDBOXER_NAME;
+- criSandboxerList = args->json_confs->cri_sandboxers;
+- for (size_t i = 0; i < criSandboxerList->len; i++) {
+- if (criSandboxerList->keys[i] == nullptr || criSandboxerList->values[i] == nullptr ||
+- criSandboxerList->values[i]->name == nullptr) {
+- WARN("CRI runtimes key or value is null");
+- continue;
+- }
+-
+- if (runtime == std::string(criSandboxerList->keys[i])) {
+- sandboxer = std::string(criSandboxerList->values[i]->name);
+- break;
+- }
++ criSandboxer = GetCRISandboxer(runtime, args);
++ if (criSandboxer != nullptr) {
++ sandboxer = std::string(criSandboxer->name);
+ }
+
+ out:
+ (void)isulad_server_conf_unlock();
+ return sandboxer;
+ }
++
++#ifdef ENABLE_REMOTE_IMAGE
++std::string GetCRISandboxerImageType(const std::string &runtime)
++{
++ std::string imageType = "";
++ defs_map_string_object_sandboxer_element *criSandboxer = nullptr;
++
++ if (runtime.empty()) {
++ return imageType;
++ }
++
++ if (isulad_server_conf_rdlock()) {
++ ERROR("Lock isulad server conf failed");
++ return imageType;
++ }
++
++ struct service_arguments *args = conf_get_server_conf();
++ if (args == nullptr || args->json_confs == nullptr || args->json_confs->cri_sandboxers == nullptr) {
++ ERROR("Cannot get cri sandboxer list");
++ goto out;
++ }
++
++ criSandboxer = GetCRISandboxer(runtime, args);
++ if (criSandboxer != nullptr && criSandboxer->image_type != nullptr) {
++ imageType = std::string(criSandboxer->image_type);
++ }
++
++out:
++ (void)isulad_server_conf_unlock();
++ return imageType;
++}
++#endif
+ #else
+ std::string CRISandboxerConvert(const std::string &runtime)
+ {
+diff --git a/src/daemon/common/cri/v1/v1_cri_helpers.h b/src/daemon/common/cri/v1/v1_cri_helpers.h
+index 6a848581..4fd15d0b 100644
+--- a/src/daemon/common/cri/v1/v1_cri_helpers.h
++++ b/src/daemon/common/cri/v1/v1_cri_helpers.h
+@@ -69,6 +69,10 @@ void AddSecurityOptsToHostConfig(std::vector<std::string> &securityOpts, host_co
+
+ std::string CRISandboxerConvert(const std::string &runtime);
+
++#ifdef ENABLE_REMOTE_IMAGE
++std::string GetCRISandboxerImageType(const std::string &runtime);
++#endif
++
+ void ApplySandboxSecurityContextToHostConfig(const runtime::v1::LinuxSandboxSecurityContext &context, host_config *hc,
+ Errors &error);
+ #ifdef ENABLE_CDI
+diff --git a/src/daemon/entry/cri/v1/v1_cri_container_manager_service.cc b/src/daemon/entry/cri/v1/v1_cri_container_manager_service.cc
+index fe1cca0c..1cc584fb 100644
+--- a/src/daemon/entry/cri/v1/v1_cri_container_manager_service.cc
++++ b/src/daemon/entry/cri/v1/v1_cri_container_manager_service.cc
+@@ -371,6 +371,11 @@ auto ContainerManagerService::GenerateSandboxInfo(
+ }
+
+ sandbox_info->sandboxer = util_strdup_s(sandbox.GetSandboxer().c_str());
++#ifdef ENABLE_REMOTE_IMAGE
++ sandbox_info->image_type = util_strdup_s(
++ CRIHelpersV1::GetCRISandboxerImageType(sandbox.GetSandboxer()).c_str()
++ );
++#endif
+ sandbox_info->id = util_strdup_s(sandbox.GetId().c_str());
+ sandbox_info->pid = sandbox.GetPid();
+ sandbox_info->task_address = util_strdup_s(sandbox.GetTaskAddress().c_str());
+diff --git a/src/daemon/entry/cri/v1/v1_cri_pod_sandbox_manager_service.cc b/src/daemon/entry/cri/v1/v1_cri_pod_sandbox_manager_service.cc
+index 8a7779ad..fd87e90b 100644
+--- a/src/daemon/entry/cri/v1/v1_cri_pod_sandbox_manager_service.cc
++++ b/src/daemon/entry/cri/v1/v1_cri_pod_sandbox_manager_service.cc
+@@ -468,11 +468,17 @@ auto PodSandboxManagerService::RunPodSandbox(const runtime::v1::PodSandboxConfig
+ // But pull image interface is only in CRI image service, and it can't be called in shim controller,
+ // so we pull image in CRI pod service.
+ const std::string &image = m_podSandboxImage;
+- if (!EnsureSandboxImageExists(image, runtimeInfo.sandboxer, error)) {
+- ERROR("Failed to pull sandbox image %s: %s", image.c_str(), error.NotEmpty() ? error.GetCMessage() : "");
+- error.Errorf("Failed to pull sandbox image %s: %s", image.c_str(), error.NotEmpty() ? error.GetCMessage() : "");
+- return response_id;
++#ifdef ENABLE_REMOTE_IMAGE
++ if (CRIHelpersV1::GetCRISandboxerImageType(runtimeInfo.sandboxer) != std::string(SANDBOX_IMAGE_TYPE_REMOTE)) {
++#endif
++ if (!EnsureSandboxImageExists(image, runtimeInfo.sandboxer, error)) {
++ ERROR("Failed to pull sandbox image %s: %s", image.c_str(), error.NotEmpty() ? error.GetCMessage() : "");
++ error.Errorf("Failed to pull sandbox image %s: %s", image.c_str(), error.NotEmpty() ? error.GetCMessage() : "");
++ return response_id;
++ }
++#ifdef ENABLE_REMOTE_IMAGE
+ }
++#endif
+
+ // Step 3: Prepare sandbox checkpoint
+ PrepareSandboxCheckpoint(config, jsonCheckpoint, error);
+diff --git a/src/daemon/executor/container_cb/execution_create.c b/src/daemon/executor/container_cb/execution_create.c
+index dcbdd1d3..6cd860c2 100644
+--- a/src/daemon/executor/container_cb/execution_create.c
++++ b/src/daemon/executor/container_cb/execution_create.c
+@@ -1016,6 +1016,18 @@ static int get_request_container_info(const container_create_request *request, c
+
+ static int get_request_image_info(const container_create_request *request, char **image_type, char **image_name)
+ {
++#ifdef ENABLE_REMOTE_IMAGE
++ if (is_container_in_sandbox(request->sandbox) &&
++ strcmp(request->sandbox->image_type, IMAGE_TYPE_REMOTE) == 0) {
++ /*
++ * Note: Currently, remote image type and coco image type
++ * are considered to be the same type.
++ */
++ *image_type = util_strdup_s(IMAGE_TYPE_REMOTE);
++ *image_name = util_strdup_s(IMAGE_NAME_COCO);
++ return 0;
++ }
++#endif
+ *image_type = im_get_image_type(request->image, request->rootfs);
+ if (*image_type == NULL) {
+ return -1;
+diff --git a/src/daemon/modules/api/image_api.h b/src/daemon/modules/api/image_api.h
+index f35cd013..062684c9 100644
+--- a/src/daemon/modules/api/image_api.h
++++ b/src/daemon/modules/api/image_api.h
+@@ -41,6 +41,9 @@ extern "C" {
+ #define IMAGE_TYPE_OCI "oci"
+ #define IMAGE_TYPE_EMBEDDED "embedded"
+ #define IMAGE_TYPE_EXTERNAL "external"
++#ifdef ENABLE_REMOTE_IMAGE
++#define IMAGE_TYPE_REMOTE SANDBOX_IMAGE_TYPE_REMOTE
++#endif
+
+ typedef struct {
+ char *image;
+diff --git a/src/daemon/modules/image/CMakeLists.txt b/src/daemon/modules/image/CMakeLists.txt
+index d8b78ce1..6d31a1ea 100644
+--- a/src/daemon/modules/image/CMakeLists.txt
++++ b/src/daemon/modules/image/CMakeLists.txt
+@@ -33,6 +33,16 @@ if (ENABLE_EMBEDDED_IMAGE)
+ )
+ endif()
+
++if (ENABLE_REMOTE_IMAGE)
++ add_subdirectory(remote)
++ list(APPEND local_image_srcs
++ ${REMOTE_SRCS}
++ )
++ list(APPEND local_image_incs
++ ${REMOTE_INCS}
++ )
++endif()
++
+ set(IMAGE_SRCS
+ ${local_image_srcs}
+ PARENT_SCOPE
+diff --git a/src/daemon/modules/image/image.c b/src/daemon/modules/image/image.c
+index 871f5f39..baf2ba9d 100644
+--- a/src/daemon/modules/image/image.c
++++ b/src/daemon/modules/image/image.c
+@@ -125,6 +125,10 @@ struct bim_type {
+ #include "oci_image.h"
+ #endif
+
++#ifdef ENABLE_REMOTE_IMAGE
++#include "remote_image.h"
++#endif
++
+ #ifdef ENABLE_EMBEDDED_IMAGE
+ #include "embedded_image.h"
+ #include "db_all.h"
+@@ -243,6 +247,45 @@ static const struct bim_ops g_ext_ops = {
+ #endif
+ };
+
++#ifdef ENABLE_REMOTE_IMAGE
++/* remote */
++static const struct bim_ops g_remote_ops = {
++ .init = NULL,
++ .clean_resource = NULL,
++ .detect = NULL,
++
++ .prepare_rf = remote_prepare_rf,
++ .mount_rf = remote_mount_rf,
++ .umount_rf = remote_umount_rf,
++ .delete_rf = remote_delete_rf,
++ .delete_broken_rf = remote_delete_broken_rf,
++ .export_rf = NULL,
++ .get_dir_rf = NULL,
++
++ .merge_conf = remote_merge_conf_rf,
++ .get_user_conf = remote_get_user_conf,
++
++ .list_ims = NULL,
++ .get_image_count = NULL,
++ .rm_image = remote_rmi,
++ .inspect_image = NULL,
++ .resolve_image_name = remote_resolve_image_name,
++ .container_fs_usage = remote_container_filesystem_usage,
++ .get_filesystem_info = remote_get_filesystem_info,
++ .image_status = NULL,
++ .load_image = NULL,
++ .pull_image = NULL,
++ .login = NULL,
++ .logout = NULL,
++ .tag_image = NULL,
++ .import = NULL,
++ .image_summary = NULL,
++#ifdef ENABLE_IMAGE_SEARCH
++ .search_image = NULL,
++#endif
++};
++#endif
++
+ static const struct bim_type g_bims[] = {
+ #ifdef ENABLE_OCI_IMAGE
+ {
+@@ -254,6 +297,9 @@ static const struct bim_type g_bims[] = {
+ #ifdef ENABLE_EMBEDDED_IMAGE
+ { .image_type = IMAGE_TYPE_EMBEDDED, .ops = &g_embedded_ops },
+ #endif
++#ifdef ENABLE_REMOTE_IMAGE
++ { .image_type = IMAGE_TYPE_REMOTE, .ops = &g_remote_ops },
++#endif
+ };
+
+
+diff --git a/src/daemon/modules/image/remote/CMakeLists.txt b/src/daemon/modules/image/remote/CMakeLists.txt
+new file mode 100644
+index 00000000..6e7dab6d
+--- /dev/null
++++ b/src/daemon/modules/image/remote/CMakeLists.txt
+@@ -0,0 +1,13 @@
++# get current directory sources files
++aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_remote_srcs)
++
++set(REMOTE_SRCS
++ ${local_remote_srcs}
++ PARENT_SCOPE
++ )
++
++set(REMOTE_INCS
++ ${CMAKE_CURRENT_SOURCE_DIR}
++ PARENT_SCOPE
++ )
++
+diff --git a/src/daemon/modules/image/remote/remote_image.c b/src/daemon/modules/image/remote/remote_image.c
+new file mode 100644
+index 00000000..87b7593d
+--- /dev/null
++++ b/src/daemon/modules/image/remote/remote_image.c
+@@ -0,0 +1,81 @@
++/******************************************************************************
++ * Copyright (c) Huawei Technologies Co., Ltd. 2025-2026. 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: liuxu
++ * Create: 2025-02-11
++ * Explanation: provide remote image function
++ ******************************************************************************/
++#include "remote_image.h"
++
++#include <isula_libutils/log.h>
++
++#include "utils.h"
++
++
++char *remote_resolve_image_name(const char *name)
++{
++ return util_strdup_s(IMAGE_NAME_COCO);
++}
++
++int remote_prepare_rf(const im_prepare_request *request, char **real_rootfs)
++{
++ if (real_rootfs == NULL) {
++ ERROR("Failed to prepare remote rootfs, rootfs is NULL.");
++ return -1;
++ }
++ // real_rootfs will be changed by runtime when sandbox image type is "remote"
++ *real_rootfs = util_strdup_s("rootfs");
++ return 0;
++}
++
++int remote_rmi(const im_rmi_request *request)
++{
++ return 0;
++}
++
++int remote_get_filesystem_info(im_fs_info_response **response)
++{
++ return 0;
++}
++
++int remote_container_filesystem_usage(const im_container_fs_usage_request *request, imagetool_fs_info **fs_usage)
++{
++ return 0;
++}
++
++int remote_delete_broken_rf(const im_delete_rootfs_request *request)
++{
++ return 0;
++}
++
++int remote_delete_rf(const im_delete_rootfs_request *request)
++{
++ return 0;
++}
++
++int remote_umount_rf(const im_umount_request *request)
++{
++ return 0;
++}
++
++int remote_mount_rf(const im_mount_request *request)
++{
++ return 0;
++}
++
++int remote_merge_conf_rf(const char *img_name, container_config *container_spec)
++{
++ return 0;
++}
++
++int remote_get_user_conf(const char *basefs, host_config *hc, const char *userstr, defs_process_user *puser)
++{
++ return 0;
++}
+diff --git a/src/daemon/modules/image/remote/remote_image.h b/src/daemon/modules/image/remote/remote_image.h
+new file mode 100644
+index 00000000..be952129
+--- /dev/null
++++ b/src/daemon/modules/image/remote/remote_image.h
+@@ -0,0 +1,40 @@
++/******************************************************************************
++ * Copyright (c) Huawei Technologies Co., Ltd. 2025-2026. 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: liuxu
++ * Create: 2025-02-11
++ * Explanation: provide remote image function definition
++ ******************************************************************************/
++#ifndef DAEMON_MODULES_IMAGE_REMOTE_IMAGE_H
++#define DAEMON_MODULES_IMAGE_REMOTE_IMAGE_H
++
++#include "image_api.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++char *remote_resolve_image_name(const char *name);
++int remote_prepare_rf(const im_prepare_request *request, char **real_rootfs);
++int remote_rmi(const im_rmi_request *request);
++int remote_get_filesystem_info(im_fs_info_response **response);
++int remote_container_filesystem_usage(const im_container_fs_usage_request *request, imagetool_fs_info **fs_usage);
++int remote_delete_broken_rf(const im_delete_rootfs_request *request);
++int remote_delete_rf(const im_delete_rootfs_request *request);
++int remote_umount_rf(const im_umount_request *request);
++int remote_mount_rf(const im_mount_request *request);
++int remote_merge_conf_rf(const char *img_name, container_config *container_spec);
++int remote_get_user_conf(const char *basefs, host_config *hc, const char *userstr, defs_process_user *puser);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/src/daemon/sandbox/sandboxer/sandboxer_sandbox.cc b/src/daemon/sandbox/sandboxer/sandboxer_sandbox.cc
+index b2e2fb32..e26b87c8 100644
+--- a/src/daemon/sandbox/sandboxer/sandboxer_sandbox.cc
++++ b/src/daemon/sandbox/sandboxer/sandboxer_sandbox.cc
+@@ -31,6 +31,8 @@
+ #include "cxxutils.h"
+ #include "utils_timestamp.h"
+ #include "utils_array.h"
++#include "constants.h"
++#include "v1_cri_helpers.h"
+
+ namespace sandbox {
+
+@@ -297,6 +299,12 @@ static defs_process *clone_defs_process(defs_process *process_spec)
+
+ auto SandboxerSandbox::GenerateCtrlRootfs(sandbox_task *task, const char *baseFs) -> int
+ {
++#ifdef ENABLE_REMOTE_IMAGE
++ // do not mount image to vm for remote or confidential containers
++ if (CRIHelpersV1::GetCRISandboxerImageType(GetSandboxer()) == std::string(SANDBOX_IMAGE_TYPE_REMOTE)) {
++ return 0;
++ }
++#endif
+ size_t len = 1;
+ if (nullptr == baseFs) {
+ ERROR("Container %s has no base fs", task->task_id);
+@@ -573,7 +581,7 @@ auto SandboxerSandbox::PrepareExec(const char *containerId, const char *execId,
+ }
+ process = process_wrapper->move();
+ if (InitApiSandbox(apiSandbox) != 0) {
+- ERROR("Failed to init %s api sandbox.", containerId);
++ ERROR("Failed to update %s api sandbox.", containerId);
+ goto del_out;
+ }
+ if (DoSandboxUpdate(apiSandbox) != 0) {
+--
+2.34.1
+