From 7a2410da66d776a4be1b4e793f4c038c49d6d517 Mon Sep 17 00:00:00 2001 From: xuyongfei Date: Wed, 18 Nov 2020 21:37:23 +0800 Subject: [PATCH] =?UTF-8?q?=EF=BC=9Aserving=200.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format | 151 ++ .gitignore | 61 + .gitmodules | 3 + CMakeLists.txt | 61 + README.md | 150 ++ README_CN.md | 185 ++ RELEASE.md | 12 + build.sh | 134 ++ cmake/check_requirements.cmake | 39 + cmake/dependency_ms.cmake | 13 + cmake/dependency_securec.cmake | 22 + cmake/dependency_utils.cmake | 25 + cmake/external_libs/absl.cmake | 14 + cmake/external_libs/c-ares.cmake | 12 + cmake/external_libs/glog.cmake | 17 + cmake/external_libs/grpc.cmake | 110 + cmake/external_libs/gtest.cmake | 13 + cmake/external_libs/json.cmake | 9 + cmake/external_libs/libevent.cmake | 13 + cmake/external_libs/protobuf.cmake | 120 + cmake/external_libs/pybind11.cmake | 12 + cmake/external_libs/zlib.cmake | 9 + cmake/mind_expression.cmake | 32 + cmake/options.cmake | 53 + cmake/package.cmake | 70 + cmake/package_script.cmake | 75 + cmake/utils.cmake | 422 ++++ docs/GRPC.md | 167 ++ docs/MODEL.md | 147 ++ docs/RESTful.md | 231 ++ docs/image/architecture.png | Bin 0 -> 1195614 bytes docs/image/matmul_without_batch.png | Bin 0 -> 9499 bytes docs/image/resnet_example.png | Bin 0 -> 72658 bytes docs/image/resnet_with_batch.png | Bin 0 -> 20638 bytes mindspore_serving/CMakeLists.txt | 137 ++ mindspore_serving/__init__.py | 14 + .../ccsrc/common/buffer_tensor.cc | 63 + .../ccsrc/common/buffer_tensor.h | 62 + mindspore_serving/ccsrc/common/exit_handle.cc | 62 + mindspore_serving/ccsrc/common/exit_handle.h | 49 + .../ccsrc/common/file_system_operation.cc | 72 + .../ccsrc/common/file_system_operation.h | 33 + mindspore_serving/ccsrc/common/grpc_server.cc | 68 + mindspore_serving/ccsrc/common/grpc_server.h | 52 + mindspore_serving/ccsrc/common/instance.h | 73 + mindspore_serving/ccsrc/common/log.cc | 103 + mindspore_serving/ccsrc/common/log.h | 137 ++ .../ccsrc/common/proto_tensor.cc | 377 +++ mindspore_serving/ccsrc/common/proto_tensor.h | 82 + mindspore_serving/ccsrc/common/servable.cc | 295 +++ mindspore_serving/ccsrc/common/servable.h | 122 + .../ccsrc/common/serving_common.h | 26 + mindspore_serving/ccsrc/common/status.h | 75 + mindspore_serving/ccsrc/common/tensor.cc | 113 + mindspore_serving/ccsrc/common/tensor.h | 119 + mindspore_serving/ccsrc/common/tensor_base.cc | 147 ++ mindspore_serving/ccsrc/common/tensor_base.h | 116 + mindspore_serving/ccsrc/common/thread_pool.cc | 69 + mindspore_serving/ccsrc/common/thread_pool.h | 74 + mindspore_serving/ccsrc/master/dispacther.cc | 346 +++ mindspore_serving/ccsrc/master/dispacther.h | 71 + .../ccsrc/master/grpc/grpc_process.cc | 153 ++ .../ccsrc/master/grpc/grpc_process.h | 68 + .../ccsrc/master/restful/http_handle.cc | 146 ++ .../ccsrc/master/restful/http_handle.h | 38 + .../ccsrc/master/restful/http_process.cc | 1224 ++++++++++ .../ccsrc/master/restful/http_process.h | 110 + .../ccsrc/master/restful/restful_request.cc | 208 ++ .../ccsrc/master/restful/restful_request.h | 70 + .../ccsrc/master/restful/restful_server.cc | 211 ++ .../ccsrc/master/restful/restful_server.h | 70 + mindspore_serving/ccsrc/master/server.cc | 70 + mindspore_serving/ccsrc/master/server.h | 51 + .../ccsrc/python/master/master_py.cc | 52 + .../ccsrc/python/master/master_py.h | 49 + mindspore_serving/ccsrc/python/serving_py.cc | 146 ++ mindspore_serving/ccsrc/python/tensor_py.cc | 259 ++ mindspore_serving/ccsrc/python/tensor_py.h | 98 + .../ccsrc/python/worker/postprocess_py.cc | 82 + .../ccsrc/python/worker/postprocess_py.h | 64 + .../ccsrc/python/worker/preprocess_py.cc | 86 + .../ccsrc/python/worker/preprocess_py.h | 66 + .../ccsrc/python/worker/worker_py.cc | 129 + .../ccsrc/python/worker/worker_py.h | 52 + mindspore_serving/ccsrc/worker/context.cc | 60 + mindspore_serving/ccsrc/worker/context.h | 46 + .../ccsrc/worker/grpc/worker_process.cc | 68 + .../ccsrc/worker/grpc/worker_process.h | 41 + .../ccsrc/worker/inference/inference.h | 209 ++ .../worker/inference/mindspore_model_wrap.cc | 342 +++ .../worker/inference/mindspore_model_wrap.h | 122 + mindspore_serving/ccsrc/worker/model.cc | 37 + mindspore_serving/ccsrc/worker/model.h | 67 + .../ccsrc/worker/notfiy_master/base_notify.h | 38 + .../ccsrc/worker/notfiy_master/grpc_notfiy.cc | 150 ++ .../ccsrc/worker/notfiy_master/grpc_notify.h | 50 + .../worker/notfiy_master/local_notify.cc | 37 + .../ccsrc/worker/notfiy_master/local_notify.h | 37 + mindspore_serving/ccsrc/worker/postprocess.cc | 47 + mindspore_serving/ccsrc/worker/postprocess.h | 63 + .../ccsrc/worker/predict_thread.cc | 93 + .../ccsrc/worker/predict_thread.h | 56 + mindspore_serving/ccsrc/worker/preprocess.cc | 47 + mindspore_serving/ccsrc/worker/preprocess.h | 63 + .../ccsrc/worker/register/argmax.cc | 89 + mindspore_serving/ccsrc/worker/task_queue.cc | 340 +++ mindspore_serving/ccsrc/worker/task_queue.h | 138 ++ .../version_control/version_controller.cc | 67 + .../version_control/version_controller.h | 50 + mindspore_serving/ccsrc/worker/worker.cc | 520 ++++ mindspore_serving/ccsrc/worker/worker.h | 119 + .../ccsrc/worker/worker_execute.cc | 546 +++++ .../ccsrc/worker/worker_execute.h | 104 + mindspore_serving/client/__init__.py | 16 + mindspore_serving/client/cpp/client.cc | 448 ++++ mindspore_serving/client/cpp/client.h | 248 ++ mindspore_serving/client/python/__init__.py | 14 + mindspore_serving/client/python/client.py | 201 ++ .../example/add/add/servable_config.py | 51 + mindspore_serving/example/add/client.py | 57 + .../example/add/export_model/add_model.py | 61 + .../example/add/master_with_worker.py | 30 + mindspore_serving/example/lenet/client.py | 37 + .../example/lenet/cpp/lenet_client.cc | 175 ++ .../example/lenet/lenet/servable_config.py | 64 + mindspore_serving/example/lenet/master.py | 26 + .../example/lenet/master_with_worker.py | 30 + mindspore_serving/example/lenet/worker.py | 28 + .../example/python_client/ms_client.py | 136 ++ mindspore_serving/master/__init__.py | 15 + mindspore_serving/master/_master.py | 72 + mindspore_serving/proto/ms_master.proto | 74 + mindspore_serving/proto/ms_service.proto | 93 + mindspore_serving/proto/ms_worker.proto | 27 + mindspore_serving/worker/__init__.py | 17 + mindspore_serving/worker/_worker.py | 182 ++ mindspore_serving/worker/check_type.py | 51 + mindspore_serving/worker/common.py | 35 + mindspore_serving/worker/context.py | 60 + mindspore_serving/worker/register/__init__.py | 2 + mindspore_serving/worker/register/method.py | 266 +++ .../worker/register/postprocess.py | 72 + .../worker/register/preprocess.py | 72 + mindspore_serving/worker/register/servable.py | 48 + mindspore_serving/worker/task.py | 253 ++ scripts/format_source_code.sh | 105 + setup.py | 225 ++ .../python/servables/add/servable_config.py | 51 + .../add_error_model_type/servable_config.py | 51 + .../servables/export_model/add_model.py | 61 + tests/ut/python/test_worker_start_servable.py | 43 + third_party/mindspore | 1 + third_party/securec/CMakeLists.txt | 14 + third_party/securec/include/securec.h | 634 +++++ third_party/securec/include/securectype.h | 542 +++++ third_party/securec/src/CMakeLists.txt | 6 + third_party/securec/src/fscanf_s.c | 56 + third_party/securec/src/fwscanf_s.c | 55 + third_party/securec/src/gets_s.c | 75 + third_party/securec/src/input.inl | 2125 +++++++++++++++++ third_party/securec/src/memcpy_s.c | 577 +++++ third_party/securec/src/memmove_s.c | 120 + third_party/securec/src/memset_s.c | 522 ++++ third_party/securec/src/output.inl | 1401 +++++++++++ third_party/securec/src/scanf_s.c | 55 + third_party/securec/src/secinput.h | 156 ++ third_party/securec/src/securecutil.c | 74 + third_party/securec/src/securecutil.h | 541 +++++ third_party/securec/src/secureinput_a.c | 25 + third_party/securec/src/secureinput_w.c | 46 + third_party/securec/src/secureprintoutput.h | 98 + third_party/securec/src/secureprintoutput_a.c | 101 + third_party/securec/src/secureprintoutput_w.c | 170 ++ third_party/securec/src/snprintf_s.c | 113 + third_party/securec/src/sprintf_s.c | 61 + third_party/securec/src/sscanf_s.c | 61 + third_party/securec/src/strcat_s.c | 102 + third_party/securec/src/strcpy_s.c | 351 +++ third_party/securec/src/strncat_s.c | 121 + third_party/securec/src/strncpy_s.c | 143 ++ third_party/securec/src/strtok_s.c | 117 + third_party/securec/src/swprintf_s.c | 51 + third_party/securec/src/swscanf_s.c | 57 + third_party/securec/src/vfscanf_s.c | 67 + third_party/securec/src/vfwscanf_s.c | 66 + third_party/securec/src/vscanf_s.c | 68 + third_party/securec/src/vsnprintf_s.c | 149 ++ third_party/securec/src/vsprintf_s.c | 73 + third_party/securec/src/vsscanf_s.c | 88 + third_party/securec/src/vswprintf_s.c | 66 + third_party/securec/src/vswscanf_s.c | 79 + third_party/securec/src/vwscanf_s.c | 67 + third_party/securec/src/wcscat_s.c | 111 + third_party/securec/src/wcscpy_s.c | 91 + third_party/securec/src/wcsncat_s.c | 118 + third_party/securec/src/wcsncpy_s.c | 111 + third_party/securec/src/wcstok_s.c | 116 + third_party/securec/src/wmemcpy_s.c | 68 + third_party/securec/src/wmemmove_s.c | 67 + third_party/securec/src/wscanf_s.c | 55 + 200 files changed, 26015 insertions(+) create mode 100644 .clang-format create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 README_CN.md create mode 100644 RELEASE.md create mode 100644 build.sh create mode 100644 cmake/check_requirements.cmake create mode 100644 cmake/dependency_ms.cmake create mode 100644 cmake/dependency_securec.cmake create mode 100644 cmake/dependency_utils.cmake create mode 100644 cmake/external_libs/absl.cmake create mode 100644 cmake/external_libs/c-ares.cmake create mode 100644 cmake/external_libs/glog.cmake create mode 100644 cmake/external_libs/grpc.cmake create mode 100644 cmake/external_libs/gtest.cmake create mode 100644 cmake/external_libs/json.cmake create mode 100644 cmake/external_libs/libevent.cmake create mode 100644 cmake/external_libs/protobuf.cmake create mode 100644 cmake/external_libs/pybind11.cmake create mode 100644 cmake/external_libs/zlib.cmake create mode 100644 cmake/mind_expression.cmake create mode 100644 cmake/options.cmake create mode 100644 cmake/package.cmake create mode 100644 cmake/package_script.cmake create mode 100644 cmake/utils.cmake create mode 100644 docs/GRPC.md create mode 100644 docs/MODEL.md create mode 100644 docs/RESTful.md create mode 100644 docs/image/architecture.png create mode 100644 docs/image/matmul_without_batch.png create mode 100644 docs/image/resnet_example.png create mode 100644 docs/image/resnet_with_batch.png create mode 100644 mindspore_serving/CMakeLists.txt create mode 100644 mindspore_serving/__init__.py create mode 100644 mindspore_serving/ccsrc/common/buffer_tensor.cc create mode 100644 mindspore_serving/ccsrc/common/buffer_tensor.h create mode 100644 mindspore_serving/ccsrc/common/exit_handle.cc create mode 100644 mindspore_serving/ccsrc/common/exit_handle.h create mode 100644 mindspore_serving/ccsrc/common/file_system_operation.cc create mode 100644 mindspore_serving/ccsrc/common/file_system_operation.h create mode 100644 mindspore_serving/ccsrc/common/grpc_server.cc create mode 100644 mindspore_serving/ccsrc/common/grpc_server.h create mode 100644 mindspore_serving/ccsrc/common/instance.h create mode 100644 mindspore_serving/ccsrc/common/log.cc create mode 100644 mindspore_serving/ccsrc/common/log.h create mode 100644 mindspore_serving/ccsrc/common/proto_tensor.cc create mode 100644 mindspore_serving/ccsrc/common/proto_tensor.h create mode 100644 mindspore_serving/ccsrc/common/servable.cc create mode 100644 mindspore_serving/ccsrc/common/servable.h create mode 100644 mindspore_serving/ccsrc/common/serving_common.h create mode 100644 mindspore_serving/ccsrc/common/status.h create mode 100644 mindspore_serving/ccsrc/common/tensor.cc create mode 100644 mindspore_serving/ccsrc/common/tensor.h create mode 100644 mindspore_serving/ccsrc/common/tensor_base.cc create mode 100644 mindspore_serving/ccsrc/common/tensor_base.h create mode 100644 mindspore_serving/ccsrc/common/thread_pool.cc create mode 100644 mindspore_serving/ccsrc/common/thread_pool.h create mode 100644 mindspore_serving/ccsrc/master/dispacther.cc create mode 100644 mindspore_serving/ccsrc/master/dispacther.h create mode 100644 mindspore_serving/ccsrc/master/grpc/grpc_process.cc create mode 100644 mindspore_serving/ccsrc/master/grpc/grpc_process.h create mode 100644 mindspore_serving/ccsrc/master/restful/http_handle.cc create mode 100644 mindspore_serving/ccsrc/master/restful/http_handle.h create mode 100644 mindspore_serving/ccsrc/master/restful/http_process.cc create mode 100644 mindspore_serving/ccsrc/master/restful/http_process.h create mode 100644 mindspore_serving/ccsrc/master/restful/restful_request.cc create mode 100644 mindspore_serving/ccsrc/master/restful/restful_request.h create mode 100644 mindspore_serving/ccsrc/master/restful/restful_server.cc create mode 100644 mindspore_serving/ccsrc/master/restful/restful_server.h create mode 100644 mindspore_serving/ccsrc/master/server.cc create mode 100644 mindspore_serving/ccsrc/master/server.h create mode 100644 mindspore_serving/ccsrc/python/master/master_py.cc create mode 100644 mindspore_serving/ccsrc/python/master/master_py.h create mode 100644 mindspore_serving/ccsrc/python/serving_py.cc create mode 100644 mindspore_serving/ccsrc/python/tensor_py.cc create mode 100644 mindspore_serving/ccsrc/python/tensor_py.h create mode 100644 mindspore_serving/ccsrc/python/worker/postprocess_py.cc create mode 100644 mindspore_serving/ccsrc/python/worker/postprocess_py.h create mode 100644 mindspore_serving/ccsrc/python/worker/preprocess_py.cc create mode 100644 mindspore_serving/ccsrc/python/worker/preprocess_py.h create mode 100644 mindspore_serving/ccsrc/python/worker/worker_py.cc create mode 100644 mindspore_serving/ccsrc/python/worker/worker_py.h create mode 100644 mindspore_serving/ccsrc/worker/context.cc create mode 100644 mindspore_serving/ccsrc/worker/context.h create mode 100644 mindspore_serving/ccsrc/worker/grpc/worker_process.cc create mode 100644 mindspore_serving/ccsrc/worker/grpc/worker_process.h create mode 100644 mindspore_serving/ccsrc/worker/inference/inference.h create mode 100644 mindspore_serving/ccsrc/worker/inference/mindspore_model_wrap.cc create mode 100644 mindspore_serving/ccsrc/worker/inference/mindspore_model_wrap.h create mode 100644 mindspore_serving/ccsrc/worker/model.cc create mode 100644 mindspore_serving/ccsrc/worker/model.h create mode 100644 mindspore_serving/ccsrc/worker/notfiy_master/base_notify.h create mode 100644 mindspore_serving/ccsrc/worker/notfiy_master/grpc_notfiy.cc create mode 100644 mindspore_serving/ccsrc/worker/notfiy_master/grpc_notify.h create mode 100644 mindspore_serving/ccsrc/worker/notfiy_master/local_notify.cc create mode 100644 mindspore_serving/ccsrc/worker/notfiy_master/local_notify.h create mode 100644 mindspore_serving/ccsrc/worker/postprocess.cc create mode 100644 mindspore_serving/ccsrc/worker/postprocess.h create mode 100644 mindspore_serving/ccsrc/worker/predict_thread.cc create mode 100644 mindspore_serving/ccsrc/worker/predict_thread.h create mode 100644 mindspore_serving/ccsrc/worker/preprocess.cc create mode 100644 mindspore_serving/ccsrc/worker/preprocess.h create mode 100644 mindspore_serving/ccsrc/worker/register/argmax.cc create mode 100644 mindspore_serving/ccsrc/worker/task_queue.cc create mode 100644 mindspore_serving/ccsrc/worker/task_queue.h create mode 100644 mindspore_serving/ccsrc/worker/version_control/version_controller.cc create mode 100644 mindspore_serving/ccsrc/worker/version_control/version_controller.h create mode 100644 mindspore_serving/ccsrc/worker/worker.cc create mode 100644 mindspore_serving/ccsrc/worker/worker.h create mode 100644 mindspore_serving/ccsrc/worker/worker_execute.cc create mode 100644 mindspore_serving/ccsrc/worker/worker_execute.h create mode 100644 mindspore_serving/client/__init__.py create mode 100644 mindspore_serving/client/cpp/client.cc create mode 100644 mindspore_serving/client/cpp/client.h create mode 100644 mindspore_serving/client/python/__init__.py create mode 100644 mindspore_serving/client/python/client.py create mode 100644 mindspore_serving/example/add/add/servable_config.py create mode 100644 mindspore_serving/example/add/client.py create mode 100644 mindspore_serving/example/add/export_model/add_model.py create mode 100644 mindspore_serving/example/add/master_with_worker.py create mode 100644 mindspore_serving/example/lenet/client.py create mode 100644 mindspore_serving/example/lenet/cpp/lenet_client.cc create mode 100644 mindspore_serving/example/lenet/lenet/servable_config.py create mode 100644 mindspore_serving/example/lenet/master.py create mode 100644 mindspore_serving/example/lenet/master_with_worker.py create mode 100644 mindspore_serving/example/lenet/worker.py create mode 100644 mindspore_serving/example/python_client/ms_client.py create mode 100644 mindspore_serving/master/__init__.py create mode 100644 mindspore_serving/master/_master.py create mode 100644 mindspore_serving/proto/ms_master.proto create mode 100644 mindspore_serving/proto/ms_service.proto create mode 100644 mindspore_serving/proto/ms_worker.proto create mode 100644 mindspore_serving/worker/__init__.py create mode 100644 mindspore_serving/worker/_worker.py create mode 100644 mindspore_serving/worker/check_type.py create mode 100644 mindspore_serving/worker/common.py create mode 100644 mindspore_serving/worker/context.py create mode 100644 mindspore_serving/worker/register/__init__.py create mode 100644 mindspore_serving/worker/register/method.py create mode 100644 mindspore_serving/worker/register/postprocess.py create mode 100644 mindspore_serving/worker/register/preprocess.py create mode 100644 mindspore_serving/worker/register/servable.py create mode 100644 mindspore_serving/worker/task.py create mode 100755 scripts/format_source_code.sh create mode 100644 setup.py create mode 100644 tests/ut/python/servables/add/servable_config.py create mode 100644 tests/ut/python/servables/add_error_model_type/servable_config.py create mode 100644 tests/ut/python/servables/export_model/add_model.py create mode 100644 tests/ut/python/test_worker_start_servable.py create mode 160000 third_party/mindspore create mode 100644 third_party/securec/CMakeLists.txt create mode 100644 third_party/securec/include/securec.h create mode 100644 third_party/securec/include/securectype.h create mode 100644 third_party/securec/src/CMakeLists.txt create mode 100644 third_party/securec/src/fscanf_s.c create mode 100644 third_party/securec/src/fwscanf_s.c create mode 100644 third_party/securec/src/gets_s.c create mode 100644 third_party/securec/src/input.inl create mode 100644 third_party/securec/src/memcpy_s.c create mode 100644 third_party/securec/src/memmove_s.c create mode 100644 third_party/securec/src/memset_s.c create mode 100644 third_party/securec/src/output.inl create mode 100644 third_party/securec/src/scanf_s.c create mode 100644 third_party/securec/src/secinput.h create mode 100644 third_party/securec/src/securecutil.c create mode 100644 third_party/securec/src/securecutil.h create mode 100644 third_party/securec/src/secureinput_a.c create mode 100644 third_party/securec/src/secureinput_w.c create mode 100644 third_party/securec/src/secureprintoutput.h create mode 100644 third_party/securec/src/secureprintoutput_a.c create mode 100644 third_party/securec/src/secureprintoutput_w.c create mode 100644 third_party/securec/src/snprintf_s.c create mode 100644 third_party/securec/src/sprintf_s.c create mode 100644 third_party/securec/src/sscanf_s.c create mode 100644 third_party/securec/src/strcat_s.c create mode 100644 third_party/securec/src/strcpy_s.c create mode 100644 third_party/securec/src/strncat_s.c create mode 100644 third_party/securec/src/strncpy_s.c create mode 100644 third_party/securec/src/strtok_s.c create mode 100644 third_party/securec/src/swprintf_s.c create mode 100644 third_party/securec/src/swscanf_s.c create mode 100644 third_party/securec/src/vfscanf_s.c create mode 100644 third_party/securec/src/vfwscanf_s.c create mode 100644 third_party/securec/src/vscanf_s.c create mode 100644 third_party/securec/src/vsnprintf_s.c create mode 100644 third_party/securec/src/vsprintf_s.c create mode 100644 third_party/securec/src/vsscanf_s.c create mode 100644 third_party/securec/src/vswprintf_s.c create mode 100644 third_party/securec/src/vswscanf_s.c create mode 100644 third_party/securec/src/vwscanf_s.c create mode 100644 third_party/securec/src/wcscat_s.c create mode 100644 third_party/securec/src/wcscpy_s.c create mode 100644 third_party/securec/src/wcsncat_s.c create mode 100644 third_party/securec/src/wcsncpy_s.c create mode 100644 third_party/securec/src/wcstok_s.c create mode 100644 third_party/securec/src/wmemcpy_s.c create mode 100644 third_party/securec/src/wmemmove_s.c create mode 100644 third_party/securec/src/wscanf_s.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..fb2799b --- /dev/null +++ b/.clang-format @@ -0,0 +1,151 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: +# - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Right +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' + BasedOnStyle: google +ReflowComments: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 2 +UseTab: Never +SortIncludes: false +... + diff --git a/.gitignore b/.gitignore index 259148f..dc734c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,16 @@ +# MindSpore Serving +build/ +mindspore_serving/lib +output +*.ir + +# Cmake files +CMakeFiles/ +cmake_install.cmake +CMakeCache.txt +Makefile +cmake-build-debug + # Prerequisites *.d @@ -15,6 +28,7 @@ *.so *.dylib *.dll +*.so.* # Fortran module files *.mod @@ -30,3 +44,50 @@ *.exe *.out *.app + +# Protocol buffers +*_pb2.py +*.pb.h +*.pb.cc +*.pb +*_grpc.py + +# Editor +.vscode +.idea/ + +# Cquery +.cquery_cached_index/ +compile_commands.json + +# Ctags and cscope +tags +TAGS +CTAGS +GTAGS +GRTAGS +GSYMS +GPATH +cscope.* + +# Python files +*__pycache__* +.pytest_cache + +# Mac files +*.DS_Store + +# Test results +test_temp_summary_event_file/ +*.dot +*.dat +*.svg +*.perf +*.info +*.ckpt +*.shp +*.pkl +.clangd +mindspore_serving/version.py +mindspore_serving/default_config.py +mindspore_serving/.commit_id diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..741ca06 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third_party/mindspore"] + path = third_party/mindspore + url = https://gitee.com/mindspore/mindspore.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b045b66 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.14.1) +project (MindSpore_Serving) + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.3.0) + message(FATAL_ERROR "GCC vesion ${CMAKE_CXX_COMPILER_VERSION} must not be less than 7.3.0") +endif () + +include(${CMAKE_SOURCE_DIR}/cmake/options.cmake) # set compile options +include(${CMAKE_SOURCE_DIR}/cmake/check_requirements.cmake) # check require party, like OpenSSL +set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O2 -Wl,--allow-shlib-undefined -DHALF_ENABLE_CPP11_USER_LITERALS=0 -D_FORTIFY_SOURCE=2") +if (NOT CMAKE_SYSTEM_NAME MATCHES "Windows") + add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0) +endif () + +if (ENABLE_PYTHON) + add_compile_definitions(ENABLE_PYTHON) +endif() + +set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -g2 -ggdb -fno-inline-functions -fno-omit-frame-pointer -Wl,--allow-shlib-undefined -D_LIBCPP_INLINE_VISIBILITY='' -D_LIBCPP_DISABLE_EXTERN_TEMPLATE=1 -DHALF_ENABLE_CPP11_USER_LITERALS=0 -D_FORTIFY_SOURCE=2 -Wno-cpp") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I/usr/local/include -std=c++17 -Werror -Wall -fPIC") +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(PYBIND11_CPP_STANDARD -std=c++17) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPTION_CXX_FLAGS}") + +# compile third party: grpc, libevent, gtest, onnx +include(${CMAKE_SOURCE_DIR}/cmake/mind_expression.cmake) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/securec/include) + +# find python3 packages +include(${CMAKE_SOURCE_DIR}/cmake/dependency_utils.cmake) +find_package(Python3 3.7 COMPONENTS Interpreter Development) +if(Python3_FOUND) + set(PYTHON_INCLUDE_DIRS "${Python3_INCLUDE_DIRS}") + set(PYTHON_LIBRARIES "${Python3_LIBRARIES}") + if (WIN32) + if (Python3_DIR) + message("Python3_DIR set already: " ${Python3_DIR}) + else() + string(LENGTH ${PYTHON_LIBRARIES} PYTHON_LIBRARIES_LEN) + string(LENGTH "libpythonxx.a" Python3_NAME_LEN) + math(EXPR Python3_DIR_LEN ${PYTHON_LIBRARIES_LEN}-${Python3_NAME_LEN}) + string(SUBSTRING ${Python3_LIBRARIES} 0 ${Python3_DIR_LEN} Python3_DIR) + message("Python3_DIR: " ${Python3_DIR}) + endif() + link_directories(${Python3_DIR}) + endif() +else() + find_python_package(py_inc py_lib) + set(PYTHON_INCLUDE_DIRS "${py_inc}") + set(PYTHON_LIBRARIES "${py_lib}") +endif() +message("PYTHON_INCLUDE_DIRS = ${PYTHON_INCLUDE_DIRS}") +message("PYTHON_LIBRARIES = ${PYTHON_LIBRARIES}") +include_directories(${PYTHON_INCLUDE_DIRS}) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") +find_package(Threads REQUIRED) +add_subdirectory(mindspore_serving) +include(cmake/package.cmake) diff --git a/README.md b/README.md new file mode 100644 index 0000000..0529495 --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ +# MindSpore-based Inference Service Deployment + + + + +- [MindSpore-based Inference Service Deployment](#mindspore-based-serving-service-deployment) + - [Overview](#overview) + - [Starting Serving](#starting-serving) + - [Application Example](#application-example) + - [Exporting Model](#exporting-model) + - [Starting Serving Inference](#starting-serving-serving) + - [Client Samples](#client-samples) + - [Python Client Sample](#python-client-sample) + - [C++ Client Sample](#cpp-client-sample) + + + + + +## Overview + +MindSpore Serving is a lightweight and high-performance service module that helps MindSpore developers efficiently deploy online serving services in the production environment. After completing model training using MindSpore, you can export the MindSpore model and use MindSpore Serving to create an serving service for the model. Currently, only Ascend 910 is supported. + + +## Starting Serving +After MindSpore is installed using `pip`, the Serving executable program is stored in `/{your python path}/lib/python3.7/site-packages/mindspore/ms_serving`. +Run the following command to start Serving: +```bash +ms_serving [--help] [--model_path ] [--model_name ] + [--port ] [--device_id ] +``` +Parameters are described as follows: + +|Parameter|Attribute|Function|Parameter Type|Default Value|Value Range| +|---|---|---|---|---|---| +|`--help`|Optional|Displays the help information about the startup command. |-|-|-| +|`--model_path=`|Mandatory|Path for storing the model to be loaded. |String|Null|-| +|`--model_name=`|Mandatory|Name of the model file to be loaded. |String|Null|-| +|`--=port `|Optional|Specifies the external Serving port number. |Integer|5500|1–65535| +|`--device_id=`|Optional|Specifies device ID to be used.|Integer|0|0 to 7| + + > Before running the startup command, add the path `/{your python path}/lib:/{your python path}/lib/python3.7/site-packages/mindspore/lib` to the environment variable `LD_LIBRARY_PATH`. + +## Application Example +The following uses a simple network as an example to describe how to use MindSpore Serving. + +### Exporting Model +Use [add_model.py](https://gitee.com/mindspore/mindspore/blob/master/serving/example/export_model/add_model.py) to build a network with only the Add operator and export the MindSpore serving deployment model. + +```python +python add_model.py +``` +Execute the script to generate the `tensor_add.mindir` file. The input of the model is two one-dimensional tensors with shape [2,2], and the output is the sum of the two input tensors. + +### Starting Serving Inference +```bash +ms_serving --model_path={model directory} --model_name=tensor_add.mindir +``` +If the server prints the `MS Serving Listening on 0.0.0.0:5500` log, the Serving has loaded the serving model. + +### Client Samples +#### Python Client Sample +Obtain [ms_client.py](https://gitee.com/mindspore/mindspore/blob/master/serving/example/python_client/ms_client.py) and start the Python client. +```bash +python ms_client.py +``` + +If the following information is displayed, the Serving has correctly executed the serving of the Add network. +``` +ms client received: +[[2. 2.] + [2. 2.]] +``` + +#### C++ Client Sample +1. Obtain an executable client sample program. + + Download the [MindSpore source code](https://gitee.com/mindspore/mindspore). You can use either of the following methods to compile and obtain the client sample program: + + When MindSpore is compiled using the source code, the Serving C++ client sample program is generated. You can find the `ms_client` executable program in the `build/mindspore/serving/example/cpp_client` directory. + + Independent compilation + + Preinstall [gRPC](https://gRPC.io). + + Run the following command in the MindSpore source code path to compile a client sample program: + ```bash + cd mindspore/serving/example/cpp_client + mkdir build && cd build + cmake -D GRPC_PATH={grpc_install_dir} .. + make + ``` + In the preceding command, `{grpc_install_dir}` indicates the gRPC installation path. Replace it with the actual gRPC installation path. + +2. Start the client. + + Execute `ms_client` to send an serving request to the Serving. + ```bash + ./ms_client --target=localhost:5500 + ``` + If the following information is displayed, the Serving has correctly executed the serving of the Add network. + ``` + Compute [[1, 2], [3, 4]] + [[1, 2], [3, 4]] + Add result is 2 4 6 8 + client received: RPC OK + ``` + +The client code consists of the following parts: + +1. Implement the client based on MSService::Stub and create a client instance. + ``` + class MSClient { + public: + explicit MSClient(std::shared_ptr channel) : stub_(MSService::NewStub(channel)) {} + private: + std::unique_ptr stub_; + };MSClient client(grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials())); + + MSClient client(grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials())); + + ``` +2. Build the request input parameter `Request`, output parameter `Reply`, and gRPC client `Context` based on the actual network input. + ``` + PredictRequest request; + PredictReply reply; + ClientContext context; + + //construct tensor + Tensor data; + + //set shape + TensorShape shape; + shape.add_dims(4); + *data.mutable_tensor_shape() = shape; + + //set type + data.set_tensor_type(ms_serving::MS_FLOAT32); + std::vector input_data{1, 2, 3, 4}; + + //set datas + data.set_data(input_data.data(), input_data.size()); + + //add tensor to request + *request.add_data() = data; + *request.add_data() = data; + ``` +3. Call the gRPC API to communicate with the Serving that has been started, and obtain the return value. + ``` + Status status = stub_->Predict(&context, request, &reply); + ``` + +For details about the complete code, see [ms_client](https://gitee.com/mindspore/mindspore/blob/master/serving/example/cpp_client/ms_client.cc). diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..05a052b --- /dev/null +++ b/README_CN.md @@ -0,0 +1,185 @@ +# MindSpore Serving + +[View English](./README.md) + +- [概述](#概述) +- [安装部署](#安装部署) + - [安装MindSpore Serving](#安装MindSpore Serving) + - [配置环境变量](#配置环境变量) + - [部署MindSpore Serving](#部署MindSpore Serving) +- [快速入门](#快速入门) + - [导出模型](#导出模型) + - [部署Serving推理服务](#部署serving推理服务) + - [执行推理](#执行推理) +- [文档](#文档) + - [开发者教程](#开发者教程) +- [社区](#社区) + - [治理](#治理) + - [交流](#交流) +- [贡献](#贡献) +- [版本说明](#版本说明) +- [许可证](#许可证) + +## 概述 + +MindSpore Serving是一个轻量级、高性能的服务模块,旨在帮助MindSpore开发者在生产环境中高效部署在线推理服务。当用户使用MindSpore完成模型训练后,导出MindSpore模型,即可使用MindSpore Serving创建该模型的推理服务。 + +**MindSpore Serving架构:** +当前MindSpore Serving服务节点分为client,master和worker。client为用户节点,下发推理服务命令。执行机worker部署了模型服务。当前仅支持Ascend 310和Ascend 910,后续会逐步支持GPU和CPU场景。master节点用来管理所有的执行机worker及其部署的模型信息,并进行任务管理与分发。master和worker可以部署在一个进程中,也可以部署在不同进程中。 +MindSpore Architecture + +**MindSpore Serving提供以下功能:** +- 支持客户端gRPC和RESTful接口 +- 支持组装模型的前处理和后处理 +- 支持batch功能 +- 提供客户端python简易接口 + +## 安装部署 +MindSpore Serving依赖MindSpore训练推理框架,安装完[MindSpore](https://gitee.com/mindspore/mindspore#%E5%AE%89%E8%A3%85) ,再安装MindSpore Serving。 + +### 安装MindSpore Serving +使用pip命令安装,安装方式如下: + +**1、请从MindSpore Serving下载页面下载并安装whl包。** +```python +pip install mindspore_serving-1.0.0-cp37-cp37m-linux_x86_64.whl +``` +**2、源码安装。** +下载[源码](https://gitee.com/mindspore/serving)。 + +方式一,使用已安装或编译的MindSpore包: +```shell +sh build.sh -p $MINDSPORE_LIB_PATH +``` +$MINDSPORE_LIB_PATH为mindspore软件包的安装路径下的lib路径,例:softwarepath/mindspore/lib,该路径包含mindspore运行依赖的库文件。 + +方式二,编译Serving时编译配套的MindSpore包,需要配置MindSpore编译时的[环境变量](https://gitee.com/mindspore/docs/blob/master/install/mindspore_ascend_install_source.md#配置环境变量) : +```shell +# ascend 310 +sh build.sh -eacl +# ascend 910 +sh build.sh -ed +``` + +编译完后,在output/目录下找到安装包进行安装: +```python +pip install mindspore_serving-0.1.0-cp37-cp37m-linux_x86_64.whl +``` +### 配置环境变量 +Asend 910环境上安装mindspore,需要完成[环境变量配置](https://gitee.com/mindspore/docs/blob/master/install/mindspore_ascend_install_pip.md#%E9%85%8D%E7%BD%AE%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F)。 +运行MindSpore Serving,还需要增加额外mindspore软件包的安装路径下的lib路径到LD_LIBRARY_PATH。 +```shell +export LD_LIBRARY_PATH=$MINDSPORE_LIB_PATH:${LD_LIBRARY_PATH} +``` + +### 部署MindSpore Serving +MindSpore Serving提供两种部署方式,用户可根据需要进行选择部署。 + +**轻量级部署:** +服务端调用python接口直接启动推理进程(master和worker共进程),客户端直接连接推理服务后下发推理任务。 +启动服务: +```python +import os +from mindspore_serving import master +from mindspore_serving import worker +servable_dir = os.path.abspath(".") +worker.start_servable_in_master(servable_dir, "xxx", device_id=0) +master.start_grpc_server("127.0.0.1", 5500) +``` + +**集群部署:** +服务端由master进程和worker进程组成,master用来管理集群内所有的worker节点,并进行推理任务的分发。 +启动worker: +```python +import os +from mindspore_serving import worker +servable_dir = os.path.abspath(".") +worker.start_servable(servable_dir, "lenet", device_id=0, + master_ip="127.0.0.1", master_port=5500, + host_ip="127.0.0.1", host_port=5600) +``` +启动master: +```python +from mindspore_serving import master +master.start_grpc_server("127.0.0.1", 5500) +``` +完成服务端部署后,即可启用客户端程序执行推理操作。 + +## 快速入门 +以一个简单的Add网络为例,演示MindSpore Serving如何使用。 + +### 导出模型 +使用[add_model.py](https://gitee.com/mindspore/serving/blob/master/mindspore_serving/example/add/export_model/add_model.py),构造一个只有Add算子的网络,并导出MindSpore推理部署模型。 + +```python +python add_model.py +``` +执行脚本,生成`tensor_add.mindir`文件,该模型的输入为两个shape为[2,2]的二维Tensor,输出结果是两个输入Tensor之和。 + +### 部署Serving推理服务 +执行以下[python程序](https://gitee.com/mindspore/serving/blob/master/mindspore_serving/example/add/master_with_worker.py),启动服务: +```bash +import os +from mindspore_serving import master +from mindspore_serving import worker +def start(): + servable_dir = os.path.abspath(".") + worker.start_servable_in_master(servable_dir, "add", device_id=0) + master.start_grpc_server("127.0.0.1", 5500) +``` +启动过程需要使用servable_dir路径下的模型文件和配置文件,文件目录结果如下图所示: +
add/
+├── 1
+│   └── tensor_add.mindir
+└── servable_config.py
+
+其中,模型文件为上一步网络生成的,即`tensor_add.mindir`文件。配置文件为[servable_config.py](https://gitee.com/mindspore/serving/blob/master/mindspore_serving/example/add/add/servable_config.py),其定义了模型的处理函数,包含前处理和后处理过程。 +当服务端打印日志`Serving gRPC start success, listening on 0.0.0.0:5500`时,表示Serving服务已加载推理模型完毕。 + +### 执行推理 +使用[client.py](https://gitee.com/mindspore/serving/blob/master/mindspore_serving/example/add/client.py),启动Python客户端。 +```bash +python client.py +``` + +显示如下返回值说明Serving服务已正确执行Add网络的推理。 +```bash +[{'y': array([[2. , 2.], + [2., 2.]], dtype=float32)}] +[{'y': array([[2. , 2.], + [2., 2.]], dtype=float32)}] +``` + +## 文档 + +### 开发者教程 +- [如何使用python接口开发客户端?](docs/GRPC.md) +- [如何启动Restful服务进行推理?](docs/RESTful.md) +- [如何实现模型前处理和后处理?](docs/MODEL.md) + +有关安装指南、教程和API的更多详细信息,请参阅[用户文档](https://gitee.com/mindspore/serving/docs)。 + +## 社区 + +### 治理 + +查看MindSpore如何进行[开放治理](https://gitee.com/mindspore/community/blob/master/governance.md)。 + +### 交流 + +- [MindSpore Slack](https://join.slack.com/t/mindspore/shared_invite/zt-dgk65rli-3ex4xvS4wHX7UDmsQmfu8w) 开发者交流平台。 +- `#mindspore`IRC频道(仅用于会议记录) +- 视频会议:待定 +- 邮件列表: + +## 贡献 + +欢迎参与贡献。 + +## 版本说明 + +版本说明请参阅[RELEASE](RELEASE.md)。 + +## 许可证 + +[Apache License 2.0](LICENSE) diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000..947d19f --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,12 @@ +# Release 0.1.0-alpha + +## Main Features + +### Ascend 310 & Ascend 910 Serving Framework +* Recommended OS: Ubuntu 16.04 (or later) or EulerOS 2.5 or EulerOS 2.8 +* Python version: 3.7.5 +* Provide gRPC and RESTful API +* Support preprocessing & postprocessing +* Support batching +* Multiple instances +* Version Control \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..9603039 --- /dev/null +++ b/build.sh @@ -0,0 +1,134 @@ +#!/bin/bash +set -e +PROJECTPATH=$(cd "$(dirname $0)"; pwd) +export BUILD_PATH="${PROJECTPATH}/build/" + +# print usage message +usage() +{ + echo "Usage:" + echo "bash build.sh [-d] [-v] [-c] [-a on|off] [-j[n]] [-p]" + echo "" + echo "Options:" + echo " -d Debug model" + echo " -j[n] Set the threads when building (Default: -j8)" + echo " -v Display build command. Run cpack with verbose output." + echo " -c Enable code coverage, default off" + echo " -a Enable ASAN, default off. Memory error detection tool." + echo " -p MindSpore lib [mindspore_shared_lib] path." + +} + +# check value of input is 'on' or 'off' +# usage: check_on_off arg_value arg_name +check_on_off() +{ + if [[ "X$1" != "Xon" && "X$1" != "Xoff" ]]; then + echo "Invalid value $1 for option -$2" + usage + exit 1 + fi +} + +# check and set options +checkopts() +{ + # Init default values of build options + THREAD_NUM=8 + VERBOSE="" + DEBUG_MODE="off" + ENABLE_COVERAGE="off" + ENABLE_ASAN="off" + ENABLE_PYTHON="on" + MS_WHL_LIB_PATH="" + MS_BACKEND="" + + # Process the options + while getopts 'dvcj:a:p:e:' opt + do + LOW_OPTARG=$(echo ${OPTARG} | tr '[A-Z]' '[a-z]') + + case "${opt}" in + e) + echo "user opt: -e"${LOW_OPTARG} + if [[ "$OPTARG" != "" ]]; then + MS_BACKEND=$OPTARG + fi + ;; + p) + if [[ "$OPTARG" != "" ]]; then + MS_WHL_LIB_PATH=$OPTARG + else + echo "Invalid value ${LOW_OPTARG} for option -e" + usage + exit 1 + fi + ;; + d) + echo "user opt: -d"${LOW_OPTARG} + DEBUG_MODE="on" + ;; + j) + echo "user opt: -j"${LOW_OPTARG} + THREAD_NUM=$OPTARG + ;; + v) + echo "user opt: -v"${LOW_OPTARG} + VERBOSE="VERBOSE=1" + ;; + c) + check_on_off $OPTARG c + ENABLE_COVERAGE="$OPTARG" + ;; + a) + check_on_off $OPTARG a + ENABLE_ASAN="$OPTARG" + ;; + *) + echo "Unknown option ${opt}!" + usage + exit 1 + esac + done +} + +checkopts "$@" +echo "---------------- MindSpore Serving: build start ----------------" +mkdir -pv "${BUILD_PATH}/package/mindspore_serving/lib" +if [[ "$MS_BACKEND" != "" ]]; then + git submodule update --init third_party/mindspore +fi + +# Create building path +build_mindspore_serving() +{ + echo "start build mindspore_serving project." + mkdir -pv "${BUILD_PATH}/mindspore_serving" + cd "${BUILD_PATH}/mindspore_serving" + CMAKE_ARGS="-DDEBUG_MODE=$DEBUG_MODE -DBUILD_PATH=$BUILD_PATH" + CMAKE_ARGS="${CMAKE_ARGS} -DENABLE_PYTHON=${ENABLE_PYTHON}" + CMAKE_ARGS="${CMAKE_ARGS} -DTHREAD_NUM=${THREAD_NUM}" + if [[ "X$ENABLE_COVERAGE" = "Xon" ]]; then + CMAKE_ARGS="${CMAKE_ARGS} -DENABLE_COVERAGE=ON" + fi + if [[ "X$ENABLE_ASAN" = "Xon" ]]; then + CMAKE_ARGS="${CMAKE_ARGS} -DENABLE_ASAN=ON" + fi + if [[ "$MS_BACKEND" != "" ]]; then + CMAKE_ARGS="${CMAKE_ARGS} -DMS_BACKEND=${MS_BACKEND}" + fi + if [[ "$MS_WHL_LIB_PATH" != "" ]]; then + CMAKE_ARGS="${CMAKE_ARGS} -DMS_WHL_LIB_PATH=${MS_WHL_LIB_PATH}" + fi + echo "${CMAKE_ARGS}" + cmake ${CMAKE_ARGS} ../.. + if [[ -n "$VERBOSE" ]]; then + CMAKE_VERBOSE="--verbose" + fi + cmake --build . --target package ${CMAKE_VERBOSE} -j$THREAD_NUM + echo "success building mindspore_serving project!" +} + +build_mindspore_serving + +echo "---------------- mindspore_serving: build end ----------------" diff --git a/cmake/check_requirements.cmake b/cmake/check_requirements.cmake new file mode 100644 index 0000000..266b05d --- /dev/null +++ b/cmake/check_requirements.cmake @@ -0,0 +1,39 @@ +## define customized find fucntions, print customized error messages +function(find_required_package pkg_name) + find_package(${pkg_name}) + if (NOT ${pkg_name}_FOUND) + message(FATAL_ERROR "Required package ${pkg_name} not found, please install the package and try building mindspore_serving again.") + endif() +endfunction() + +## find python, quit if the found python is static +set(Python3_USE_STATIC_LIBS FALSE) +find_package(Python3 COMPONENTS Interpreter Development) +if (Python3_FOUND) + message("Python3 found, version: ${Python3_VERSION}") + message("Python3 library path: ${Python3_LIBRARY_DIRS}") + message("Python3 interpreter: ${Python3_EXECUTABLE}") + else() + message(FATAL_ERROR "Python3 not found, please install Python>=3.7.5, and set --enable-shared " + "if you are building Python locally") +endif () + +## packages used both on windows and linux +if (DEFINED ENV{MS_PATCH_PATH}) + find_program(Patch_EXECUTABLE patch PATHS $ENV{MS_PATCH_PATH}) + set(Patch_FOUND ${Patch_EXECUTABLE}) +else () + find_package(Patch) +endif () +if (NOT Patch_FOUND) + message(FATAL_ERROR "Patch not found, please set environment variable MS_PATCH_PATH to path where Patch is located, " + "usually found in GIT_PATH/usr/bin on Windows") +endif () +message(PATCH_EXECUTABLE = ${Patch_EXECUTABLE}) + +find_required_package(Threads) + +## packages used on Linux +if (NOT CMAKE_SYSTEM_NAME MATCHES "Windows") + find_required_package(OpenSSL) +endif() diff --git a/cmake/dependency_ms.cmake b/cmake/dependency_ms.cmake new file mode 100644 index 0000000..1d83508 --- /dev/null +++ b/cmake/dependency_ms.cmake @@ -0,0 +1,13 @@ +# Compile MindSpore + +message(STATUS "**********begin to compile MindSpore**********") +set(MS_SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/mindspore) +message(STATUS "MindSpore dir: ${MS_SOURCE_DIR}") +message(STATUS "MindSpore compile method: -e${MS_BACKEND}") +message(STATUS "MindSpore compile thread num: -j${THREAD_NUM}") + +execute_process( + COMMAND bash ${MS_SOURCE_DIR}/build.sh -e${MS_BACKEND} -j${THREAD_NUM} + WORKING_DIRECTORY ${MS_SOURCE_DIR} +) +message(STATUS "**********end to compile MindSpore**********") \ No newline at end of file diff --git a/cmake/dependency_securec.cmake b/cmake/dependency_securec.cmake new file mode 100644 index 0000000..7ff5aca --- /dev/null +++ b/cmake/dependency_securec.cmake @@ -0,0 +1,22 @@ +# securec library +# +# +# SECUREC_LIBRARY +# + +if (NOT TARGET securec) + set(_ms_tmp_CMAKE_POSITION_INDEPENDENT_CODE ${CMAKE_POSITION_INDEPENDENT_CODE}) + set(_ms_tmp_CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) + + set(CMAKE_C_FLAGS "${SECURE_CXX_FLAGS}") + if (CMAKE_SYSTEM_NAME MATCHES "Windows") + add_compile_definitions(SECUREC_ONLY_DECLARE_MEMSET) + endif() + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third_party/securec ${CMAKE_BINARY_DIR}/securec) + set(CMAKE_POSITION_INDEPENDENT_CODE ${_ms_tmp_CMAKE_POSITION_INDEPENDENT_CODE}) + set(CMAKE_C_FLAGS ${_ms_tmp_CMAKE_C_FLAGS}) +endif() + +include_directories(${CMAKE_CURRENT_LIST_DIR}/../third_party/securec/include) + +set(SECUREC_LIBRARY securec) diff --git a/cmake/dependency_utils.cmake b/cmake/dependency_utils.cmake new file mode 100644 index 0000000..129365e --- /dev/null +++ b/cmake/dependency_utils.cmake @@ -0,0 +1,25 @@ +# MS Utils +# + +function(find_python_package out_inc out_lib) + # Use PYTHON_EXECUTABLE if it is defined, otherwise default to python + if ("${PYTHON_EXECUTABLE}" STREQUAL "") + set(PYTHON_EXECUTABLE "python3") + else() + set(PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}") + endif() + + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())" + RESULT_VARIABLE result + OUTPUT_VARIABLE inc) + string(STRIP "${inc}" inc) + set(${out_inc} ${inc} PARENT_SCOPE) + + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" -c "import distutils.sysconfig as sysconfig; import os; print(os.path.join(sysconfig.get_config_var('LIBDIR'), sysconfig.get_config_var('LDLIBRARY')))" + RESULT_VARIABLE result + OUTPUT_VARIABLE lib) + string(STRIP "${lib}" lib) + set(${out_lib} ${lib} PARENT_SCOPE) +endfunction() diff --git a/cmake/external_libs/absl.cmake b/cmake/external_libs/absl.cmake new file mode 100644 index 0000000..a8b64c9 --- /dev/null +++ b/cmake/external_libs/absl.cmake @@ -0,0 +1,14 @@ +mindspore_add_pkg(absl + VER 20200225.2 + LIBS absl_strings absl_throw_delegate absl_raw_logging_internal absl_int128 absl_bad_optional_access + URL https://github.com/abseil/abseil-cpp/archive/20200225.2.tar.gz + MD5 73f2b6e72f1599a9139170c29482ddc4 + CMAKE_OPTION -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=TRUE) + +include_directories(${absl_INC}) + +add_library(mindspore_serving::absl_strings ALIAS absl::absl_strings) +add_library(mindspore_serving::absl_throw_delegate ALIAS absl::absl_throw_delegate) +add_library(mindspore_serving::absl_raw_logging_internal ALIAS absl::absl_raw_logging_internal) +add_library(mindspore_serving::absl_int128 ALIAS absl::absl_int128) +add_library(mindspore_serving::absl_bad_optional_access ALIAS absl::absl_bad_optional_access) diff --git a/cmake/external_libs/c-ares.cmake b/cmake/external_libs/c-ares.cmake new file mode 100644 index 0000000..9f7d451 --- /dev/null +++ b/cmake/external_libs/c-ares.cmake @@ -0,0 +1,12 @@ +mindspore_add_pkg(c-ares + VER 1.15.0 + LIBS cares + URL https://github.com/c-ares/c-ares/releases/download/cares-1_15_0/c-ares-1.15.0.tar.gz + MD5 d2391da274653f7643270623e822dff7 + CMAKE_OPTION -DCMAKE_BUILD_TYPE:STRING=Release + -DCARES_SHARED:BOOL=OFF + -DCARES_STATIC:BOOL=ON + -DCARES_STATIC_PIC:BOOL=ON) + +include_directories(${c-ares_INC}) +add_library(mindspore_serving::cares ALIAS c-ares::cares) diff --git a/cmake/external_libs/glog.cmake b/cmake/external_libs/glog.cmake new file mode 100644 index 0000000..e65d474 --- /dev/null +++ b/cmake/external_libs/glog.cmake @@ -0,0 +1,17 @@ +set(glog_CXXFLAGS "-D_FORTIFY_SOURCE=2 -O2 ${SECURE_CXX_FLAGS} -D_GLIBCXX_USE_CXX11_ABI=0") +set(glog_CFLAGS "-D_FORTIFY_SOURCE=2 -O2") +if (ENABLE_GITEE) + set(REQ_URL "https://gitee.com/mirrors/glog/repository/archive/v0.4.0.tar.gz") + set(MD5 "22fe340ddc231e6c8e46bc295320f8ee") +else() + set(REQ_URL "https://github.com/google/glog/archive/v0.4.0.tar.gz") + set(MD5 "0daea8785e6df922d7887755c3d100d0") +endif () +mindspore_add_pkg(glog + VER 0.4.0 + LIBS glog + URL ${REQ_URL} + MD5 ${MD5} + CMAKE_OPTION -DBUILD_TESTING=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=ON -DWITH_GFLAGS=OFF) +include_directories(${glog_INC}) +add_library(mindspore::glog ALIAS glog::glog) diff --git a/cmake/external_libs/grpc.cmake b/cmake/external_libs/grpc.cmake new file mode 100644 index 0000000..1a58e99 --- /dev/null +++ b/cmake/external_libs/grpc.cmake @@ -0,0 +1,110 @@ +set(grpc_USE_STATIC_LIBS ON) +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(grpc_CXXFLAGS "-fstack-protector-all -Wno-uninitialized -Wno-unused-parameter -fPIC -fvisibility=hidden -D_FORTIFY_SOURCE=2 -O2") +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(grpc_CXXFLAGS "-fstack-protector-all -Wno-maybe-uninitialized -Wno-unused-parameter -fPIC -fvisibility=hidden -D_FORTIFY_SOURCE=2 -O2") +else() + set(grpc_CXXFLAGS "-fstack-protector-all -Wno-maybe-uninitialized -Wno-unused-parameter -fPIC -fvisibility=hidden -D_FORTIFY_SOURCE=2 -D_GLIBCXX_USE_CXX11_ABI=0 -O2") +endif() + +set(grpc_LDFLAGS "-Wl,-z,relro,-z,now,-z,noexecstack") + + +if (EXISTS ${protobuf_ROOT}/lib64) + set(_FINDPACKAGE_PROTOBUF_CONFIG_DIR "${protobuf_ROOT}/lib64/cmake/protobuf") +else() + set(_FINDPACKAGE_PROTOBUF_CONFIG_DIR "${protobuf_ROOT}/lib/cmake/protobuf") +endif() +message("grpc using Protobuf_DIR : " ${_FINDPACKAGE_PROTOBUF_CONFIG_DIR}) + +if (EXISTS ${absl_ROOT}/lib64) + set(_FINDPACKAGE_ABSL_CONFIG_DIR "${absl_ROOT}/lib64/cmake/absl") +else() + set(_FINDPACKAGE_ABSL_CONFIG_DIR "${absl_ROOT}/lib/cmake/absl") +endif() +message("grpc using absl_DIR : " ${_FINDPACKAGE_ABSL_CONFIG_DIR}) + +set(_CMAKE_ARGS_OPENSSL_ROOT_DIR "") +if (OPENSSL_ROOT_DIR) + set(_CMAKE_ARGS_OPENSSL_ROOT_DIR "-DOPENSSL_ROOT_DIR:PATH=${OPENSSL_ROOT_DIR}") +endif() + +mindspore_add_pkg(grpc + VER 1.27.3 + LIBS grpc++ grpc gpr upb address_sorting + EXE grpc_cpp_plugin + URL https://github.com/grpc/grpc/archive/v1.27.3.tar.gz + MD5 0c6c3fc8682d4262dd0e5e6fabe1a7e2 + CMAKE_OPTION -DCMAKE_BUILD_TYPE:STRING=Release + -DgRPC_INSTALL:BOOL=ON + -DgRPC_BUILD_TESTS:BOOL=OFF + -DgRPC_PROTOBUF_PROVIDER:STRING=package + -DgRPC_PROTOBUF_PACKAGE_TYPE:STRING=CONFIG + -DProtobuf_DIR:PATH=${_FINDPACKAGE_PROTOBUF_CONFIG_DIR} + -DgRPC_ZLIB_PROVIDER:STRING=package + -DZLIB_ROOT:PATH=${zlib_ROOT} + -DgRPC_ABSL_PROVIDER:STRING=package + -Dabsl_DIR:PATH=${_FINDPACKAGE_ABSL_CONFIG_DIR} + -DgRPC_CARES_PROVIDER:STRING=package + -Dc-ares_DIR:PATH=${c-ares_ROOT}/lib/cmake/c-ares + -DgRPC_SSL_PROVIDER:STRING=package + ${_CMAKE_ARGS_OPENSSL_ROOT_DIR} + ) + +include_directories(${grpc_INC}) + +add_library(mindspore_serving::grpc++ ALIAS grpc::grpc++) + +# link other grpc libs +target_link_libraries(grpc::grpc++ INTERFACE grpc::grpc grpc::gpr grpc::upb grpc::address_sorting) + +# link built dependencies +target_link_libraries(grpc::grpc++ INTERFACE mindspore_serving::z) +target_link_libraries(grpc::grpc++ INTERFACE mindspore_serving::cares) +target_link_libraries(grpc::grpc++ INTERFACE mindspore_serving::absl_strings mindspore_serving::absl_throw_delegate + mindspore_serving::absl_raw_logging_internal mindspore_serving::absl_int128 mindspore_serving::absl_bad_optional_access) + +# link system openssl +find_package(OpenSSL REQUIRED) +target_link_libraries(grpc::grpc++ INTERFACE OpenSSL::SSL OpenSSL::Crypto) + + +function(ms_grpc_generate c_var h_var) + if(NOT ARGN) + message(SEND_ERROR "Error: ms_grpc_generate() called without any proto files") + return() + endif() + + set(${c_var}) + set(${h_var}) + + foreach(file ${ARGN}) + get_filename_component(abs_file ${file} ABSOLUTE) + get_filename_component(file_name ${file} NAME_WE) + get_filename_component(file_dir ${abs_file} PATH) + file(RELATIVE_PATH rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${file_dir}) + + list(APPEND ${c_var} "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.cc") + list(APPEND ${h_var} "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.h") + list(APPEND ${c_var} "${CMAKE_BINARY_DIR}/proto/${file_name}.grpc.pb.cc") + list(APPEND ${h_var} "${CMAKE_BINARY_DIR}/proto/${file_name}.grpc.pb.h") + + add_custom_command( + OUTPUT "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.cc" + "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.h" + "${CMAKE_BINARY_DIR}/proto/${file_name}.grpc.pb.cc" + "${CMAKE_BINARY_DIR}/proto/${file_name}.grpc.pb.h" + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/proto" + COMMAND protobuf::protoc --version + COMMAND protobuf::protoc -I${file_dir} --cpp_out=${CMAKE_BINARY_DIR}/proto + --grpc_out=${CMAKE_BINARY_DIR}/proto --plugin=protoc-gen-grpc=$ ${abs_file} + DEPENDS protobuf::protoc grpc::grpc_cpp_plugin ${abs_file} + COMMENT "Running C++ gRPC compiler on ${file}" VERBATIM) + endforeach() + + set_source_files_properties(${${c_var}} ${${h_var}} PROPERTIES GENERATED TRUE) + set(${c_var} ${${c_var}} PARENT_SCOPE) + set(${h_var} ${${h_var}} PARENT_SCOPE) + +endfunction() diff --git a/cmake/external_libs/gtest.cmake b/cmake/external_libs/gtest.cmake new file mode 100644 index 0000000..4a409f2 --- /dev/null +++ b/cmake/external_libs/gtest.cmake @@ -0,0 +1,13 @@ +set(gtest_CXXFLAGS "-D_FORTIFY_SOURCE=2 -D_GLIBCXX_USE_CXX11_ABI=0 -O2") +set(gtest_CFLAGS "-D_FORTIFY_SOURCE=2 -O2") +mindspore_add_pkg(gtest + VER 1.8.0 + LIBS gtest + URL https://github.com/google/googletest/archive/release-1.8.0.tar.gz + MD5 16877098823401d1bf2ed7891d7dce36 + CMAKE_OPTION -DBUILD_TESTING=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_SHARED_LIBS=ON + -DCMAKE_MACOSX_RPATH=TRUE -Dgtest_disable_pthreads=ON) +include_directories(${gtest_INC}) +add_library(mindspore_serving::gtest ALIAS gtest::gtest) +file(COPY ${gtest_LIBPATH}/libgtest${CMAKE_SHARED_LIBRARY_SUFFIX} DESTINATION ${CMAKE_BINARY_DIR}/googletest/googlemock/gtest) +file(COPY ${gtest_LIBPATH}/libgtest_main${CMAKE_SHARED_LIBRARY_SUFFIX} DESTINATION ${CMAKE_BINARY_DIR}/googletest/googlemock/gtest) diff --git a/cmake/external_libs/json.cmake b/cmake/external_libs/json.cmake new file mode 100644 index 0000000..2c69d89 --- /dev/null +++ b/cmake/external_libs/json.cmake @@ -0,0 +1,9 @@ +set(nlohmann_json_CXXFLAGS "-D_FORTIFY_SOURCE=2 -O2") +set(nlohmann_json_CFLAGS "-D_FORTIFY_SOURCE=2 -O2") +mindspore_add_pkg(nlohmann_json + VER 3.6.1 + HEAD_ONLY ./ + URL https://github.com/nlohmann/json/releases/download/v3.6.1/include.zip + MD5 0dc903888211db3a0f170304cd9f3a89) +include_directories(${nlohmann_json_INC}) +add_library(mindspore_serving::json ALIAS nlohmann_json) \ No newline at end of file diff --git a/cmake/external_libs/libevent.cmake b/cmake/external_libs/libevent.cmake new file mode 100644 index 0000000..6ee66e5 --- /dev/null +++ b/cmake/external_libs/libevent.cmake @@ -0,0 +1,13 @@ +set(libevent_CFLAGS "-fstack-protector-all -D_FORTIFY_SOURCE=2 -O2") +set(libevent_LDFLAGS "-Wl,-z,now") +mindspore_add_pkg(libevent + VER 2.1.12 + LIBS event event_pthreads + URL https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz + MD5 b5333f021f880fe76490d8a799cd79f4 + CMAKE_OPTION -DCMAKE_BUILD_TYPE:STRING=Release -DBUILD_TESTING=OFF) + +include_directories(${libevent_INC}) # 将指定目录添加到编译器的头文件搜索路径之下,指定的目录被解释成当前源码路径的相对路径。 + +add_library(mindspore_serving::event ALIAS libevent::event) +add_library(mindspore_serving::event_pthreads ALIAS libevent::event_pthreads) diff --git a/cmake/external_libs/protobuf.cmake b/cmake/external_libs/protobuf.cmake new file mode 100644 index 0000000..d9a24a5 --- /dev/null +++ b/cmake/external_libs/protobuf.cmake @@ -0,0 +1,120 @@ +set(protobuf_USE_STATIC_LIBS ON) +if (BUILD_LITE) + set(protobuf_CXXFLAGS "-fstack-protector-all -Wno-maybe-uninitialized -Wno-unused-parameter -fPIC -fvisibility=hidden -D_FORTIFY_SOURCE=2 -O2") +else(BUILD_LITE) + if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(protobuf_CXXFLAGS "-fstack-protector-all -Wno-uninitialized -Wno-unused-parameter -fPIC -fvisibility=hidden -D_FORTIFY_SOURCE=2 -O2") + elseif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") + set(protobuf_CXXFLAGS "-fstack-protector-all -Wno-maybe-uninitialized -Wno-unused-parameter -fPIC -fvisibility=hidden -D_FORTIFY_SOURCE=2 -O2") + else() + set(protobuf_CXXFLAGS "-fstack-protector-all -Wno-maybe-uninitialized -Wno-unused-parameter -fPIC -fvisibility=hidden -D_FORTIFY_SOURCE=2 -D_GLIBCXX_USE_CXX11_ABI=0 -O2") + endif() +endif(BUILD_LITE) + +set(protobuf_LDFLAGS "-Wl,-z,relro,-z,now,-z,noexecstack") +set(_ms_tmp_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) +set(CMAKE_CXX_FLAGS ${_ms_tmp_CMAKE_CXX_FLAGS}) +string(REPLACE " -Wall" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +string(REPLACE " -Werror" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + +mindspore_add_pkg(protobuf + VER 3.8.0 + LIBS protobuf + EXE protoc + URL https://github.com/protocolbuffers/protobuf/archive/v3.8.0.tar.gz + MD5 3d9e32700639618a4d2d342c99d4507a + CMAKE_PATH cmake/ + CMAKE_OPTION -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_BUILD_SHARED_LIBS=OFF) + +include_directories(${protobuf_INC}) +add_library(mindspore_serving::protobuf ALIAS protobuf::protobuf) +set(CMAKE_CXX_FLAGS ${_ms_tmp_CMAKE_CXX_FLAGS}) + +function(ms_protobuf_generate c_var h_var) + if(NOT ARGN) + message(SEND_ERROR "Error: ms_protobuf_generate() called without any proto files") + return() + endif() + + set(${c_var}) + set(${h_var}) + + foreach(file ${ARGN}) + get_filename_component(abs_file ${file} ABSOLUTE) + get_filename_component(file_name ${file} NAME_WE) + get_filename_component(file_dir ${abs_file} PATH) + file(RELATIVE_PATH rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${file_dir}) + + list(APPEND ${c_var} "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.cc") + list(APPEND ${h_var} "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.h") + + add_custom_command( + OUTPUT "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.cc" + "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.h" + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/proto" + COMMAND protobuf::protoc -I${file_dir} --cpp_out=${CMAKE_BINARY_DIR}/proto ${abs_file} + DEPENDS protobuf::protoc ${abs_file} + COMMENT "Running C++ protocol buffer compiler on ${file}" VERBATIM) + endforeach() + + set_source_files_properties(${${c_var}} ${${h_var}} PROPERTIES GENERATED TRUE) + set(${c_var} ${${c_var}} PARENT_SCOPE) + set(${h_var} ${${h_var}} PARENT_SCOPE) + +endfunction() + +function(ms_protobuf_generate_py c_var h_var py_var) + if(NOT ARGN) + message(SEND_ERROR "Error: ms_protobuf_generate() called without any proto files") + return() + endif() + + set(${c_var}) + set(${h_var}) + set(${py_var}) + + foreach(file ${ARGN}) + get_filename_component(abs_file ${file} ABSOLUTE) + get_filename_component(file_name ${file} NAME_WE) + get_filename_component(file_dir ${abs_file} PATH) + + list(APPEND ${c_var} "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.cc") + list(APPEND ${h_var} "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.h") + list(APPEND ${py_var} "${CMAKE_BINARY_DIR}/proto/${file_name}_pb2.py") + if (WIN32) + add_custom_command( + OUTPUT "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.cc" + "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.h" + "${CMAKE_BINARY_DIR}/proto/${file_name}_pb2.py" + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/proto" + COMMAND protobuf::protoc -I${file_dir} --cpp_out=${CMAKE_BINARY_DIR}/proto ${abs_file} + COMMAND protobuf::protoc -I${file_dir} --python_out=${CMAKE_BINARY_DIR}/proto ${abs_file} + COMMAND protobuf::protoc -I${file_dir} --python_out=${CMAKE_BINARY_DIR}/proto ${abs_file} + COMMAND perl -pi.bak -e "s/import (.+_pb2.*)/from . import \\1/" "${CMAKE_BINARY_DIR}/proto/${file_name}_pb2.py" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_BINARY_DIR}/proto/${file_name}_pb2.py" "${PROJECT_SOURCE_DIR}/mindspore_serving/train/" + DEPENDS protobuf::protoc ${abs_file} + COMMENT "Running C++ protocol buffer compiler on ${file}" VERBATIM ) + else() + add_custom_command( + OUTPUT "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.cc" + "${CMAKE_BINARY_DIR}/proto/${file_name}.pb.h" + "${CMAKE_BINARY_DIR}/proto/${file_name}_pb2.py" + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/proto" + COMMAND protobuf::protoc -I${file_dir} --cpp_out=${CMAKE_BINARY_DIR}/proto ${abs_file} + COMMAND protobuf::protoc -I${file_dir} --python_out=${CMAKE_BINARY_DIR}/proto ${abs_file} + COMMAND protobuf::protoc -I${file_dir} --python_out=${CMAKE_BINARY_DIR}/proto ${abs_file} + COMMAND perl -pi -e "s/import (.+_pb2.*)/from . import \\1/" "${CMAKE_BINARY_DIR}/proto/${file_name}_pb2.py" + COMMAND cp "${CMAKE_BINARY_DIR}/proto/${file_name}_pb2.py" "${PROJECT_SOURCE_DIR}/mindspore_serving/train/" + DEPENDS protobuf::protoc ${abs_file} + COMMENT "Running C++ protocol buffer compiler on ${file}" VERBATIM) + endif() + endforeach() + set_source_files_properties(${${c_var}} ${${h_var}} ${${py_var}} PROPERTIES GENERATED TRUE) + set(${c_var} ${${c_var}} PARENT_SCOPE) + set(${h_var} ${${h_var}} PARENT_SCOPE) + set(${py_var} ${${py_var}} PARENT_SCOPE) + +endfunction() diff --git a/cmake/external_libs/pybind11.cmake b/cmake/external_libs/pybind11.cmake new file mode 100644 index 0000000..4e5714f --- /dev/null +++ b/cmake/external_libs/pybind11.cmake @@ -0,0 +1,12 @@ +set(pybind11_CXXFLAGS "-D_FORTIFY_SOURCE=2 -O2") +set(pybind11_CFLAGS "-D_FORTIFY_SOURCE=2 -O2") +mindspore_add_pkg(pybind11 + VER 2.4.3 + URL https://github.com/pybind/pybind11/archive/v2.4.3.tar.gz + MD5 62254c40f89925bb894be421fe4cdef2 + CMAKE_OPTION -DPYBIND11_TEST=OFF -DPYBIND11_LTO_CXX_FLAGS=FALSE + ) +include_directories(${pybind11_INC}) +find_package(pybind11 REQUIRED) +set_property(TARGET pybind11::module PROPERTY IMPORTED_GLOBAL TRUE) +add_library(mindspore_serving::pybind11_module ALIAS pybind11::module) diff --git a/cmake/external_libs/zlib.cmake b/cmake/external_libs/zlib.cmake new file mode 100644 index 0000000..bfe6e36 --- /dev/null +++ b/cmake/external_libs/zlib.cmake @@ -0,0 +1,9 @@ +mindspore_add_pkg(zlib + VER 1.2.11 + LIBS z + URL https://github.com/madler/zlib/archive/v1.2.11.tar.gz + MD5 0095d2d2d1f3442ce1318336637b695f + CMAKE_OPTION -DCMAKE_BUILD_TYPE:STRING=Release) + +include_directories(${zlib_INC}) +add_library(mindspore_serving::z ALIAS zlib::z) diff --git a/cmake/mind_expression.cmake b/cmake/mind_expression.cmake new file mode 100644 index 0000000..2c37bf5 --- /dev/null +++ b/cmake/mind_expression.cmake @@ -0,0 +1,32 @@ +set(SECURE_CXX_FLAGS "") +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set(SECURE_CXX_FLAGS "-fstack-protector-all -Wl,-z,relro,-z,now,-z,noexecstack") +endif() +set(_ms_tmp_CMAKE_CXX_FLAGS_F ${CMAKE_CXX_FLAGS}) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + +# define third party library download function +include(cmake/utils.cmake) + +include(${CMAKE_SOURCE_DIR}/cmake/dependency_securec.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/json.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/protobuf.cmake) + +# build dependencies of gRPC +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/absl.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/c-ares.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/zlib.cmake) +# build gRPC +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/grpc.cmake) +# build event +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/libevent.cmake) + +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/pybind11.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/gtest.cmake) +include(${CMAKE_SOURCE_DIR}/cmake/external_libs/glog.cmake) + +set(CMAKE_CXX_FLAGS ${_ms_tmp_CMAKE_CXX_FLAGS_F}) + +if(MS_BACKEND) + include(${CMAKE_SOURCE_DIR}/cmake/dependency_ms.cmake) +endif() \ No newline at end of file diff --git a/cmake/options.cmake b/cmake/options.cmake new file mode 100644 index 0000000..3566513 --- /dev/null +++ b/cmake/options.cmake @@ -0,0 +1,53 @@ +option(DEBUG_MODE "Debug mode, default off" OFF) +option(ENABLE_COVERAGE "Enable code coverage report" OFF) +option(ENABLE_PYTHON "Enable python" ON) +option(ENABLE_ASAN "Enable Google Sanitizer to find memory bugs") +option(MS_WHL_LIB_PATH "MindSpore lib path") +option(MS_BACKEND "Compile MindSpore") + +if(MS_WHL_LIB_PATH) + message("MindSpore whl lib path: " ${MS_WHL_LIB_PATH}) +elseif(MS_BACKEND) + message("MindSpore backend method: " ${MS_BACKEND}) +else() + message( FATAL_ERROR "Please confirm how to use MindSpore.") +endif() + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND Linux) + set(OPTION_CXX_FLAGS "${OPTION_CXX_FLAGS} -fstack-protector-all -Wl,-z,relro,-z,now,-z,noexecstack") +endif() + +if (ENABLE_COVERAGE) + set(COVERAGE_COMPILER_FLAGS "-g --coverage -fprofile-arcs -ftest-coverage") + set(OPTION_CXX_FLAGS "${OPTION_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}") +endif() + +if (ENABLE_ASAN) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(OPTION_CXX_FLAGS "${OPTION_CXX_FLAGS} -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -fsanitize=undefined") + else() + set(OPTION_CXX_FLAGS "${OPTION_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -static-libsan -fsanitize=undefined") + endif() +endif() + +if (DEBUG_MODE) + set(CMAKE_BUILD_TYPE "Debug") + add_compile_definitions(MEM_REUSE_DEBUG) +else() + set(CMAKE_BUILD_TYPE "Release") +endif() + +if ((CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") OR (CMAKE_BUILD_TYPE STREQUAL Release)) + set(PYBIND11_LTO_CXX_FLAGS FALSE) +endif() + +if (NOT BUILD_PATH) + set(BUILD_PATH "${CMAKE_SOURCE_DIR}/build") +endif() + +if (NOT CMAKE_SYSTEM_NAME MATCHES "Windows") + set(MS_BUILD_GRPC ON) +endif() + +add_compile_definitions(USE_GLOG) + diff --git a/cmake/package.cmake b/cmake/package.cmake new file mode 100644 index 0000000..d511557 --- /dev/null +++ b/cmake/package.cmake @@ -0,0 +1,70 @@ +# include dependency +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +# set package information +set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) +set(CPACK_GENERATOR "External") +set(CPACK_EXTERNAL_PACKAGE_SCRIPT ${CMAKE_SOURCE_DIR}/cmake/package_script.cmake) +set(CPACK_EXTERNAL_ENABLE_STAGING true) +set(CPACK_TEMPORARY_PACKAGE_FILE_NAME ${CMAKE_SOURCE_DIR}/build/package/mindspore_serving) +set(CPACK_TEMPORARY_INSTALL_DIRECTORY ${CMAKE_SOURCE_DIR}/build/package/mindspore_serving) + +set(CPACK_MS_PACKAGE_NAME "mindspore_serving") +include(CPack) + +# set install path +set(INSTALL_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE PATH "Installation directory for libraries") +set(INSTALL_PY_DIR ".") +set(INSTALL_BASE_DIR ".") +set(INSTALL_LIB_DIR "lib") + +file(GLOB_RECURSE LIBEVENT_LIB_LIST + ${libevent_LIBPATH}/libevent* + ${libevent_LIBPATH}/libevent_pthreads* + ) +install( + FILES ${LIBEVENT_LIB_LIST} + DESTINATION ${INSTALL_LIB_DIR} + COMPONENT mindspore_serving +) + +file(GLOB_RECURSE GLOG_LIB_LIST ${glog_LIBPATH}/libglog*) +install( + FILES ${GLOG_LIB_LIST} + DESTINATION ${INSTALL_LIB_DIR} + COMPONENT mindspore_serving +) + +# set python files +file(GLOB MS_PY_LIST ${CMAKE_SOURCE_DIR}/mindspore_serving/*.py) +install( + FILES ${MS_PY_LIST} + DESTINATION ${INSTALL_PY_DIR} + COMPONENT mindspore_serving +) + +install( + TARGETS _mindspore_serving + DESTINATION ${INSTALL_BASE_DIR} + COMPONENT mindspore_serving +) +install( + TARGETS serving_common + DESTINATION ${INSTALL_LIB_DIR} + COMPONENT mindspore_serving +) +install( + DIRECTORY + ${CMAKE_SOURCE_DIR}/mindspore_serving/master + ${CMAKE_SOURCE_DIR}/mindspore_serving/worker + ${CMAKE_SOURCE_DIR}/mindspore_serving/client + DESTINATION ${INSTALL_PY_DIR} + COMPONENT mindspore_serving +) +install( + FILES ${CMAKE_SOURCE_DIR}/build/mindspore_serving/mindspore_serving/mindspore_serving/proto/ms_service_pb2.py + ${CMAKE_SOURCE_DIR}/build/mindspore_serving/mindspore_serving/mindspore_serving/proto/ms_service_pb2_grpc.py + DESTINATION ${INSTALL_PY_DIR}/proto + COMPONENT mindspore_serving +) diff --git a/cmake/package_script.cmake b/cmake/package_script.cmake new file mode 100644 index 0000000..7f1446d --- /dev/null +++ b/cmake/package_script.cmake @@ -0,0 +1,75 @@ +# find exec +find_package(Python3 3.7 COMPONENTS Interpreter) +if (NOT Python3_FOUND) + message(FATAL_ERROR "No python3 found.") +endif () + +set(PYTHON ${Python3_EXECUTABLE}) +set(PYTHON_VERSION ${Python3_VERSION_MAJOR}.${Python3_VERSION_MINOR}) + +if (NOT PYTHON_VERSION MATCHES "3.7") + message(FATAL_ERROR "FIND PYTHON VERSION ${PYTHON_VERSION} BUT CAN NOT MATCH PYTHON VERSION 3.7") +endif () + +find_package(Git) +if (NOT GIT_FOUND) + message("No git found.") + return () +endif () +set(GIT ${GIT_EXECUTABLE}) + +# set path +set(MS_ROOT_DIR ${CPACK_PACKAGE_DIRECTORY}/../../) +set(MS_PACK_ROOT_DIR ${MS_ROOT_DIR}/build/package) +# set package file name +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + if (PYTHON_VERSION MATCHES "3.7") + set(PY_TAGS "cp37-cp37m") + else () + message("Could not find 'Python 3.7'") + return() + endif () + string(TOLOWER linux_${CMAKE_HOST_SYSTEM_PROCESSOR} PLATFORM_TAG) +else () + message(FATAL_ERROR "other platform: ${CMAKE_SYSTEM_NAME}") +endif () + +# get git commit id +set(GIT_COMMIT_ID "") +execute_process( + COMMAND ${GIT} log --format='[sha1]:%h,[branch]:%d' --abbrev=8 -1 + OUTPUT_VARIABLE GIT_COMMIT_ID + WORKING_DIRECTORY ${MS_ROOT_DIR} + ERROR_QUIET) +string(REPLACE " " "" GIT_COMMIT_ID ${GIT_COMMIT_ID}) + +set(ENV{MS_PACKAGE_NAME} ${CPACK_MS_PACKAGE_NAME}) +set(ENV{COMMIT_ID} ${GIT_COMMIT_ID}) + +execute_process( + COMMAND ${PYTHON} ${MS_ROOT_DIR}/setup.py "bdist_wheel" + WORKING_DIRECTORY ${MS_PACK_ROOT_DIR} +) + +# finally +set(PACKAGE_NAME ${CPACK_MS_PACKAGE_NAME}) +if (NOT CMAKE_SYSTEM_NAME MATCHES "Windows") + string(REPLACE "-" "_" PACKAGE_NAME ${PACKAGE_NAME}) + execute_process( + COMMAND chmod -R 700 ${MS_PACK_ROOT_DIR}/mindspore_serving/ + COMMAND chmod -R 700 ${MS_PACK_ROOT_DIR}/${PACKAGE_NAME}.egg-info/ + ) +endif () + +file(GLOB WHL_FILE ${MS_PACK_ROOT_DIR}/dist/*.whl) +get_filename_component(ORIGIN_FILE_NAME ${WHL_FILE} NAME) +string(REPLACE "-" ";" ORIGIN_FILE_NAME ${ORIGIN_FILE_NAME}) +list(GET ORIGIN_FILE_NAME 1 VERSION) +set(NEW_FILE_NAME ${PACKAGE_NAME}-${VERSION}-${PY_TAGS}-${PLATFORM_TAG}.whl) +file(RENAME ${WHL_FILE} ${MS_PACK_ROOT_DIR}/${NEW_FILE_NAME}) +file(REMOVE_RECURSE ${MS_ROOT_DIR}/output) +file(MAKE_DIRECTORY ${MS_ROOT_DIR}/output) +file(COPY ${MS_PACK_ROOT_DIR}/${NEW_FILE_NAME} DESTINATION ${MS_ROOT_DIR}/output/) + +file(SHA256 ${MS_ROOT_DIR}/output/${NEW_FILE_NAME} SHA256_VAR) +file(WRITE ${MS_ROOT_DIR}/output/${NEW_FILE_NAME}.sha256 ${SHA256_VAR} " " ${NEW_FILE_NAME}) diff --git a/cmake/utils.cmake b/cmake/utils.cmake new file mode 100644 index 0000000..80e95dd --- /dev/null +++ b/cmake/utils.cmake @@ -0,0 +1,422 @@ +include(FetchContent) # 下载第三方库 +set(FETCHCONTENT_QUIET OFF) + +function(mindspore_add_submodule_obj des_submodule_objs sub_dir submodule_name_obj) + + add_subdirectory(${sub_dir}) + + if(NOT TARGET ${submodule_name_obj}) + message(FATAL_ERROR "Can not find submodule '${submodule_name_obj}'. in ${CMAKE_CURRENT_LIST_FILE}") + endif() + if("$" IN_LIST ${des_submodule_objs}) + message(FATAL_ERROR "submodule '${submodule_name_obj}' added more than once. in ${CMAKE_CURRENT_LIST_FILE}") + endif() + + set(${des_submodule_objs} ${${des_submodule_objs}} $ PARENT_SCOPE) + +endfunction() + +if (DEFINED ENV{MSLIBS_CACHE_PATH}) + set(_MS_LIB_CACHE $ENV{MSLIBS_CACHE_PATH}) +else() + set(_MS_LIB_CACHE ${CMAKE_BINARY_DIR}/.mslib) +endif () +message("MS LIBS CACHE PATH: ${_MS_LIB_CACHE}") + +if (NOT EXISTS ${_MS_LIB_CACHE}) + file(MAKE_DIRECTORY ${_MS_LIB_CACHE}) +endif () + +if (DEFINED ENV{MSLIBS_SERVER}) # export MSLIBS_SERVER=49.4.0.74 + set(LOCAL_LIBS_SERVER $ENV{MSLIBS_SERVER}) + message("LOCAL_LIBS_SERVER: ${LOCAL_LIBS_SERVER}") +endif () + +include(ProcessorCount) # 确定处理器/核的数量并将值保存在${var}中 +ProcessorCount(N) +if (JOBS) + set(THNUM ${JOBS}) +else() + set(JOBS 8) + if (${JOBS} GREATER ${N}) + set(THNUM ${N}) + else() + set(THNUM ${JOBS}) + endif() +endif () +message("set make thread num: ${THNUM}") + +if(LOCAL_LIBS_SERVER) + if (NOT ENV{no_proxy}) + set(ENV{no_proxy} "${LOCAL_LIBS_SERVER}") + else() + string(FIND $ENV{no_proxy} ${LOCAL_LIBS_SERVER} IP_POS) + if (${IP_POS} EQUAL -1) + set(ENV{no_proxy} "$ENV{no_proxy},${LOCAL_LIBS_SERVER}") + endif () + endif () +endif() + +function(__download_pkg pkg_name pkg_url pkg_md5) + + if(LOCAL_LIBS_SERVER) + get_filename_component(_URL_FILE_NAME ${pkg_url} NAME) + set(pkg_url "http://${LOCAL_LIBS_SERVER}:8081/libs/${pkg_name}/${_URL_FILE_NAME}" ${pkg_url}) + endif() + + FetchContent_Declare( # 获取项目。可以是一个URL也可以是一个Git仓库。 + ${pkg_name} + URL ${pkg_url} + URL_HASH MD5=${pkg_md5} + ) + FetchContent_GetProperties(${pkg_name}) # 获取我们需要的变量MyName_*。 + message("download: ${${pkg_name}_SOURCE_DIR} , ${pkg_name} , ${pkg_url}") + if(NOT ${pkg_name}_POPULATED) + FetchContent_Populate(${pkg_name}) # 将信息记录在可以随时查询的全局属性中 + set(${pkg_name}_SOURCE_DIR ${${pkg_name}_SOURCE_DIR} PARENT_SCOPE) + endif() + +endfunction() + +function(__download_pkg_with_git pkg_name pkg_url pkg_git_commit pkg_md5) + + if(LOCAL_LIBS_SERVER) + set(pkg_url "http://${LOCAL_LIBS_SERVER}:8081/libs/${pkg_name}/${pkg_git_commit}") + FetchContent_Declare( + ${pkg_name} + URL ${pkg_url} + URL_HASH MD5=${pkg_md5} + ) + else() + FetchContent_Declare( + ${pkg_name} + GIT_REPOSITORY ${pkg_url} + GIT_TAG ${pkg_git_commit}) + endif() + FetchContent_GetProperties(${pkg_name}) + message("download: ${${pkg_name}_SOURCE_DIR} , ${pkg_name} , ${pkg_url}") + if(NOT ${pkg_name}_POPULATED) + FetchContent_Populate(${pkg_name}) + set(${pkg_name}_SOURCE_DIR ${${pkg_name}_SOURCE_DIR} PARENT_SCOPE) + endif() + +endfunction() + + +function(__find_pkg_then_add_target pkg_name pkg_exe lib_path) + + unset(${pkg_name}_LIBS) + + message("_FIND:${${pkg_name}_BASE_DIR}") + + if(pkg_exe) + # find_program:该命令用于查找程序。创建名为的缓存条目以存储此命令的结果。 + # 如果找到程序,则结果存储在变量中,除非清除变量,否则将不会重复搜索。如果什么也没找到,结果将是-NOTFOUND。 + find_program(${pkg_exe}_EXE ${pkg_exe} PATHS ${${pkg_name}_BASE_DIR}/bin NO_DEFAULT_PATH) + if(NOT ${pkg_exe}_EXE) + return() + endif() + # add_executable: 使用给定的源文件,为工程引入一个可执行文件。 + # IMPORTED:一个导入的可执行目标引用了一个位于工程之外的可执行文件。 + add_executable(${pkg_name}::${pkg_exe} IMPORTED GLOBAL) + set_target_properties(${pkg_name}::${pkg_exe} PROPERTIES + IMPORTED_LOCATION ${${pkg_exe}_EXE} + ) + message("found ${${pkg_exe}_EXE}") + endif() + + foreach(_LIB_NAME ${ARGN}) + set(_LIB_SEARCH_NAME ${_LIB_NAME}) + set(_LIB_TYPE SHARED) + if (${pkg_name}_USE_STATIC_LIBS) + set(_LIB_SEARCH_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}${_LIB_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(_LIB_TYPE STATIC) + endif () + set(${_LIB_NAME}_LIB ${_LIB_NAME}_LIB-NOTFOUND) + find_library(${_LIB_NAME}_LIB ${_LIB_SEARCH_NAME} PATHS ${${pkg_name}_BASE_DIR}/${lib_path} NO_DEFAULT_PATH) + if (NOT ${_LIB_NAME}_LIB AND BUILD_LITE AND PLATFORM_ARM) + set(${_LIB_NAME}_LIB "${${pkg_name}_BASE_DIR}/${lib_path}/lib${_LIB_SEARCH_NAME}.so") + endif(NOT ${_LIB_NAME}_LIB AND BUILD_LITE AND PLATFORM_ARM) + if(NOT ${_LIB_NAME}_LIB) + return() + endif() + + add_library(${pkg_name}::${_LIB_NAME} ${_LIB_TYPE} IMPORTED GLOBAL) + if (WIN32 AND ${_LIB_TYPE} STREQUAL "SHARED") + set_target_properties(${pkg_name}::${_LIB_NAME} PROPERTIES IMPORTED_IMPLIB_RELEASE ${${_LIB_NAME}_LIB}) + else() + set_target_properties(${pkg_name}::${_LIB_NAME} PROPERTIES IMPORTED_LOCATION ${${_LIB_NAME}_LIB}) + endif() + + if (EXISTS ${${pkg_name}_BASE_DIR}/include) + set_target_properties(${pkg_name}::${_LIB_NAME} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${${pkg_name}_BASE_DIR}/include") + endif () + + list(APPEND ${pkg_name}_LIBS ${pkg_name}::${_LIB_NAME}) + message("found ${${_LIB_NAME}_LIB}") + STRING( REGEX REPLACE "(.+)/(.+)" "\\1" LIBPATH ${${_LIB_NAME}_LIB}) + set(${pkg_name}_LIBPATH ${LIBPATH} CACHE STRING INTERNAL) + endforeach(_LIB_NAME) + + set(${pkg_name}_LIBS ${${pkg_name}_LIBS} PARENT_SCOPE) +endfunction() + +function(__exec_cmd) + set(options ) + set(oneValueArgs WORKING_DIRECTORY) + set(multiValueArgs COMMAND) + + cmake_parse_arguments(EXEC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + + execute_process(COMMAND ${EXEC_COMMAND} + WORKING_DIRECTORY ${EXEC_WORKING_DIRECTORY} + RESULT_VARIABLE RESULT) + if(NOT RESULT EQUAL "0") + message(FATAL_ERROR "error! when ${EXEC_COMMAND} in ${EXEC_WORKING_DIRECTORY}") + endif() +endfunction() + +function(__check_patches pkg_patches) + # check patches + if (PKG_PATCHES) + file(TOUCH ${_MS_LIB_CACHE}/${pkg_name}_patch.md5) + file(READ ${_MS_LIB_CACHE}/${pkg_name}_patch.md5 ${pkg_name}_PATCHES_MD5) + + message("patches md5:${${pkg_name}_PATCHES_MD5}") + + set(${pkg_name}_PATCHES_NEW_MD5 ) + foreach(_PATCH ${PKG_PATCHES}) + file(MD5 ${_PATCH} _PF_MD5) + set(${pkg_name}_PATCHES_NEW_MD5 "${${pkg_name}_PATCHES_NEW_MD5},${_PF_MD5}") + endforeach(_PATCH) + + if (NOT ${pkg_name}_PATCHES_MD5 STREQUAL ${pkg_name}_PATCHES_NEW_MD5) + set(${pkg_name}_PATCHES ${PKG_PATCHES}) + file(REMOVE_RECURSE "${_MS_LIB_CACHE}/${pkg_name}-subbuild") + file(WRITE ${_MS_LIB_CACHE}/${pkg_name}_patch.md5 ${${pkg_name}_PATCHES_NEW_MD5}) + message("patches changed : ${${pkg_name}_PATCHES_NEW_MD5}") + endif () + endif () +endfunction() + +set(MS_FIND_NO_DEFAULT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_BUILDS_PATH NO_CMAKE_PACKAGE_REGISTRY NO_CMAKE_SYSTEM_PATH + NO_CMAKE_SYSTEM_PACKAGE_REGISTRY) +set(MS_FIND_NO_DEFAULT_PATH ${MS_FIND_NO_DEFAULT_PATH} PARENT_SCOPE) +function(mindspore_add_pkg pkg_name ) + + message("---------add pkg: " ${pkg_name} "---------") + set(options ) + set(oneValueArgs URL MD5 GIT_REPOSITORY GIT_TAG VER EXE DIR HEAD_ONLY CMAKE_PATH RELEASE LIB_PATH CUSTOM_CMAKE) + set(multiValueArgs CMAKE_OPTION LIBS PRE_CONFIGURE_COMMAND CONFIGURE_COMMAND BUILD_OPTION INSTALL_INCS INSTALL_LIBS PATCHES SUBMODULES SOURCEMODULES ONLY_MAKE ONLY_MAKE_INCS ONLY_MAKE_LIBS) + cmake_parse_arguments(PKG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + + if (NOT PKG_LIB_PATH) + set(PKG_LIB_PATH lib) + endif () + + if(NOT PKG_EXE) + set(PKG_EXE 0) + endif() + + set(__FIND_PKG_NAME ${pkg_name}) + string(TOLOWER ${pkg_name} pkg_name) + message("pkg name:${__FIND_PKG_NAME},${pkg_name}") + + set(${pkg_name}_PATCHES_HASH ) + foreach(_PATCH ${PKG_PATCHES}) + file(MD5 ${_PATCH} _PF_MD5) + set(${pkg_name}_PATCHES_HASH "${${pkg_name}_PATCHES_HASH},${_PF_MD5}") + endforeach(_PATCH) + + # check options + set(${pkg_name}_CONFIG_TXT + "${CMAKE_CXX_COMPILER_VERSION}-${CMAKE_C_COMPILER_VERSION} + ${ARGN} - ${${pkg_name}_USE_STATIC_LIBS}- ${${pkg_name}_PATCHES_HASH} + ${${pkg_name}_CXXFLAGS}--${${pkg_name}_CFLAGS}--${${pkg_name}_LDFLAGS}") + string(REPLACE ";" "-" ${pkg_name}_CONFIG_TXT ${${pkg_name}_CONFIG_TXT}) + string(MD5 ${pkg_name}_CONFIG_HASH ${${pkg_name}_CONFIG_TXT}) + + message("${pkg_name} config hash: ${${pkg_name}_CONFIG_HASH}") + + set(${pkg_name}_BASE_DIR ${_MS_LIB_CACHE}/${pkg_name}_${${pkg_name}_CONFIG_HASH}) + set(${pkg_name}_DIRPATH ${${pkg_name}_BASE_DIR} CACHE STRING INTERNAL) + + if(EXISTS ${${pkg_name}_BASE_DIR}/options.txt AND PKG_HEAD_ONLY) + set(${pkg_name}_INC ${${pkg_name}_BASE_DIR}/${PKG_HEAD_ONLY} PARENT_SCOPE) + add_library(${pkg_name} INTERFACE) + target_include_directories(${pkg_name} INTERFACE ${${pkg_name}_INC}) + if (${PKG_RELEASE}) + __find_pkg_then_add_target(${pkg_name} ${PKG_EXE} ${PKG_LIB_PATH} ${PKG_LIBS}) + endif () + return() + endif () + + set(${__FIND_PKG_NAME}_ROOT ${${pkg_name}_BASE_DIR}) + set(${__FIND_PKG_NAME}_ROOT ${${pkg_name}_BASE_DIR} PARENT_SCOPE) + + if (PKG_LIBS) + __find_pkg_then_add_target(${pkg_name} ${PKG_EXE} ${PKG_LIB_PATH} ${PKG_LIBS}) + if(${pkg_name}_LIBS) + set(${pkg_name}_INC ${${pkg_name}_BASE_DIR}/include PARENT_SCOPE) + message("Found libs: ${${pkg_name}_LIBS}") + return() + endif() + elseif(NOT PKG_HEAD_ONLY) + find_package(${__FIND_PKG_NAME} ${PKG_VER} ${MS_FIND_NO_DEFAULT_PATH}) + if (${__FIND_PKG_NAME}_FOUND) + set(${pkg_name}_INC ${${pkg_name}_BASE_DIR}/include PARENT_SCOPE) + message("Found pkg: ${__FIND_PKG_NAME}") + return() + endif () + endif () + + if (NOT PKG_DIR) + if (PKG_GIT_REPOSITORY) + __download_pkg_with_git(${pkg_name} ${PKG_GIT_REPOSITORY} ${PKG_GIT_TAG} ${PKG_MD5}) + else() + __download_pkg(${pkg_name} ${PKG_URL} ${PKG_MD5}) + endif() + foreach(_SUBMODULE_FILE ${PKG_SUBMODULES}) + STRING( REGEX REPLACE "(.+)_(.+)" "\\1" _SUBMODEPATH ${_SUBMODULE_FILE}) + STRING( REGEX REPLACE "(.+)/(.+)" "\\2" _SUBMODENAME ${_SUBMODEPATH}) + file(GLOB ${pkg_name}_INSTALL_SUBMODULE ${_SUBMODULE_FILE}/*) + file(COPY ${${pkg_name}_INSTALL_SUBMODULE} DESTINATION ${${pkg_name}_SOURCE_DIR}/3rdparty/${_SUBMODENAME}) + endforeach (_SUBMODULE_FILE) + else() + set(${pkg_name}_SOURCE_DIR ${PKG_DIR}) + endif () + file(WRITE ${${pkg_name}_BASE_DIR}/options.txt ${${pkg_name}_CONFIG_TXT}) + message("${pkg_name}_SOURCE_DIR : ${${pkg_name}_SOURCE_DIR}") + + foreach(_PATCH_FILE ${PKG_PATCHES}) + get_filename_component(_PATCH_FILE_NAME ${_PATCH_FILE} NAME) + set(_LF_PATCH_FILE ${CMAKE_BINARY_DIR}/_ms_patch/${_PATCH_FILE_NAME}) + configure_file(${_PATCH_FILE} ${_LF_PATCH_FILE} NEWLINE_STYLE LF @ONLY) + + message("patching ${${pkg_name}_SOURCE_DIR} -p1 < ${_LF_PATCH_FILE}") + execute_process(COMMAND ${Patch_EXECUTABLE} -p1 INPUT_FILE ${_LF_PATCH_FILE} + WORKING_DIRECTORY ${${pkg_name}_SOURCE_DIR} + RESULT_VARIABLE Result) + if(NOT Result EQUAL "0") + message(FATAL_ERROR "Failed patch: ${_LF_PATCH_FILE}") + endif() + endforeach(_PATCH_FILE) + foreach(_SOURCE_DIR ${PKG_SOURCEMODULES}) + file(GLOB ${pkg_name}_INSTALL_SOURCE ${${pkg_name}_SOURCE_DIR}/${_SOURCE_DIR}/*) + file(COPY ${${pkg_name}_INSTALL_SOURCE} DESTINATION ${${pkg_name}_BASE_DIR}/${_SOURCE_DIR}/) + endforeach (_SUBMODULE_FILE) + file(LOCK ${${pkg_name}_BASE_DIR} DIRECTORY GUARD FUNCTION RESULT_VARIABLE ${pkg_name}_LOCK_RET TIMEOUT 600) + if(NOT ${pkg_name}_LOCK_RET EQUAL "0") + message(FATAL_ERROR "error! when try lock ${${pkg_name}_BASE_DIR} : ${${pkg_name}_LOCK_RET}") + endif() + + if (PKG_CUSTOM_CMAKE) + file(GLOB ${pkg_name}_cmake ${PKG_CUSTOM_CMAKE}/CMakeLists.txt) + file(COPY ${${pkg_name}_cmake} DESTINATION ${${pkg_name}_SOURCE_DIR}) + endif () + + if(${pkg_name}_SOURCE_DIR) + if (PKG_HEAD_ONLY) + file(GLOB ${pkg_name}_SOURCE_SUBDIRS ${${pkg_name}_SOURCE_DIR}/*) + file(COPY ${${pkg_name}_SOURCE_SUBDIRS} DESTINATION ${${pkg_name}_BASE_DIR}) + set(${pkg_name}_INC ${${pkg_name}_BASE_DIR}/${PKG_HEAD_ONLY} PARENT_SCOPE) + if (NOT PKG_RELEASE) + add_library(${pkg_name} INTERFACE) + target_include_directories(${pkg_name} INTERFACE ${${pkg_name}_INC}) + endif () + + elseif (PKG_ONLY_MAKE) + __exec_cmd(COMMAND ${CMAKE_MAKE_PROGRAM} ${${pkg_name}_CXXFLAGS} -j${THNUM} + WORKING_DIRECTORY ${${pkg_name}_SOURCE_DIR}) + set(PKG_INSTALL_INCS ${PKG_ONLY_MAKE_INCS}) + set(PKG_INSTALL_LIBS ${PKG_ONLY_MAKE_LIBS}) + file(GLOB ${pkg_name}_INSTALL_INCS ${${pkg_name}_SOURCE_DIR}/${PKG_INSTALL_INCS}) + file(GLOB ${pkg_name}_INSTALL_LIBS ${${pkg_name}_SOURCE_DIR}/${PKG_INSTALL_LIBS}) + file(COPY ${${pkg_name}_INSTALL_INCS} DESTINATION ${${pkg_name}_BASE_DIR}/include) + file(COPY ${${pkg_name}_INSTALL_LIBS} DESTINATION ${${pkg_name}_BASE_DIR}/lib) + + elseif (PKG_CMAKE_OPTION) + # in cmake + file(MAKE_DIRECTORY ${${pkg_name}_SOURCE_DIR}/_build) + if (${pkg_name}_CFLAGS) + set(${pkg_name}_CMAKE_CFLAGS "-DCMAKE_C_FLAGS=${${pkg_name}_CFLAGS}") + endif () + if (${pkg_name}_CXXFLAGS) + set(${pkg_name}_CMAKE_CXXFLAGS "-DCMAKE_CXX_FLAGS=${${pkg_name}_CXXFLAGS}") + endif () + + if (${pkg_name}_LDFLAGS) + if (${pkg_name}_USE_STATIC_LIBS) + #set(${pkg_name}_CMAKE_LDFLAGS "-DCMAKE_STATIC_LINKER_FLAGS=${${pkg_name}_LDFLAGS}") + else() + set(${pkg_name}_CMAKE_LDFLAGS "-DCMAKE_SHARED_LINKER_FLAGS=${${pkg_name}_LDFLAGS}") + endif () + endif () + + __exec_cmd(COMMAND ${CMAKE_COMMAND} ${PKG_CMAKE_OPTION} -G ${CMAKE_GENERATOR} + ${${pkg_name}_CMAKE_CFLAGS} ${${pkg_name}_CMAKE_CXXFLAGS} ${${pkg_name}_CMAKE_LDFLAGS} + -DCMAKE_INSTALL_PREFIX=${${pkg_name}_BASE_DIR} ${${pkg_name}_SOURCE_DIR}/${PKG_CMAKE_PATH} + WORKING_DIRECTORY ${${pkg_name}_SOURCE_DIR}/_build) + + __exec_cmd(COMMAND ${CMAKE_COMMAND} --build . --target install -- -j${THNUM} + WORKING_DIRECTORY ${${pkg_name}_SOURCE_DIR}/_build) + + else() + if (${pkg_name}_CFLAGS) + set(${pkg_name}_MAKE_CFLAGS "CFLAGS=${${pkg_name}_CFLAGS}") + endif () + if (${pkg_name}_CXXFLAGS) + set(${pkg_name}_MAKE_CXXFLAGS "CXXFLAGS=${${pkg_name}_CXXFLAGS}") + endif () + if (${pkg_name}_LDFLAGS) + set(${pkg_name}_MAKE_LDFLAGS "LDFLAGS=${${pkg_name}_LDFLAGS}") + endif () + # in configure && make + if (PKG_PRE_CONFIGURE_COMMAND) + __exec_cmd(COMMAND ${PKG_PRE_CONFIGURE_COMMAND} + WORKING_DIRECTORY ${${pkg_name}_SOURCE_DIR}) + endif () + + if (PKG_CONFIGURE_COMMAND) + __exec_cmd(COMMAND ${PKG_CONFIGURE_COMMAND} + ${${pkg_name}_MAKE_CFLAGS} ${${pkg_name}_MAKE_CXXFLAGS} ${${pkg_name}_MAKE_LDFLAGS} + --prefix=${${pkg_name}_BASE_DIR} + WORKING_DIRECTORY ${${pkg_name}_SOURCE_DIR}) + endif () + set(${pkg_name}_BUILD_OPTION ${PKG_BUILD_OPTION}) + if (NOT PKG_CONFIGURE_COMMAND) + set(${pkg_name}_BUILD_OPTION ${${pkg_name}_BUILD_OPTION} + ${${pkg_name}_MAKE_CFLAGS} ${${pkg_name}_MAKE_CXXFLAGS} ${${pkg_name}_MAKE_LDFLAGS}) + endif () + # build + __exec_cmd(COMMAND ${CMAKE_MAKE_PROGRAM} ${${pkg_name}_BUILD_OPTION} -j${THNUM} + WORKING_DIRECTORY ${${pkg_name}_SOURCE_DIR}) + + if (PKG_INSTALL_INCS OR PKG_INSTALL_LIBS) + file(GLOB ${pkg_name}_INSTALL_INCS ${${pkg_name}_SOURCE_DIR}/${PKG_INSTALL_INCS}) + file(GLOB ${pkg_name}_INSTALL_LIBS ${${pkg_name}_SOURCE_DIR}/${PKG_INSTALL_LIBS}) + file(COPY ${${pkg_name}_INSTALL_INCS} DESTINATION ${${pkg_name}_BASE_DIR}/include) + file(COPY ${${pkg_name}_INSTALL_LIBS} DESTINATION ${${pkg_name}_BASE_DIR}/lib) + else() + __exec_cmd(COMMAND ${CMAKE_MAKE_PROGRAM} install WORKING_DIRECTORY ${${pkg_name}_SOURCE_DIR}) + endif () + endif () + endif() + + if (PKG_LIBS) + __find_pkg_then_add_target(${pkg_name} ${PKG_EXE} ${PKG_LIB_PATH} ${PKG_LIBS}) + set(${pkg_name}_INC ${${pkg_name}_BASE_DIR}/include PARENT_SCOPE) + if(NOT ${pkg_name}_LIBS) + message(FATAL_ERROR "Can not find pkg: ${pkg_name}") + endif() + else() + find_package(${__FIND_PKG_NAME} ${PKG_VER} QUIET ${MS_FIND_NO_DEFAULT_PATH}) + if (${__FIND_PKG_NAME}_FOUND) + set(${pkg_name}_INC ${${pkg_name}_BASE_DIR}/include PARENT_SCOPE) + message("Found pkg: ${${__FIND_PKG_NAME}_LIBRARIES}") + return() + endif () + endif () +endfunction() diff --git a/docs/GRPC.md b/docs/GRPC.md new file mode 100644 index 0000000..025b3fb --- /dev/null +++ b/docs/GRPC.md @@ -0,0 +1,167 @@ +# gRPC接口使用 + +## 概述 +MindSpore Serving提供gRPC接口访问Serving服务。在Python环境下,我们提供[mindspore_serving.client](../mindspore_serving/client/python/client.py) 接口填写请求、解析回复。接下来我们详细说明`mindspore_serving.client`如何使用。 + +## 样例 +在详细说明接口之前,我们先看几个样例。 + +### add样例 +样例来源于[add example](../mindspore_serving/example/add/client.py) +``` +from mindspore_serving.client import Client +import numpy as np + + +def run_add_common(): + """invoke Servable add method add_common""" + client = Client("localhost", 5500, "add", "add_common") + instances = [] + + # instance 1 + x1 = np.asarray([[1, 1], [1, 1]]).astype(np.float32) + x2 = np.asarray([[1, 1], [1, 1]]).astype(np.float32) + instances.append({"x1": x1, "x2": x2}) + + # instance 2 + x1 = np.asarray([[2, 2], [2, 2]]).astype(np.float32) + x2 = np.asarray([[2, 2], [2, 2]]).astype(np.float32) + instances.append({"x1": x1, "x2": x2}) + + # instance 3 + x1 = np.asarray([[3, 3], [3, 3]]).astype(np.float32) + x2 = np.asarray([[3, 3], [3, 3]]).astype(np.float32) + instances.append({"x1": x1, "x2": x2}) + + result = client.infer(instances) + print(result) + + +if __name__ == '__main__': + run_add_common() +``` +按照[入门流程](../README_CN.md/#%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8) 导出模型,启动Serving服务器,并执行客户端代码。当运行正常后,将打印以下结果,为了展示方便,格式作了调整: +``` +[{'y': array([[2., 2.], [2., 2.]], dtype=float32)}, + {'y': array([[4., 4.], [4., 4.]], dtype=float32)}, + {'y': array([[6., 6.], [6., 6.]], dtype=float32)}] +``` + +以下将对其中的细节进行说明。 +1. 构造`Client` + + 构造`Client`时,指示Serving的ip和端口号,并给定Servable名称和它提供的方法。这里的Servable可以是单个模型,也可以是多个模型的组合,一个Servable可以提供多种方法以提供不同的服务。 + + 上面的`add`样例, Serving运行在本地(`localhost`),指定的gRPC端口号为`5500`,运行了`add` Servable,`add` Servable提供了`add_common`方法。 + +2. 添加实例 + + 每次请求可包括一个或多个实例,每个实例之间相互独立,结果互不影响。 + + 比如:`add` Servable提供的`add_common`方法提供两个2x2 Tensor相加功能,即一个实例包含两个2x2 Tensor输入,一个2x2 Tensor输出。一次请求可包括一个、两个或者多个这样的实例,针对每个实例返回一个结果。上述`add`样例提供了三个实例,预期将返回三个实例的结果。 + ``` + Given Request: + instance1: + x1 = [[1, 1], [1, 1]] + x2 = [[1, 1], [1, 1]] + + instance2: + x1 = [[2, 2], [2, 2]] + x2 = [[2, 2], [2, 2]] + + instance3: + x1 = [[3, 3], [3, 3]] + x2 = [[3, 3], [3, 3]] + + Expected Relpy: + instance1: + y = [[2., 2.], [2., 2.]] # instance1 x1 + x2 + + instance2: + y = [[4., 4.], [4., 4.]] # instance2 x1 + x2 + + instance3: + y = [[6., 6.], [6., 6.]] # instance3 x1 + x2 + ``` + + `Client.infer`接口入参可为实例的list、tuple或者单个实例。每个实例的输入由dict表示,dict的key即为输入的名称,value为输入的值。 + + value可以是以下格式的值: + + | 值类型 | 说明 | 举例 | + | ---- | ---- | ---- | + | numpy array | 用以表示Tensor | np.ones((3,224), np.float32) | + | numpy scalar | 用以表示Scalar | np.int8(5) | + | python bool int float | 用以表示Scalar, 当前int将作为int32, float将作为float32 | 32.0 | + | python str | 用以表示字符串 | "this is a text" | + | python bytes | 用以表示二进制数据 | 图片数据 | + + 上面的add样例,`add` Servable提供的`add_common`方法入参名为`x1`和`x2`,添加每个实例时指定每个输入的值。 + +3. 获取推理结果 + 通过`Client.infer`填入一个或多个实例。 + 返回可能有以下形式: + + 所有实例推理正确: + + ``` + [{'y': array([[2., 2.], [2., 2.]], dtype=float32)}, + {'y': array([[4., 4.], [4., 4.]], dtype=float32)}, + {'y': array([[6., 6.], [6., 6.]], dtype=float32)}] + ``` + + 针对所有实例共同的错误,返回一个包含`error`的dict。将例子中Client构造时填入的`add_common`改为`add_common2`,将返回结果: + + ``` + {'error', 'Request Servable(add) method(add_common2), method is not available'} + ``` + + 部分实例推理错误,出错的推理实例将返回包含`error`的dict。将instance2一个输入的`dtype`改为`np.int32`,将返回结果: + + ``` + [{'y': array([[2., 2.], [2., 2.]], dtype=float32)}, + {'error': 'Given model input 1 data type kMSI_Int32 not match ...'}, + {'y': array([[6., 6.], [6., 6.]], dtype=float32)}] + ``` + 每个实例返回一个dict,key的值来自于Servable的方法定义,例如本例子中,`add` Servable提供的`add_common`方法输出仅有一个,为`y`。value为以下格式: + + | Serving输出类型 | Client返回类型 | 说明 | 举例 | + | ---- | ---- | ---- | ---- | + | Tensor | numpy array | tensor array | np.ones((3,224), np.float32) | + | Scalar:
int8, int16, int32, int64,
uint8, uint16, uint32, uint64,
bool, float16, float32, float64 | numpy scalar | Scalar格式的数据转为numpy scalar | np.int8(5) | + | String | python str | 字符串格式输出转为python str | "news_car" | + | Bytes | python bytes | 二进制格式输出转为python bytes | 图片数据 | + + +### lenet样例 +样例来源于[lenet example](../mindspore_serving/example/lenet/client.py) 。通过lenet样例来说明二进制的输入,lenet输入为二进制,输出为Scalar。 +``` +import os +from mindspore_serving.client import Client + + +def run_predict(): + client = Client("localhost", 5500, "lenet", "predict") + instances = [] + for path, _, file_list in os.walk("./test_image/"): + for file_name in file_list: + image_file = os.path.join(path, file_name) + print(image_file) + with open(image_file, "rb") as fp: + instances.append({"image": fp.read()}) + result = client.infer(instances) + if "error" in result: + print("error happen:", result["error"]) + return + print(result) + + +if __name__ == '__main__': + run_predict() +``` +上面lenet例子中,输入的每个实例的唯一的输入`image`为文件二进制方式读取的bytes。 +正常结束执行后,可能将会有以下打印: +``` +[{'result': 4}, {'result': 4}, {'result': 4}, {'result': 4}] +``` + diff --git a/docs/MODEL.md b/docs/MODEL.md new file mode 100644 index 0000000..a8b1cb4 --- /dev/null +++ b/docs/MODEL.md @@ -0,0 +1,147 @@ +# 模型配置接口 + +## 概述 + +MindSpore Serving的Servable包含两种类型。一种是推理服务来源于单模型,一种是推理服务来源于多模型组合,多模型组合当前不支持。 + +本文将说明如何对单模型进行配置以提供Servable,以下所有Servable配置说明针对的是单模型Servable,Serving客户端简称客户端。 + +## 相关概念 + +### 预处理和后处理 + +模型提供推理能力,模型的每个输入和输出的数据类型、数据长度、Shape是固定的。 + +如果客户端发来的数据不能直接满足模型输入要求,需要通过预处理转化为满足模型输入的数据。 +如果模型的输出不直接提供给客户端,需要通过后处理转化为所需的输出数据。 + +![image](image/resnet_example.png) + +比如,针对Resnet推理模型,客户端发来的数据为jpg、png等格式的图片,预期返回图片的分类。Resnet模型输入为经过图片`Decode`、`Resize`、`Normalize`等操作产生的Tensor,输出为每个类别的得分Tensor。需要通过预处理将图片转化为满足模型输入的Tensor,通过后处理返回得分最大的类别名称或者前5类别名称及其得分。 + +在不同的场景下,如果来自客户端的数据输入组成、结构或类型不同,可以提供不同的预处理。如果对模型的输出也有不同的要求,可以提供不同的后处理。比如上述`resnet` Servable,针对返回`得分最大的类别名称`还是`前5类别名称及其得分`这两种场景提供了两个后处理。 + +### 方法 + +上述的`resnet` Servable提供了`classify_top5`和`classify_top1`两个方法(`Method`)。`classify_top5`输入为`image`,输出为`label`和`score`,返回前5的分类名称和得分。`classify_top1`预处理和`classify_top5`一致,而后处理不同,输入为`image`,输出为`label`,返回最大得分的分类名称。 + +一个Servable可提供一个或多个方法,Servable的名称和方法的名称标记了Serving提供的一个服务,每个方法对客户端提供的数据进行可选的预处理,接着进行模型推理,对模型的推理结果进行可选的后处理,最后将需要的结果返回给客户端。 + +即,每个方法: + +- 指定可选的预处理和可选的后处理; +- 定义方法输入、预处理、模型、后处理、方法输出之间的数据流,前者可作为后者的输入。比如方法输出的值可来源于方法输入、预处理、模型或后处理; +- 指定方法名,使客户端可以通过方法名指定使用的方法; +- 指定方法的输入和输出名称,使客户端可以通过名称来指定输入、获取输出。 + +### 实例 + +每次请求可包括一个或多个实例,每个实例之间相互独立,结果互不影响。比如一张图片返回一个分类类别,三张独立的图片独立返回三个分类类别。 + +## 模型配置 + +以Resnet模型为例,模型配置文件目录结果如下图所示: + +
resnet/
+├── 1
+│   └── resnet_classify.minir
+├── 2
+│   └── resnet_classify.minir
+└── servable_config.py
+
+ +目录`resnet`指示Servable的名称。 + +通过`servable_config.py`配置Servable,其中包括预处理和后处理定义、模型声明、方法定义。 + +目录`1`和`2`表示版本`1`和版本`2`的模型,模型版本为正整数,从`1`开始,数字越大表示版本越新。 + +### 预处理和后处理定义 + +预处理和后处理定义方式例子如下: + +``` +def resnet_preprocess(instances): # instances: tuple of instance + for instance in instances: # instance: tuple of input + image = instance[0] # input 0 + # input1 = instance[1] # input 1 if exist + + # Decode, Resize, Normalize + image_result = ... + yield (image_result,) + +idx_to_label = ["person", "car", ...] +def resnet_postprocess_top1(instances): + for instance in instances: + score = instance[0] # get input 0 + max_idx = np.argmax(score) + yield (idx_to_label[max_idx],) + +def resnet_postprocess_top5(instances): + for instance in instances: + score = instance[0] # get input 0 + ids = np.argsort(score)[:5] # top 5 + ret_label = idx_to_label[ids] + ret_score = score[ids] + yield (",".join(ret_label), ret_score) +``` + +预处理和后处理定义格式相同,入参为实例数据组成的tuple,每个实例数据为输入数据组成的tuple,每个输入数据为**numpy对象**,通过`yield`返回实例的处理结果,`yield`返回的数据类型可为**numpy对象、Python的bool、int、float、str、bytes**组成的tuple。 + +预处理和后处理输入的来源和输出的使用由[方法定义](#方法定义)决定。 + +### 模型声明 + +``` +from mindspore_serving.worker import register +register.declare_servable(servable_file="resnet_classify.minir", model_format="MindIR", with_batch_dim=True) +``` +其中`declare_servable`入参`servable_file`指示模型的文件名称;`model_format`指示模型的模型类别,当前Ascend310环境支持`OM`和`MindIR`两种模型类型,Ascend910环境仅支持`MindIR`模型类型。 + +如果模型输入输出第1维度不是`batch`维度,需要设置参数`with_batch_dim=False`,`with_batch_dim`默认为`True`。 + +`with_batch_dim`为`True`,主要针对处理图片、文本等包含`batch`维度的模型。假设`batch_size=2`,当前请求有3个实例,共3张图片,会拆分为2次模型推理,第1次处理2张图片返回2个结果,第2次对剩余的1张图片进行拷贝做一次推理并返回1个结果,最终返回3个结果。 + +![image](image/resnet_with_batch.png) + +`with_batch_dim`为`False`,主要针对不涉及或不考虑`batch`维度的模型。比如输入输出为二维Tensor的矩阵乘模型。请求的每个实例将单独作一次推理任务。 + +![image](./image/matmul_without_batch.png) + +### 方法定义 + +方法定义的例子如下: + +``` +@register.register_method(output_names=["label"]) +def classify_top1(image): + x = register.call_preprocess(resnet_preprocess, image) + x = register.call_servable(x) + x = register.call_postprocess(resnet_postprocess_top1, x) + return x + +@register.register_method(output_names=["label", "score"]) +def classify_top5(image): + x = register.call_preprocess(resnet_preprocess, image) + x = register.call_servable(x) + label, score = register.call_postprocess(resnet_postprocess_top5, x) + return label, score +``` + +Python函数和Servable方法对应关系如下表: + +| Python函数 | Servable方法 | +| ---- | ---- | +| 函数名 | 方法名 | +| 入参和入参名称 | 入参和入参名称 | +| `register_method`的`output_names`参数 | 出参和出参名称 | + +`call_preprocess`指示了使用的预处理及其输入。 + +`call_servable`指示了模型推理的输入。 + +`call_postprocess`指示了使用的后处理及其输入。 + +`return`指示了方法的返回数据,和`register_method`的`output_names`参数对应。 + +方法定义不能包括if、for、while等分支结构,预处理和后处理可选,模型推理必选,且顺序不能打乱。 diff --git a/docs/RESTful.md b/docs/RESTful.md new file mode 100644 index 0000000..049564b --- /dev/null +++ b/docs/RESTful.md @@ -0,0 +1,231 @@ + + +## RESTful 接口使用说明 + +MindSpore Serving支持`GPRC`和`RESTful`两种请求方式。本章节介绍`RESTful`类型请求。 + +部署`Serving`参考[快速入门](https://gitee.com/mindspore/serving/blob/master/README_CN.md#快速入门) 章节。 + +与通过`master.start_grpc_server("127.0.0.1", 5500)`启动`GRPC`服务不同的是,`RESTful`服务需要通过`master.start_restful_server("0.0.0.0", 1500)`来启动。 + +### 1.请求方式 + +当前支持`POST`类型的RESTful请求,请求格式如下: + +``` +POST http://${HOST}:${PORT}/model/${MODLE_NAME}[/version/${VERSION}]:${METHOD_NAME} +``` + +其中: + +- `HOST`:指定访问的IP地址; +- `PORT`:指定访问的端口号; +- `MODEL_NAME`:请求的模型名称; +- `VERSION`:表示版本号。版本号是可选的,若未指定具体版本号,则默认使用模型的最新版本。 +- `METHOD_NAME`:表示请求模型的具体方法名称。 + +如果使用`curl`工具,RESTful请求方式如下: + +``` +curl -X POST -d '${REQ_JSON_MESSAGE}' http://${HOST}:${PORT}/model/${MODLE_NAME}[/version/${VERSION}]:${METHOD_NAME} +``` + +例子:请求`lenet`模型的`predict`方法进行数字图片的推理,请求如下: + +``` +curl -X POST -d '{"instances":{"image":{"b64":"babe64-encoded-string"}' http://127.0.0.1:1500/model/lenet/version/1:predict +``` + +其中:`babe64-encoded-string`是数字`1`图片经过`base64`编码之后的字符串。由于字符串比较长,不显式列出。 + + + +### 2.请求输入格式 + +RESTful支持`Json`请求格式,`key`固定为`instances`,`value`:表示多个实例。 + +每个实例通过`key-value`格式的`Json`表示。其中: + +- `key`:表示输入名称,需要与请求模型提供的方法的输入参数名称一致,若不一致,则请求失败。 + +- `value`:表示具体的值。当前支持的`value`类型: + + - 标量:`str`、`bytes`、`int`、`float`、`bool`; + + `bytes`:通过`base64`编码方式支持。 + + - 张量:`int`、`float`、`bool`。 + + 张量通过数组格式表示数据和维度信息。 + +`Json`中支持的`int`类型:是`int32`表示的范围,`float`类型:是`float32`表示的范围。 + +请求格式: + +``` +{ +"instances":[ + { + "input_name1":||, + "input_name2":||, + ... + }, + { + "input_name1":||, + "input_name2":||, + ... + } + ... +] +} +``` + +例子: + +``` +{ + "instances":[ + { + "tag":"one", + "box":[[1,1],[2,3],[3,4]] + "image":{"b64":"iVBOR...ggg==="} + }, + { + "tag":"two" + "box":[[2,2],[5,5],[6,6]] + "image":{"b64":"iVBOR...QmCC", "type":"bytes"} + } + ] +} +``` + +其中:`iVBOR...ggg===`是图片数字`0`经过`base64`编码之后的省略字符串。`iVBOR...QmCC`是图片数字`1`经过`base64`编码之后的省略字符串。不同图片编码出来的字符串可能不同,上述是示意说明。 + +`bytes`类型需要通过`base64`编码进行表示。`base64`除了支持`bytes`类型,也支持表示其他标量和张量,此时需要通过`type`指定数据类型,通过`shape`指定维度信息。 + +- `type`:可选,如果不指定,默认为`bytes`; + + 支持`int8`、`int16`、`int32`、`int64`、`uint8`、`uint16`、`uint32`、`uint64`、`fp16`、`fp32`、`fp64`、`bool`、`str`、`bytes`.  + +- `shape`:可选,如果不指定,默认为`[1]`. + +例子: + +如果要用`base64`编码表示:`int16`的数据类型,`shape`为3*2,值是`[[1,1],[2,3],[3,4]]`的张量,则表示如下: + +```json +{ + "instances":[ + { + "tag":"one", + "box":{"b64":"AQACAAIAAwADAAQA", "type":"int16", "shape":[3,2]}, + "image":{"b64":"iVBOR...ggg==="} + } + ] +} +``` + +其中`AQACAAIAAwADAAQA`:是`[[1,1],[2,3],[3,4]]`经过`base64`编码字后的字符串。 + + + +##### 支持的类型总结如下: + +| 支持的类型 | 例子 | 备注 | +| :----------------------------------------------------------: | ------------------------------------------------------------ | ---------------------------------- | +| `int` | 1,[1,2,3,4] | 默认`int32`表示范围 | +| `float` | 1.0,[[1.2, 2.3], [3.0, 4.5]] | 默认`float32`表示范围 | +| `bool` | true,false,[[true],[false]] | `bool`类型 | +| `string` | "hello"或者
{"b64":"aGVsbG8=", "type":"str"} | 直接表示或者指定`type`方式表示 | +| `bytes` | {"b64":"AQACAAIAAwADAAQA"} 或者
{"b64":"AQACAAIAAwADAAQA", "type":"bytes"} | 如果不填`type`,默认为`bytes` | +| `int8`,`int16`,`int32`,`int64`,`uint8`,`uint16`,`uint32`,`uint64` `float16`,`float32`,`bool` | {"b64":"AQACAAIAAwADAAQA", "type":"int16", "shape":[3,2]} | 利用base64编码,表示指定type的数据 | + +### 3.请求应答格式 + +应答格式与请求格式保持一致。返回`Json`格式信息。应答格式如下: + +``` +{ +"instances":[ + { + "output_name1":||, + "output_name2":||, + ... + }, + { + "output_name1":||, + "output_name2":||, + ... + } + ... +] +} +``` + + + +1. 多实例请求后,如果多实例全部成功处理,则响应格式如下: + + 例子:`lenet`请求识别数字`0`和数字`1` + + ```json + { + "instances":[ + { + "result":0 + }, + { + "result":1 + } + ] + } + ``` + + + +2. 如果部分实例出错,则响应格式如下: + + 例子:`lenet`请求识别数字`0`和一个错误数字图片 + + ```json + { + "instances":[ + { + "result":0 + }, + { + "error_msg":"Preprocess Failed" + } + ] + } + ``` + + + +3. 如果请求全部失败,则响应格式如下: + + 例子:`lenet`请求识别两张错误数字图片为例 + +```json +{ + "instances":[ + { + "error_msg":"Preprocess Failed" + }, + { + "error_msg":"Time out" + } + ] +} +``` + +4. 出现系统性或者其他解析等错误,则返回格式: + + 例子:`lenet`传入非法`Json`字符串 + +```json +{ + "error_msg":"Parse request failed" +} +``` + diff --git a/docs/image/architecture.png b/docs/image/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..a4ec5853bb37a7cce2a311b7503ca1d0e12f6ab7 GIT binary patch literal 1195614 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 literal 0 HcmV?d00001 diff --git a/docs/image/matmul_without_batch.png b/docs/image/matmul_without_batch.png new file mode 100644 index 0000000000000000000000000000000000000000..4d5873d13e4ebc13f47144433469ac20c33dee8a GIT binary patch literal 9499 zcmc)Q1yCE|z9?{@Pyq_1E$&dDxV3nLLxEB#v{-Sc6o(KrP>M_1;_e>Y-3b~B6n7}r zV8Mfg8_qp<=G-Iiop;~7dEZQu&F^X*-q_43Ug`@v&hENnIqpORF`+@ z+2u(h=H+XUjyUo#3hG*MWMIOSJQF8(tlI^R>i5f`At50N5%>}$m=gWYpAECxRDk0^ z4pZJJ;H6=Sm_nVeW$WsBBwb%AJ9HI$T-b zCaStFIJyTjS*TmarH0!W(g3`t7cyfWa3@V{ARHi{u1T@rY=)~H_vp<}cV>8K_p95( z!^1agll71TJB)!uZ=V=uZ zzb7TZPhUQ<9?M8-k|iN7cbt(nU3+W*ShVrK_vqBGrSN;yt1!1m8^}CUJ*)?L~`rKgExdh zqMPzaCLT_Gy3woXN|`l1Wu3e9OQgyT z1CjMUDx4dt`>Gk?p`u4HRD{uJjE9RS$qqJioiWlp`cb^pY35nt%S%$Norv7?eXhs+ z$zT4`A8`re#F)yS;Kgt-KjAi1gqU;O-Zh;MgVV!lh#G_Gy# zje0zCB@uEj`2C3=#FF3%L^{{@u7LBgtPG@(YSH)kkkqUmaGUPGgDRJ&i`v;HZ4 z`RodvH>44WEjjUpo_iGL~r$w0ALgkf3?b$Ui?HJ262Md=nJ$MJXqsYO=m{Z&hQM|k}; zU;w?gDH7KZ-lcoz0Z&#-L0IvE_gNyz;3T=?(P3#ow-w%_(N9=v*Y0EsCkCrbJnZjp zkF$UrURn$Va}Kdf-KmK^9ZaX_A(Q2nAdRDI?&uPkN$atXU{ot=Ypw=Jz;On0-W{Jz zv!QOY(t8)oprP=T<2IUt?IURU^NgomSwhl95&I6qi0E;;rW|ieM5kBIaX2(JM#4h%Yto zx#k^u8--F4y-OL&8}cQraGHux#%8;w`c6LKEM<*goX zFsiv&;AUSlC_)bw)G4X3Z8*d3eR`N&-D)Y|`oEkenslNHd&hsJ+&cy=P!S^0)vhxK+~s! zA>!_#vTP$w;)Eq_sCQwLM#LV`W{*SptdLzBKA%bd1h^A zip%KlXlS|`Qr7Ziuj*c>sv9qP3N1cKT;B(vw|kiA;OWmI-{-PkFEV7uKj}pLNZZ!~ z8)!F98r9txiTN%ljZE%Wir#6@_!Htpll+lBU6M9tXf{7iz-7&bDCIS4&x*Ze+H8;!LtsS~C8t-7`J)SZXw|PD?ZJvqC=nw2b+2h z*4Anc?(QfGk5`N<{AoeGa5&-e7UTHg?L5)&79j%IEL7?0{V8l!1wC6ANgc9K>|`|b zL<=t7T;bkRGhkrsiE@`>32HfK2)8d6b?DM`j(Xa}OWciI_ZMN3;dJ~6vA(D&4jj%u zkGCpm+b{$#7%o#Xa(lSg8t_=AuSJfQRdUq@RBEDZrm$Pf=2KY&vmOk*HeTSQ=d*~B z-le_y-P`^dIOG$MV7D>^vgBImZ0Ub30YiV4>MZdi>0hIl=u9v`Y@N1KD)6)fh!<~O z37-ez9?g-($0?H#rc1gk!MrvN~o=re7UIG+2{5l@p8YZ-|&Eycw~MsW1aSXoe1U4ipL1!Y2!~tk{f$x z%@?TO4)BTcN9UJyv?A$)r<#|#$CKDH3Jcv=h%XPgi3>M1A6Rc7^r-D#F>k)^4~=YU zPqFaQso$Ws-_s}O>S;2`pEPnQYqRG)D8G2eJV3m2m$B7jsA*o z%U#Rs*3b4kDLskj;S{l5Tl1|%B>{vP8>H!Z=Z~l;m3!8FL6nQ{L!mORCHi|vB*EGR z+NABKM*if;`xqI#a|MTQ@mcZfFs?m1N?TO4+@C`opJ;tGa1)EFFH2~67MM)_#{4a> zBw>cZ;3N2MYDdah6knasofPh+8KhFT=#`Cbk4z{0C`HvTIv%G$tA_#kMqpcg=MGo> zHkRH}sx8O!mz}Z_J9-xur#tieYD!AOzl-lU;hN9kPnK&lM3#o;%xydFEk2gYo^-RB zEf#~cx(4ZpfBo3!brrn%DtOFbJNwzt6@jT+acUY5bUp!Y+z^`Mj=X(&_8gyLaiLm6n(K6|`8io5W zBUMsfr9V6$b6-^@q(RZ8&h2E|E@{mJps(tx@rSezD`B+1QTf6LlW-%EL>%c3!)KN= zg)wQnGK5+?6|fnX-f+Z)@b%y_PTxE@#X%!l)NAU!mLYO~1}GlqC-;MP**waiBivq) zZgmTnb0t%^IT3Qi1^wzExqM*d5NiyPN4U!16A`zJ2m|`4d@ebxKhWa_UcxOvMD<6 z#5W6MIy7nuYaXaHd5O56UlRo{M{DwUx7p4Fe18Mu&Du?kjg7As?)c3R(M+oMqP?D) zm3rPYiz$dLxG77&`*%B{7Q@MwSkiKM~!swN5aQGzV|qP^YJA3F5o`5At^T8gs?kGNgG z4Rt?!jmtmfD4tbN^VKOn$YvtLk|pTmv(#A}2}pOLq9Jh7NU*dmO>>#j@O*}0U4}#V z>489go#gPPYpzE+k3_50!&H02uJPoLRZz9Jwn4+x$;`1w0%+|Ur(G&DOYWi(tUkzUxH)km5NuyiewAPTel$FQ_I?IDAmK@rj^AKV=J_4^x z){CHzQZPd;zj&fY38x8X8+{l2txcsMh?JhbJ|V^MfDrFL6X*Rq1rS;h6rMRrOO}5&Qm6Zp4eVdy}k1S%KA6 zR;U_hW(A;izie0CsQaSKMLMFxAXTRx^&Z+QveR9HRi!O|zp6rkkP!P5l~yxy*v-;#W@hQJ3GUCMpw|&>LVOz*MhbO@ zY>1!ik7?BR2iCYqLrQmHxvJ^>h&#NqdOt$Setlb^#W7f_uTn>K2X1uXOU9?xt_-aE z{pb_b_*@ixuDS4}H0aay%k>L{C?1`b806;O6C&v9M1uTK*&`W9C<&pvg`cQExm8X% zGS?k+ddxD$-TFQ>B4a<=UEq3F@@@jj{Er5gDCe_zRYK~ds2YvuQ)&_B-EPu4P?I-0 zd|!^ng#~Qq<5S+&u4%SPWS)E)<@`0l(jl{Jl0B`9(o6%LsN})CWxN{$@l7&aDGj1Y zXalx{d?cug%GUdHsq0;w@V5c$p`WL}%m~*6IhPU=2Z%B68LJL}8*XZHkHsBwEfJGf zUfWWTxd26FlK%|SN89?b(7*!mpeinV7GhpK<#b(LP%LqV!nkMk`S?dS2hTUnflb6^ zD}i3*-$|d!Kpv0~4yyTUCwQ;oca_5+kLcH% z%Id0&;gZT6tVm;}e&DBC?}%=>k?UGK!DO**AkOsLFzYc|fmnxSL1{!6_0_kL^$ zCn|V4n-HjyX1el&mW2co^1I`Yc50zuynws^Ms`edb@dAkP0a-iK{)XbC+8WY0o^H9 zrAOi27l9JLMk?a6p@^DakdU{jWBNDd%B89z<|MZl9uS@^` zg7Lp@d2M~i@57+c@1qg-wOtGDP*8W5#cYf9R3UG`Jz}8ybBnR0o7N%!JNFi7gJM1e zMb^?@{LED$cULs^JcSbGy={FG-h=U&RYr}f0l{^D#R9{YMim$}QE=DyDQvrs6R@ed zw@Y-9;m@Sl7bfihZyH-9n%wtqEB+B59}8HZ9xeAxXp;RdU_E}y44V7$@MGBe&G*V<5J;P1^izz(1m)g~i9KsQrocd=4eS5_(|4Bu4ik zB3RHx51jO(VB;2={`^8lY=9r&LziIzXD1jbT4e-!$EK96xe@<&5&PRCD4}w}5foFk zYF1@Zh37O^^viT#z-__~cjoi-XMoP&Lz54l74GaBP>imO3aFS0-9nMpVyeg9PnNN< z$GU)N-c73+Hr-2g$t0~47)dQ(D1FP9#uTiD@Ch)Q&Hyc zk8?5LWXS()V`&<~ZJ}NtxuNE*GP#~k`q31=`xivgGnT{e_~%Ll++Ckx?>Be#^kNTGwo546hHzmtdz1d^3cV-a?q(9df}cq5wE?vcK-KLbPakrw7uTBpuF|xwa$vEAOdEhA|2YgjQ6EymC`kDg z+eQSh(Q0D)J>>GYBD#ao&nBy*ILsP3gxT$)nfloqzXVr9Rr*FE+nI8MgXc?lETRTO zXW$)?)YCV+!qv!Bx1N!YV5qBQPAbdg70J6K7qic8Cfx!+lw(!U8MvMvr;d zYBk4IxV<>*PI1LkQ DhJxLxCDYDhjeHk}!FjJn;C*Sw^&I@L|6(WKcZ2_@>@=in zJZtMu;uOlfEiy;8ARE7R|C3O|cU@Rhh2uJNb))G=M#aN-r}UDriV>jG`M^gh0n?d_ zRqIA!sPxIjLc+29owSR6uEuyywP)I`Nh-3R`tb#V& z?W0$o-Bp@R4{c6J@U5C=sfVJ}ytgkcX?%`G>uh()-tQrQ&*qa+kW5+pMWg5hJ`;iv zxPy1sTW8MGZ%Jwd-n{HZuM&U4#6Z80+bSGE#SCB={w|-irm)BDxXhF$O+TtU+LV%T z{a48H|69oF2b&U|p5@CNn*+_s44Ux7ATq~^o0w|>OKX6x2YYyRID1acvwE$0Y#jvO zrxQjZpDVspj`e*c!7X9ER$^Q?rP7Vp5}2;Tata4MB5wGyxxB;1x#hTj_6zKdQyh$^ zN#o8U`eSVdh0^7yEmm8@QUlSAf8}A@@W%9M{~>?w_Q53L>hLirAc<-klcq&|6yTnX4cGtm*8hy{T{R>#R6B{xl zeKo|Xr1qz1=;ktpF?+Hi^T2)93+rCL z>2~O^f^i4{){_(y@Sh0Z`qUxM<^NRn=$8yloi`F@@q)Tct9&S>3o_@f! z=vl-3%UuH6KN}bis5uV=bvvnvssrl%^owBZ1P*$X}pbfXlt(xRN0D`zu~n~eb~m4K#_OXaXJ+co4brBm`+OW0dnJ!N(nCSDzkd+|ZdP8b8BTZpWSc^E zz?sd>8!A=6dzGyVGHq~T&CCt+A~&zd3Qe*xK2HJ0RC2+aIY~>sG_$4tD<$__2sBy( zxPbcy;1siD&s$Bu@TTbxG2d!5j_*_7Sf+lKwp2t0Z3;ZZLJu~$NhOqMLT^NpAMLU8 zTBQ3bcqzlSZu{$08~xG&#Z`9ta$2NKY0U~hN+C?sGbnZBG}ZjLDqY~J5P~`n&o;M= zoj+mtD@j4A|DTc+>^$(wsm(uE1GkHfc-?5?nmsS(5~cSRoaAfpwFN#bg6*9Il)yC_F*2=)?jM?UJXnjApg7`qpV}L~ zYtXRPr#yHs-?5L6PXN4XKSF1uaX(m%>QTg(<1I2tUT|ncnVR!1&dd(d%%HHbO z>p~>_u|jF_z&v1@rW0?d@2wScJ3aXoYl_)b}0=CY+n-2 z1_B_v`KMJ|C3n6?G=`$4@Sf<=X^iAQ{y8*NBQ)!&-1_}TrQ|6M+5$NP|Q@(UFn|0vXXo$Y#Gcye}@iqR_0YY*xF=o}_s708dLzx%V);{N6-+!*eq zm@wCh&GV}MLrB>sJ3jdxs-t5tu!;Z67xCnn-rVXpR1-xzd+N?7N4!d4)J((g7lt1% zx%cfCAu9%en@CT*v8|-njj2SNd)(MBJ{iR4wG7$kqoo!+b%DM{EnR0AFb$JS9_-RiiPrJ&UWpT^^WUPkVd&d6PJG6|D#~GvfN} zd9VHnQSWmj|FB$h#~BSZN6pRW?!hL?)TcCN?{4c?h4zn+ddhK3iHPo7n0^||G#!t!bVu_rOkFBgu zkGNn4+Vw}x+Cs#ID@{(3RtCM-OvniUIP#0OENi2~nBsqT#Cq)PRFLG`O}nrklxBHr zUQ85`^}P}nviRxdR!Cv&V#I*C$M?J7OQ_&c3EyXMI{{ikPcy^0L&vcnQjD_q1uFWC z&q&<()ZTlUp^_bjt2>VJ*yK&=fh>oC^-%{!nyIX5yol@~xn+v0WC2-fMBMI3rGB zQ}f_Bi5K3~KAYS*Hw|?26QVK7Igh>g0x*yCGfRq>l#+n{vM|lpBO)*q*S$h3_`x;$ z4USB)dU2N3f*BKPyh2HYHaVu7`GK~h)6UJp73CjgdIhXk4k6tj(>rE{rxvc9;7X!Y zrVi|rQVF<*|JJOp{_HVj72qJNgeCM``61;ZWy9_9eqX>5>**|-4)26-h z33UzU{lLV$b@@B>P1`BNsQ!P#NEZKdMlvYvzuMFEB%bd4zLdsmRPJX<0Ig0&KNpAL zHi7r4*$wn*F2TZJ`;rx9HnOZe3V3CGtVFXlyN{*C$_IcSl`3L%Rn1|%Wt$&Kwg+eq zBHuHR(^9|xidBw}OA^n}#5t6cu&onwIkBsEJ<{m^P0db#c_Qj`{gNiCd9T3VSGd|_ zXqS6?Zd`WJCgfwu955)*0o;R`y#L-)AIQmzWYVcM7Ez^o?{b^LVS|oO$>|1tnuIE? zW_sLC_2t^cwFEa7FSqCF%2Z%p8Wrpq3u2O&`nwF4zn!oD(;ScgdqMgun)D9NU^`g} z)-zk;88p%G-s*_mLThqg-|QYy_Rz&1>`6pwt$)y{+dOSpjC8UP`0e_#x_j_DAqvB1 z2Cpsprrq|UPbr!eQ{S7}YMUpp zC|VbH3XPzbHe+9byR)~6FDARQH95Jt4dR4Ih89oEe1)Ms*DRInFN~(qxJCDGW&Qj$ cW=A&&;WoQg#StA$b`zG8{9CzF8RIYi0mEhBg8%>k literal 0 HcmV?d00001 diff --git a/docs/image/resnet_example.png b/docs/image/resnet_example.png new file mode 100644 index 0000000000000000000000000000000000000000..3a115bfc7fa326b35a7323a0bd5f50e50d3f60c6 GIT binary patch literal 72658 zcmdqJWmp|cn=ZV7;2zv9xVuA;Kya7f?(QzZJ%kV-xVr^+3+}<)-QD49viE-Ho$JiZ zcjoV@1sAZIuCA`?s^__-R+zl3I3gS_90&wLl=vvB2m-yC1cAW6!9W4;47D4t06*S1 z3QH)%0L2T&C=~b`$LWK*laj58ldHaiG04=$*4mif(a^!z*v8S!*69SIO#lQU21$qt zDZ8Z{F1f2?Ouay#-YlIML(d7x+k?F!XTL#L=qIgXAshNFsnwdBmn4;IPGht6^@(kQ z4P)Y`7SqfkXSG%bhASj0r>b$&&EqqQ>laUBoc5aocQ-;~laafjBpdJUoMhg=-;J)Pd@|~_3si3JDVvc`gbt`cPaSi0BVvSfyF-uL@D(C_ov895`a(@ z6jJ%z-?ly9(*~j_h6w*v;nug(Qu<&d0*lE;mJUjJQkoqK=|sQrZ1Ig`+tyef^uL-- zlOK$T7reP?q^_xn)yTj*8CtNI%%l&lsHlj7ib~>4^;c!evU9VutLxnnV$KEjNbLXI zGAUYxNYp>K-1p{Cp6s6^EMP!Y{}k!=zfYkKTL^u2=L&vwz;00NeQl`N-`y1Z4PmCz zaQWue*p%-QvV`=lNp_SfRFX9C1vbC~A2_T0r(Mb&e_x!YtckMn z@`4j()dn$20~+sU9_Ek(!*k#hbi~jZ!4N-oKq8qXQ$e)izTH4nig(=j)lsZ^y3v={ zJ}A;lj0ywVS@3zFb2*ma?3t0{*39dj$#j*tY^TB@mmU3>64kD#C zcSI`i|KRH|f4ZS45kalO^yb5CGdJ$UL$m)jB3`yizFp+~HNP7z;U~HDQ0tYe_g~U^ zOPsgu&fPH}NMkl+mkna830Uu4KNN!)>s5aGiRJnSdCOetq=tVJT=p+*v+1D$M+?(e;!Yeao^>>Oi<;F{55xYK)vZ{nm!jxnmbm zO*&br^vjdmEW_Dti@3}z4@XDe5>DQvyqPC9UdYEgs{dXX}}f4EG=2 zgMpGfD@``6m5(sNSDKrw8@wT$h^|;Y+_^gGC<%E@5PD84bPBwz`0wkNG{w^`L$0JX)^z)_G>yw&}PX7*j5uWW7~OFjy?Xb8|eCl|D3s!11GqD&CkTB$C$pi5CYR zaN^wBXRYv3dUK|#G_|1;s^?^r`=Pv`&HfplNLdZ!>Pj!Jtcd6}RzpZwV!a)(${^tW zQW)crmQZOj1#`&r?IJ^Dn5BY9>0>9exTa!vIz2I|4@vKG;6&}P2kT|>-;*$(XlER zv$oz=H8y3!*@{%;-*`gb-e2N5=xFfLy~ZnXTUyNOq(NT|e3=MfMXBt9&4vOEo0eqaST>Vmf-pp7d12l zt|KrxC0^x$yBHcJ^cnSHFS(XTvc8TWw31Bly?63@YRqsw7ahv^l#!F<0~meL9qH|c zZKazn_|G(jvd#(kw4X+c&AGnOPvoSeG(Px$E#(*~;I#tf8*zo@8(oB`FgD9X0pI~= zH@#(k9iYrpv9_(YY}=?_ai;zuZ9Q6uv?WfmfF;YN@#Q?)?+m;_{!h_sGx3FH925xb zCs_G0T|;9?*h!OM8+pDFs#A1doXuhHUDM8nzCyksj_W93U?jWFR>sB&8DYd9jCSr> zV~MkKt)wxHvB81v@i`>)QPdq zjH~0gLVDB-@6CXmRU#@+u$dZKIM0(yOU4+XDm*aOmT1V#*7zRH?a?i_2$){9V?xS5 zZ}yPhzvsY>BnSZ`C|-PM5l2W#zk)KDXRwMM36|;w)eGFqhDjsV;P!o#>CJ|iiJXfG zxvW1Qm42-5#Z0|-Wt>*9pyjLKA7+;ilnL_gM=`OKtpS~^5d)Fum3AAZvi7k9L?K`jwxIximKR#>42 zJdsm-Jlk0k$b@Lq_BvNrHWtQeOb{;H@f1Ac(+alNqcMlpgH;z^3=*u82U)wEe}QhV zp-m}a!AZ#n*>&c3IG{Rl$$p+Xtb;oBcD}1O)3QVXy1zA6ALM=rE`NIG`q@u~{<>w`lG)KZ3uf8aYQ75Z3rZ)@L0jTOT z5FFu7DGLq=oca(Cl0E!s*lNfbB{(48Vd+OqW(>sp$&e58+*-9(A!*i2+&74*s2H$+ zMa=>O79)|;`XCvldxL>H%a5FiU2{#;jnCDXh6^jneS|EQZ)D~^UlHo+4~-h0ts!%M z$*mQM=h0?fTe9(MuwP;b*B~;G59xPvwKhb!tv=h(CCxbPXzz}&d$>3Yb$j7_m$Uia z+8mlPJ|;u}0a}UVTjP z#ZnW(y4A|hrc^6x1=*PXO>J2)`2kI)HCn>@Q;(G4ywak-04*#Y#dW7^f-RYpKA3np z6iiYh!b)=%RAeIS$m|A%BSLr1%R-{7EXZ90q!Nps*Nq0&8lVGY`%KaQ?|P{v{jn#O42mQ!>8s zfJn2>DX6Jo#bXG)4_$|oIYU&7KbV+MxE#%2O>+47_}IC)K!FbDYv7ZTl49sxU0p-! z>Nr8f92}ToVPP@!GZr;w%gx-N)q!|gF8A|ICFgG@2*8ohs3`ht?3C2hwVj<_Y;2LF z$hFP>xO#^z$;#f#ohC#OrLL82BN_z04=Hk?R*W~ShCysoFV)Sh3b zd^Zh|LO-wvcwe*XB(vW+61T@qCu4as$+tmoN`jEwkdhqJkj?#{Pn>ugsH2a8op?^j7mGx(-+B+#hhCAHdoTE-4) zDzMKVk~^@0da2r&7he|4r4dFQ(+9C71%r2Y&5VqVGmQto%f&0`^aY{_?-PfHh8uJS z!hV#H5CRhF!pS;=Zn#}1o@&dPGUa_B?qbQ+*({;FefznhqC&CU^X2&|G%Tz`oXA^w z-x&>cJR-9?eJ#@1&@dnZi?MTlzH!W%8%XK;L{oWPzw^8yCCz#}QK2uwW;H9n&je)3 z18hFjVE+J^zmSbl7P-`Sy{&4+0qAi}irw&E_ECxC&#;#C*l;TrM$z9D1g(FFvSDe}Cx^+4?N-lIS9pRAtN3*vkL*AP#{X zg-Swb`xa@NFj8nJmK|GJd>@DE&2NU3&aH2jT+Z7R=S|+6TF;OzE6L$XHBbh}^TX-DBBbP0u^>xvJ}bPOK0~cbg7PYfSUldx zcg?J-xT54J?qpcOW)T&C@T`||f7szkP45=gqtxx}Z|p$hrm< zp)WscROo^I3!{!kkpW;+n$4&W<#KOAe4lAR;7LWRmC(lK9L(9#Gzem<^X z1>UX+evl$KGoJjg3F_nL!mzz<=o}b=D+5a%FPDSyIx1aToq=RUGU21!{AB%{k>&Qd zoK0axkgT_PjF89xk2NhLIy&uRS2ZnMS`#Qz6TI8>STvpvE@-Js#HKmmn>a;_vK);m zUtC@B^Ttp2!JO-><>vWA`6iW>LQ5JEI27NRIJLZ? z_Tm0^e|u%{@q@z;@}fytzx4E#mh^e{Rjt;Q0b(?#-7zqweA#%O$e%IpBWy#Ca{6hW zpP6SwVlT9iq94bwvTy3wC)&1G`8^AHc?zFR82~i|&Ss&R0Ut$Bkgs!00II%Q(7JpHhM>P4^5q6Y}dN4*H zSgs!9JV=olXFwn9gcDe>8B*u(@Bp(EPd)MYL$6RbX^D|Gg zWqthT8c#Icmq*I!vL`ghHbn6Uo@lWZFFs4%lm4EOSX;w(F}%x1b9xu?lV6Q1t-Yq1 zh--OFCfvl1aNUn&Ku64ac+dv@KvKF`iKmEjk0Pf zB#>my^sFeTYPa>gmT7TC-dEOZMVc(d!x|;Yy=`C~n<0fgs(Z73k&=d+oPt9UzKPJ1 zuq3(4H?bhs1?y$ut9N<*;0vY3!H{Ti5?0{zvx|AM=n4(Z+T9+9{Y|ISxUeP3{1g5Q zaqwb&e^%wZYBNzhwRCJ;Ea_L<6-*$YW7fq@h~~APTBkKIz93-?=g;| zm$Ip5EeuG<;#{>>)a)^+n!?4l=2S5`9@BG};E1lL=;?2>s~`4rv<3pjz=W!zmuV0yxV<;LmxDs&yrxRdwf=^XsW)Z=NyvfJQfVNI`s5LsUgU`CC zL*~&~EE@m3K=doc)2hg=G%v)ib{sB9i&ASpBs_FjEr>dw6+>HHJux&g@>|YveLYvD z`-O?bK7(B1&g+X%RL9Ew_){giTAHW&IMn!{1lbR1?k;>v|KC2rtlw*DYCg)!a#(0b z1N0!vcsK@ah0l`*5MwS7>TFjrC&$#;#S=Iv&n67uoeq^TG-}}PtI>1di7Bsj@mu2p zR~mUl@~4D>0BgA&kU2j0vDE?6e3W^k6hP)GMx3!u8_Rt^77d|Mv&;S4F^`c2nQP6$ zRS~D^J#Lv8hy=sYg7?klXA46emyGngUWz)Wr>~b|L^L1m%FIqCsQ6k|sik^`HIWC$ zb(g}a`2$vUyJ2fxMoukQVM82##wOjlX|puoqd>uc92)SMu=V_?t;+&E7yPk#&mku3 zm`jZ>v91k$v!Li#jzK%5Y!+(|7XDw#DYBL3k>s2*|?ovj%d4cqGhytaYU4d+EEE9SVqZOj^dh&x- zOKNa({!7p4KJ=+fFGHfo*A=MStl2hS!R_H>N@r|i!V7%hwdlP?JBo^taL$)N3}jmR zq~th-aO{%~^cvC2fxFANlO;v(n>7$~@=3Ho(@+qBop?{*U!q&t3DZGpCdc3l(42)g z&m@uVr}8wJetsQ#n@Zu*P&f!i{r2*NZ=+Dgn1vf1?rs8EPOQ!ywQuW-5L8k#l^jmb zQSpDzWLU!MK7TpX(ZtweqpC+}urzhDzzHPOwKlMxG6M2ts}S1yY7c#hX6i4)3I6b5=s1SHu8 zRZz?m;@#_KvAlfhvzf1Id;sm(+vmh%_5sc&KhcLZr$ih1Ih@OC!DnmjUB{khg|jY) z7C5;4T${i?)yC;)-UY=-`uRJY!{+2nV?APU2nf{Kb#i5fV~IsaLsV~KJVcL}F2Cqu zlvQ1kq-03?w+A}M$xPo}rQ!y{gvmDT`#EO#_zX=J_yX5I(xS0EblW$=JgEM?7 zQn|z|?2{J6Wb9985-BpXY6bWxWA4PQFBc$L+h?L7wO7XF7`qK0WTdg{^xne(F709Z z_>*xT{&>U9LdBc_7QAa&_YVl9-BVRGGwITGoIeY^rH`w(E3)2=Ki%xZT~}^KlW>6F zmnBc1^^K2vXBQU%RaI60*1L@*Zs+*&r&sa@?$PfQawRd8%XPH(nZg5K^Rt3CFh)7X z?HND2;)tYaRA@pY*>dne4VF~FL2}}KoFrzxhA;v)8;HC)tG~|>Zm*A{#kSoSCK3GF zOelOJ62qaq_q#}s@EJ5~MMg6^$zq#YA{afqVy?+n#02l`oUWyN(?02LBuru^o=Z5I zX&)GTj5!(eQ5JU^e1;=pa|k|^MzHzbod%K13I2#4dhg7)J-zRLKccbuj!@()m>ug+ z7{dkpxdrCdUgDu}HNChpSQq3%uAU@@tJ)J#u368>sBMtO?WSRh9t)7)q5c^791UyX&bTY zvyi&%Qz1Ls&Fa{D~cy<$x1pXKq2Qz#zR%0 zraFS94?)DE063D@KrO+@Fk(hsef)yv?ntr29D4g=Nng$2{VU)u#IZcwdd2tMXRnl? zAPs6|%;+q!3AtbCurad8$<(rR`}8`6CbZR(@l3AQSgEFj7~FR<0-lboZra%xw)Dep z_3qQ$l@6lf3ccN01vM_6#ofK@z20?%vZ^W0LL~%fG`K32sJKDGW+f>fqJnM1aG+(< zKi)LpG%zOF4|b15Ux{LP!e^?Q2L`1^oEG7|Yn?m0H5x|75&gaB(pj=^YB2L;s4ty- zP#$W0qJ}k%6!lAucOuO&mtugz2+;E(m;@~A-R@Uz^`7`>~8u1)WP(On3%We z&?;ygxWH*%*dZulveQ~0afGpdO$DU}mTFNyiVG|u^-!;B)VJM!+VNQC&bzXp`k`Ed ziW}cx2kWNfRdsycb%YpZaHK{cD#8e@U#18bRg-*bxgaIIkVZwga)EEZYbludCUCJZ zVOD-tdco+YPK!sF;Pb7>*G4ijvJXHwf9IS)G~`HOm}Z#9I?w8$t#FZoLh5-#yq4?6 zbjougo;sR<#xxV3Q>c_54(Ntb1U*X9cZ%RiC|*g4p#ycWXcWZc7t?(zxTpDJ5flB`a}nC@I$2n*0u?U@Y&#hE-UORjGXkH;OqMDDYf->veqzj z?TZNLDKlBr;9Ar)o??WB$}?Z!w`++9ZBPq|8f9tL*^qE^e*(ykf|S1J6X<5VSs41R zybX)fQFZoOZbH19xCbJ!+aiEQfV&;fR5JRZytX4{Pm4tPt$ZycHJLfDFn4lIo>>ZD zt`rr&PU@p5a4VqxeT7%i5KShH4`+IM8k@r^aWWG)nqkgQV81s3uOsvcFa7z7sL=$I z4L$^)LnVA|iuZ$z7iww{W4O*+w&Aw+&h2h_lz53^wep@7-9#qTPM~7cgCKZ)jv<@-Oxzw4fSLWz%?J zeQB13euaQFlVs-y1_w(LfTJqhbvyn%v}BoL1MBl`}J**VJSx-B$jZ!A`gN#RKR zqsUUXg*F~mi@Pgm^vdjCh`bvT!#jF%*G0EGdO-duL;uT;fOp$FM%SfB@TNh9&K?C= zHhAwu_uJCl6}}~1Q!ys$%CtX9=7zq?;oroGk2`sGvtKT2G!}PvC)p)?U6JAeP#{(`y3MhqzR)=09G5@DFqSl!`A; z&H1mb?0CF4=usG_*KS0=*dC7Ki6mW{dmZ|U3Z?>O8Xz?Vb5P#9!HVW+f*YI{kVdgW zrV0Ixsp4e~)`Ba`gR_zG78MTbb_JurV=@wlhTOmPCNVX2gFNqjYB<$bZyN;&J*v`-|L8AX9`hO5@4*oX|A=1D)W-&nN zEI6IEWT<2ncMRw0KCpde85hY%n`vXUs}UgqGqb6U_i$)Z-n?|}Sy9{z$N8+|nd||4 zI?@9Vk*_3E#Zqrg9s{hjQGYs{H(Tyk?TH6*-zdQ}k0aMzb2wORuYGS0KaJ=$q-c~E z{M90TCcESN(rNG03EzHXaYS^N!$>Y0jJ-!DvCLk9(Nt~2@!IeFP8w46Et8PhoLU_L z@=_bKcpdc|!<|9SXya8iM2jUg@|N^#@}(sA3~g`_^0(Yj@84PXMm}qg)3n3e#M5Oc znCt25qP3{yFKcE-R`*72jig9X6~JnVoCQGh5(d!x?}p4N&h1(`iqT?KRNB`|HJ5o5f<_Tj$FIkeSFaQ?q1Ov3jlR2F-J z{jJB1w=vkBEnuH@m4*5VwR#aT_8*ygSYw{ZE*o6T1k8|B%YH=26%%y_ za`KX`+i^&4&Ej<@a4uO+P0DAeA1|tErhsss(5W z2YffX3dj5U#B2C_Xz6yBmzE5iIUr7rem1SjUokHzTaDgL&G%B4p5Xsby}#=!>|f zkE;{%p1f#|WG@!eo5{ovq)pI2f21|zVt{hI_Y^{Uf8qi{I(J4cLiwL<0GZ6jZRh%- zy5arsZ^x|81rz6b(Huaf$=vHdUJh<;wizzd#^3G>OHm)z^DC2#ARF>+%-)>2xlPhR zJm@Q*pqPQ0=Q}ZG_N#ji(_IYu`Yysp{g+fb&;--8on-)}uOZ55@9)JCC`$vL7lRD*vFOe@?6N=9SSm z%4%{wA!TJ%0*Kf*LHSsG#{Ko~v@fGX#vJ}`rD3?RAxI>d>f3cS@B7r^#;K2>s|&Ux zR%6<_*IQuw23)nt7Z;Fz&BhXCw951k`;x>Bg6aE#h!Y0E0>ExuReYUk^DYrS`(jYe z&U1Ga55L17=od4|BW$RAS^H??Ty3%SNsnbD>z7P^A3|iC$yuKl&R>6twaYsdR~KUgF|EO2uQaw<@?Si z>9YlpSVegCj1-|Glb`7|RI1)j=aTQPxQi`2@L$Rc=X&)F=l#1UL2x5Jsj0noa=V}X zdKSZL5l*AcE1d|E>Tp&tzz<}^(l;=dBqm?HEG-9Hh*?KPDi;ujZK(V0-=4Oxv}^^8bMvKNurZLKE)|;k?6g z5TTmb%fO(nPgQIWr-6RsL>PMV!sj=U`~rQUqO14VNSXh2YGXif;_NChh)Z5Q2og%s z&2zW$IePMT&g@~+6R)8%RNzY#R7BLtnd@g_ytR@?eILM97?{N~7{FjvJ8m;- znqt%2VREdWP_um-Bevrm?KTq>61{7X!~)yEqon<7kMM-9ySXk?jlu+E4r4vnscf+Q ze#m{x`fn@*))mg3C{}fdVtU%hvsAzBZ3dYa0}&5t`D1_otTXhA5lX};Z>P{kJ0v{E zzi>kzBh2ivD!vr+O9xkIi|$?2;Fn+d6p`}F@FNSz282E(f=b8-yVlicP@zvX-;lF* z`$G^l-*=YKVMZPSK0W%2t?<}$4~o}f51z}%j@%8L)1xJVs3D@-hyRQ&75e++)VFL- z)*}d4vY-0u#^nU?Q~4(cVpZwscN4xS3 zz15BoJAb29K8duX$@efTNOQ2LQ?g?chCj zJnHEK+MCr z9wk4Z5)ed{l+aaM%_;87r==4EK1u-S{;JUL0>Ca{xs)o=tSg(`r?tMnr8;w$XV=eG zfMpfe9oO$cjU@|2X~_TeOUy!l#xSaHNOQ*SKfr8-*1?oKTcHIR8;#@I+ta*Tlcg=s z1$YwCYxhcr-Gi*!xCVLZZ^EWWq!t_=O47WE7ozA%>%X#=*W)c}g^QJ89Zh}5-M1BD z4eS3~sM!_A>+pfjv#qhh#q3?{e3EL(Q7qv<1hhb7p3u8tYs#S_9|{-aoudzn4$Ret z{0iO6!UZb0`uaPh&HGScwl0ecV|IphiSsR$r`4+P@j(%%mWP!1=<%YXZB>Mr$(|}! z56@(%n;Ud=laiS~?aewJj|nQeMsr=(q|1W~A5ThIxfwZT8>OPriaVh@YYhcy zSW-4?c+vSRx=I1!w-`Yacyuxf#`R+#cb)X5;mk=m&CvwW zoxPCbZv9`}-!E2R){4*G2Dq5+5JkWqDWC=h1%(0dNn(Gw&Ne-f*R|}OvzpORc~Bkn z1?|Ow_ZC^3qQbXp)|ASU($WtO4nGwR1_GI$WvocEewzJDZV#NyY@WT$XD&<qmPYcF3UvR5)y>KbBVwer>0S#wXMe1?P;$Di zp-XZrTwK2s%A2pT#6lwE3w?e9)JnM6y+qxxf^V-%e4i*YGqV^Xn9mxO#d^dpofnBS z{#H;kT;uQq*3vdc3zf_TV?~Ag_#m|cpWr(sh7n{MB?j%rK){C~K>)SX+m@cE??axY z*!dTV&0;YbN#T|+RQ}sm@T&Wt3*-v^?JOR^c64+s6afs_G|U>Ms{1J6krOm;U!4i@ z%>SW(1H7Z?I|>R?0Rf#?BJveqQv9r{3W$n|0+2bX0_63Sny&uS{ngh=Ykk13BrYzo zPXU-Fx;{g~GdXTc17PqiKxzR=4T(tbM*tL~DunHgWkT>c?GnFYYEffc@q&0B|76Si z&V^|Lf}OM=oqan1g#Iag?+)c@ox#nqWY-)9*i(2M(Y79V=|gp-A8t;10npKs3(d7fYc(Qhou{9APD9gdDI~=2;wpQ%@dq03P0IXt7 zPY*X6Cv7ic^73E)yc7QkKT|dQZ;OM{Y?cSV;2VTn=sCHPI8Qs8yv8u?u_13!D7rUr8YNUsbY{g_zMx~R#Zj;>EVCJMJ8B903D55t( zl=q^(WnQqPX-JXGN^dp4TqF})-qNd)G?K~d*3QKY7?B_hT-@*&f%}ht4RP+rZS?Rv zaR6;qUfnj^JR442!iH4t7+6Qn*2Bi>hIcHsp~L95f_D`k|4Qg0L$w8MUL%fqPmZcL zSTkPR=mZL}#~4bgGY&Nwfkfe+anK_fCPqkCFYeT<%=y22PoVELO_SW8KjAF=u5@(M zY-Zkii;IvHbCs-Sp!h&X_fP>5Z0nh`LHD1uT8WF6e%g?={Du=U%W3G_mGd{)KVuAg zdUe4QdI;`u{;KV4#0D79&zg&ws+9Y2kp35O~3| zzHbeYa}K=Zi4mG$zCgcT!>tK$8NC*T?slXa7HNtEh)xjLdxiuECIyAISovWK?o`YWD^jI$J6M;OT&^5Xy&uXDxVgQaKWz z`!&_TOMfCK2|#$SuX{%QSE@g&&f$%367166XNKj)^~7kk)Cwlm2LECBA-nR@V>dHU z@-7n9?r&j_s2x~dh#v8y_@l`LTOb!qtt^UCE*2`tWnu9Sqz|90AxAb@Q`Pkk(8Y1` z;)E#Tz_R=Ta^&A5f!X^u08PZ`>5C;}wp)E)*hNg51xH6l1~E4$H&Uy~19-7Mb31N- zROCcOZNX+?WwpOK)_z?kT?!t`Gec(yFlcF@Q&b2vz@QO8?Z65zqa(uiZXl&biOBldJ ztK%=zNHNR*E6{yX6t3214ozvI(Xd9Q||3?BQ3g&Uu4=k&Z&R~3*0@}*V5th3c{87MC!uaJ- z3j!&9h{h>Wtx8XIVe8?EaFMu%p zK3gmb1s(kzu$PQ6=dy`gy z$4Z^)tIb!z8X_+!(Qpib&hP8%YX>~5KrN$Df08&jc=!Ox+^;+cS&ELEi}aB(k5~z4 zwq0}Mn9r?gOcNMfuVqIS$GgBT)iPiO(F&S%xP(9UniHE-5uTB z-6a$`V-_Z8FC-)cNJ!%wFNd@c9^16J?2mDa)Exa= zQdKe+sN&(cc<0;a#-TWE@(fO6cu!sQWG)YsvG7a8lq=6R3;;+vFe7Ivx4IJ#tVhl0RrvHL4@Zl_VTGE=lZgX*`fUx_H7@3}H~KRBqJ00z&yU6` zE>$5>r5I3;6&Rnm?+wDggh~#aZw;an5;6b_Zmz|zNAHi#vB5m8DjACWFA-Wa@oxo| z(`J3xzy@nmXnJ763QAsg0LF$LFSkSt4FTykF%5Hr2L#2-HKUgHp`N9o0-bRqm)d7S z7P@)2X7vHGqTM3ZQaC^zl-M5`84(i`3x4N}CZRu5X*uh7OX_8*!r{*5;)4#y-CWdQ zFkM9UvANqaj7kvrUpq&~SE=XEj>B>I)qyLgl6hxLYMf6&Wla_*9~Na>AY9cR45B*5 z=fEO3)7n!@77geV3P^EzUFZPJ0~l>Y83R8RM|VzjBWaX2&JIjkOq~`T`avEwI;BZYmaxiZs=~osezH|9-gF2Y4L)vVTccJv2wJODv1(^Nzn=j z(`1=JjfeiTIHna^mar-u%V=2(uUpQ1KBxO{EE+E zyH#T~#)aJGDY^yr6$Aqd`QMBUUiq*hi{%z-$)Y+*uLK>8Sf5>W>W;UiaRbuX zTELgJ+Xt(+zY%!2?%u2iFteV@c*+%kh+pM1ywA4VwxU)B6yK)H3XW&cTk@a6Tb(a# zQUR>RVU680^a2e%vK(A1=@{t4j)A)5rLS5|LHbpzD;RMcaOb~H8w`&YFqThevi7G` zHHy(gRvWtWMrC9)g0q>blHVL~f?X^_8R#uP62K`0v639<=H%oMuUAb2xu>!Tfq)}H z3x&1UsjsCYBDk)D_ySa#+uXuk<=yXZaNG_K4w*DzGrH)0Ej7mtwej3R2G6nYjdmFg zbkw0mx2vKB)|jrfN}^Eldb7@(ZFcVx7%Av=!Ma>e)(6*O-7K}a77y_<9tzUO2fI*B zxX)&p4Vi*Nu7tM?561HyaR1<|2)fhdx(nvaGWwqeI|l9TfI3w&>e=_PcVx}_tcMi( zGMR~J!vd7A7Tq<5%nPy+XW}1g{%8$^w%p#iX@rIba!#pFCN2zC^z35gqgvw1K7SDs zQs3$Plag?FocCYj4@D)LQns-l?`PfCnXo+NO+mQfrGDH(b5|)N!kQ*m7KOpC<2w%3 z|4@@~vSGh56g)YQz0ocUIK7H4SgOX@{{^{v5SDK*fn1+-M_eBvw>)sAFnYlKGi;)#I9xc;uvO8TBx;$CY-IouQ)S8%=O}v}wcuTEWtVm2DFjw8&3*4Q} zzvvc#6Zj6$261klvj$z?>M|5DpJ_~!?+MijyfbHcC#z{7WmB4?%jog2EE^zW`?vg< z-h*y*^x3iXKpAa~7)s(^5xRARUGd5|R`~26NBV=c|0Shd+H8GV<>cJ@^&HXV`qi$g znAOL3^HcAaWWxH)MqvnkIWoeqd(yfbmad;?LR?`cOo%aXWjtbo5nFE!9 z1WO+X{kq1KB?%uc2tQ`&=_dV*2r{|yfGHn2 zHUbKq?WN{3@BOqqpH6-Qyw#nbP`BP+6cS&;@2G#g06h_`4#NXF$->8$1ihD z7xe}EPca)ON&2&>qVLhWA(b5@9EFq|g=hzJtQwM*RBcU?@|;UA=S?Co<4sg+)m7EX z+miG2F|94k$(`sWS=pt?*&T)Czc`!t_*R=2G9h%i`psON?Vi=Ue!uV@oeA+7xl6g= z7rz^rQ!cpX)X7b|6p}}*wOuI#K=lVP)w8q!5Y4bjjoEcQSmXT~0wmPz6cg!W_EAj~ z(pc6FhuQNc$Vk#N1sm7LOSB|A-Yz+gEfO?_8)k3NcO0Pl6-m6%TD|8B@43>k?*5usmKzC z@3Yr%5^Jv$LX5grjrX_({aN2e4 z2_d`gtouwx(v~+Xii99d0fd5#IM{wX(G9_Pj;Ww(VOQ>MPZT=tTje#^M=Mo!*edgl zX?_|8`iaYN(?WOYD?#doXMRxn){qvA)u{|EN=X zIrP)YyJgLjoXs|;VodMTp^rl&-T6gT|B8u1#S*ESd(t%#UrFTQ)BZ|s66TVpe-;^y z0QMO{(7^o-{5<-!0_?aD&BLA4(@GbkxodK=vjU|{GAN_Wk`OHq(lxz2*RpnRD$>@d zYHpH`L-@KK^vTM{VIT|Cq~hHQ`f>EKwGIunKqO)4L0T9 zMh{xe1N5W2hlgTdq0Gw$vFB-fiSTp#WVjqLjukOFTy{@;-yesF4Dc#uePs#x446~# zaI!dtN!eB-O&V|Jbu6$|;cYn%7Rygm>BpXIZGM+Ev)pl7l}$iLPh{b|C_4D<*}Wg0 zt+h@D0`<4AUk!NR^a6)1#&e$*cGrEvG}^XIOP@UITIcNLd2a}(*E$uswHZDHL(;U` zC(hSda?$wPoR0dth{+S(f1q&y>g1k#ksPVEfvo$&5x zn0^v5rnP*Fm4PLt!GL>S0B9T7n`jR3)WA6>pXf2KK8CBr$n^f(`hj7!Zhv)#K8PUZ;812>}FZHkdB?^n=6t z#~wDJem^CselN}QI5xMIuNxL5Pm6(xSyWv;**BY@<59@ad@4)^D7^b2>+1#>7#K;> zYp6h*R`W74h$m2iYvcO*`T~QN91RBYlQVp+jGB{E&E4HS&kzJ!#ejr_oCkEHJ!}x@ zmhI1p)zxgnsk}3P+Cj?CuMN0;*a#p%p!A-vazI1ddlB$Bq$MOI{yM+K<4V!qz7l9? zX6EaM-P|Ukl8Gfo1AQ`sfX&qI^d_cq6Ycc|K_EK_M0Vgud#`l5fHtjK*`Hd=)vL{7 z0r$TtPq4W9IV{Kqm)jxd(4`4zkROi~u$9(gJS!-+63Etz0|Vdub*DgYQV9yK0Q3IO z06Xtb_B*`+dW{0mM$Y~}&fYpMs;+w<1`)YM1%nVI6hx&_7*ar_l>g6>HX$i*G|`SpP$`e|jO?{7LHJ|5UkUG5t?5~i_&t%i+fZpZ%$tbMmDV$~p()z2ihdzc zUC`neY+q1_Yl=Wxmp3pV^UpM>S{k-GG#O&0q&%8Txmnb$7aAo^S=biZ{5cHMq1^|c z&xHG$x3;9GWzeX_=~~s8mPUgBRt=l-WbgUy%xlm}v!eP@Fq}5HBp4V~nNEILxOivl zBqve@>84qj7>ERc8R@fc!2&bR&Nwp3gJ9-kSngC#*^jQ-c)576;1InjS8Z^w1_`_d z+&3`ZgDXe(Lb;F-FaEqf#MzkIUn#bu1?t&=eCYURU}>H|zgp}17$SENuI_t1&|;JA z_kHtN)^lmX0TGFb76uzXe+p%0WIQB!v@Luu6%!Fk;5Kbm&(lWx`?|6j7A#>TPnn*Y zx&>U(8XtmTJ-q#^MN$+aaD65x=OfmceOZP^n7!-Uh%{;y6NAZ@R4Z8l#xW6px9Vb?i=%Vp0&y^x_L3*O`{jHf^14-@~ORfZlT4a_~2`B zu73W@x(&xdZ%p@2Z>HXZpO*#3)z4+_02B@=eGI{@2905O)A3}BiYF#LG~~V^v-0WF zC$K{1rK#Rxu^|^&LDv<0nws7W-+L_rnwT#=J%koXziLzzOrlt8VF-gBO|+iQ%goAx z%}x~PS0Lork!2nzM_)fb`d6=p>K095H&0DhFxL;6A5W z91se3$hj4$MT7HqiUkD)2_HO=UtAn@^4rDBaR_*EzE#g7A&LpDukfH41R%XB?Bx5& z-U>ZGzxHspGzWQR7kZrIpGn3UaKEkBtwiPMI1A7-6V4b#e~XJlp3K_Dac zfv&Vq$CKl56ny4&D59=!Ez&0cS-jqluC7s1Qi7gtVB%tE6$6N}K%;sHZJ^vqfl09* z9?BGt3=IXV7yKL=8JX#_w;X$A?-nWkf0<{rnY=pYZ~SfFNqAk$h?up^ubOxX67MDB z*?YmP{*E>tY^Fm`RSObAFc7ztFYtQf_c9rXwP3R3C$%AQvMMUDp&>bH{p_qP==OZG zNZSn^A_zVtv#Lr(SXekq-}zJH6JV|ZgD)&36cdb@@|n}Fajedmf4H5W3%D6jp)1!& z4{R+u7!Z8e>q7LVwzRz51uY*^$Scqg-A~?Oh5Sm?AZM`dxPLdp=>6BzsTkcFH|Gbw zaed0t)p#D6#nH^e@jQF>DIL-;3l<>93m9FZgmLzb zmhP3u(fFKrW(TE3w3%}XzU^~aZ63*^1>%PY*wgA@lan$oMBHmt-QITklmh|>vYi^O zNg4O)W^rHr2y*zYsFuPQQziZPR!yi=ClznB=6*I-itXm{wuh!ur=Y)h!sE;3O8Oen zI7_u8W@BM4uWlPo(f--PjIhbMXO~qsq2KH)xi35-8|aFrQH^%XYmJLr$}NW>>q+^! z)2Pl|k%GmM(iKxun_^cKwEiMw0b zv2?1BKUC~in&f5}kS2O(Y~w-}N=8LUPA8|bg7Kb4ZEt`~bK4bWnRM#J3s;*w?W?WX z8?BZLr*$nc4sAH!;PD`KsvvqjD3vODv{qm|I}{Xskssz{IVxFDKp66iJJ%_@Z!oj_ zBLv!2E*8K%IO(!hbFk|{=Yb*$68|n{OKZGCk8oSB^yjonL3-Bt-hVWqaJ=_qV#=>m zO}20LB8*IJZ}s&|4Yw5jYsXT#g$=si?vZ7zU;d>z_qQlpily`Txl`(Vs$H4srH`Jq zul7^gh+0R_)@1GZ#YGiMViQ*?JNh+8kv zy>`@dyLxEntFK%R?;8I&(cbjsW}Lqr5A+@gH|tAt_b5JN;pKdWrW?VDl3L$Nbyk*E_aSA)(Y*D_=3_hDY{;)zq0LD%3S zSZ<%`t(tDR?q^c6A)19wlvzI8^}A*=b}1*jwVU=qLQBL*BgnFnQT*xc+NW9^d~lJA z%sJZ4UquRzvm>YTzDWOavdbyDo(U8iHcSI`NZd8_Lh0P~IZX7Eu-vVK6AyaAdav7V zX<0oDDxsIRMz{0Qc)7oSd#Xwe&&q7{clr(8l8!uPB{o>6+F=|8n#nr%ZJzB z?PA|ja2&ixe%5YqeiTEllHKyeOeHC}iu8PmCu+NbyQo(37W>8!g>jmi5{t}96lHny zy@Fz6k+V?RjK~{~<)*W3Gn0$v>C0R^k>&l;=YEM7(d27gOA*%?FiE*>s+ejlzn;=~ zWquH>T}RNnt(PEwH6ouX?_U(>Lr1+*DE5Dhpq4izE;R1k`Mnby0T`p&P6(# zFO=M{8>L0}4z54;{LGf0^ipWX)6I=BsZ54;$&KwIZB)$ML~*CtXL1VWQ@AcK(5LgB zZqTnZ8j=2Gm@P$6P6Rc@g%d$Y&g zhrHY>6a1)oeXkI4KP6_xt{4Y8O3xrA{%L_A&UpNks8sOWVJ=Ny(ME|QGc%Kxfk8sQ zEfByISt=hRZ0DQ}+0+Y7iwsv!iEk+^P^Lv~)n8w1Uzx@EvLc``Bbi3e)$4=ph74#CS$8rAK{Zn z+MSH4@fY~?>6UXsKkV<@7pRqsu^RWWkz+S_Uzv2w=LR;_xpuldQp=&(s$wRiGSP=t z`rNoOD?KqU1y4>+fQ@hs3gmFvH2_ZWdSNXxH*^P4TH0q`v?l)>k`-bl=Vkp1_K*1J)pb*Vuiw>=HdePv4vApb<-~|dOh367IL^@1uKPDAo#wQEP z(Ny2%%mn^$UP{WF%4yqxwHNI`>@@&H41^RETEC2|--2+W=~l4ixtMgHW{z?t$Kd8A z(r~}&T6zDUXb?kz6oQYeIh-_;6y1J!6+AP4C!Z{`I-gw6SvrT*S){CSVqiGmf#4t9 zL;su`&nV(lx0iurNyHMt-Y3gfc9EgVH34;+#NNG=0Wmc2C#8boaAf!98ywb0b22i7 zfcOsWsaM@a2f+ym`9Q`9oKOr{3lXDQ#>R#@wf^&G&**4qT06!uyiavOJt#C@N8UoM z0m6MtQa<;pwZFhEfOx4SEIj;AL~Y|s3W86&wRu!P1l%!~NyGXL-mndnYizchboz`) zm2{3ue9;E#+D;UibR6EpgYmGrv2o;U4G#Pc07IH2ZxJ)WOANau+M8}x?Uo#bux`nj zl-iWAS1an0BbAdMDVt0jmOv@j-}UjGJ0E|qFlUa)o2=C0)xhB!`L?`Bz;Q@Ve#~nS zs#T9*FRwwlDPHC)1kvQ?X`Na7v*(khPfaGfj%Bd-@W@OR{2L)gzM0{naKL{fsfWf$ zOaUK-i;oW+vFCE03bl@ec`M|VYde@kOPig8UPeq~OVq)L#K>q`nv9woC9ow2j@9+w z?!Gjwr_L`O*0K%P;%p8~UGuXW*I1q}6X!=+_|W(zlj5jJ?KDraZN1ry3=dBSEb;J5 z84`H?gA^ntjN~lwItKhD5ybpnHd8iAJiAgMdHi)j^<(*f#wrzB;|b4`FN@!m_%E}v z&W!^`w>Wc~j;{yBIDgI3zMrS_zOaQW&TUSPH?~-w|2GfeL!zu>B&`~YSBez>cO~N^ zl?;4wFOs}YNTo@8`lkIEQ>}b5|Nb}E{tMNm_6BpS)U`azF-?nnLL(S^#b-n^L;dl9 z+RS%0;Z1Wd*T#*MDE?xFql*=F3vL7D0Hmh<)C@7yKKk)_MZgyKc4AW8NpY0tVR{9U zFCPmVZ=!|DVJX}RvGyz(k*3ZEZ|4ywh#OEMcCa%!*xf4UfA-#~zglfk`qI!Sp|;e% zx5K_*+(t+=!4YCPB{DYc5{u+{E_Wz)c6)5rf4Tm?9n;8k9TtV_#mS0crSh^B4tP>{ z4k0yuYPvwbY+DUIa)?HQXby3Zr*E3+lx;e#iXu#U1?}vc*Hdd;5OW7IW~S>rTZlgO z6*0-I)y`S=0vG!vOHJ}@9Ew_1S{uj6w*HxQW$q97csiz&UUr zwJT-6D9~VfUnn?-2wtBXs0rqEkQFzhd8M#I;!x{4ZT}(e+^!hu#iM%bGr66?|BLGk z3Ik97-`sXC_Knf(T0O{8V+-8)3SvAx_mF*{c5HYY06er8Z(2s!qb&^ zGXo3?&4wqZ$E_S}E)mpOvD!Fk~~ zKrOl~YUeiGR`KQ7IWBtNet&<#J^!2M=Z7HD98K!? z!5a0aAxHb3@1z-%?4rUpB0H_)uI1W1V+i7gB32c}k`9U+C|+$JjV1CIC{L!jA5=Rf zENeWV&C3wz)IF`fWOY5u+;UhHw7B4WV0lxi@>ZC``;bNHgJ|<_zljU+jLQ_dk~o93 zNV4#FCs+5M^x7ZEqaqf+E#!xN)N|>y#KzoWkyo3wu!sm90|S7T z5)`VVg6ai7=qJ)KI?-lGI`N)NW|ETbboZG|EGKp`ai{rQ#^hg8lzJiTISuY~58qSv z{wzgS7*MF5=Gm!jIzND{IqjOQ$#-)mbmS%nc3>{&6d zDca)loXw5JH(re_=QmaUKyYs)cJ2j|e*H^7M4y-Gt z8p6%=0cbl8qN0zDm`DEEMo=5g{B8z)=A3OQk3SW-6zS_R0*?eWRf%HOU}H` zeBi@a(qUA(%Dh94f~>Ty*!O>s0#eaiY~O{w#T-2fcg8#vT4n8rT=>@##Eq88VZNYR zVq{z#l=}u

s`DI4^=?-q3P?4(x4r7;poEWaZ+WHTSj+YZkWYg5qn{GaDP$qX~-o zJaHncRiQIk#bLoGMViBp&l|qCP~( zhm7neC~;p=G5evtqwKgN`JQTp;XNe0FhqGB+0I(dcWEG+Oz4sIY6=4bG5b{(ta)!I z^k!T|OHVHjvbGUJnZON!dDEo%ATN!15IBh z>WFycE2G3NKkf7pd?%T7x+K{Ow%3{un7qUTlDX52!ty0+)`RcSLpBCoY9?HRKXcFz zW*60EM&sT;7(cj~TcJ)kMVcobrr3SC#akaUAU~tGK{s_2e#n)%h>X*>#kV)wIja2A+1zTIj)u* z7<94zL0mXFr-vPb`G1g7(?T<-VRy7e^(MZu5dShvQ0#yOCX%#ANl7_=#-4ff6jnty zq8?{Xb7+zDMwv~Tkju(BOh7O6Zk*-yjS*SN%eb)B-7czvj^ zr+3nM6_(K5IC^SIZr_*O|4BJclDBZJ$a9Zq9p`{E)3l zVQGe%cN{bdi^3ZfDw592q~tYQnv#u6UXyk|pIm;s3wCNTK~hSdU`?OrT3LfrQKqBg zD+(0%3Ys4*I4l+85Iv7S;w!{mo_?mdZ+wZWQ*KF^(qo&?f5CXhB4q4jWxas)(VtH~ z)&fHdNG>x8>6Hd#@*-hw?deF_BaE%$Dh;N>%zVrch7P&6rJrlcydDk9dUG4M1(HBq z1-pS*mj3l?4W}6g<)A?2S+8TCH*4+-HQ$9`U{-;Ng<9Zmo8<)D4UA`n-Ytn?)U zpRs7W6zv;(T&98KGW{I>~LEzr1ykFlNfjVTB;L<6*x8j*Q&Z7I z1@IaH6Q@t%zAfPJiEoKE4Edo(lU|nbd~yVBJ}@Tn29FcrrAJ# z)Sni?3qAlV`KPOkXkrk3H2p7OBlw1Xv`{l|qSW}mXz%}obbtZw={7@)Kv^i|3CRVFkH``KK=QJWoBU+K=ZJG&jby%o6j|9R##X5VZ#B@HiDd~m2BVs z3r$SP8yI8&MKf9z0M`XYLKT6?H}r2NP2a#A5!rQb~V$3KD2Cv$C!hkTiM* zVNCBXHc(IQE#hcQ`vuc*KOb_Kleo%qtkolVCrYQ>*TlVXosxNdzFzms`QMiZz{ zvl0@>0J$3zeC_ztQHdj>px=`dS?wFqSQZVNr!wJ`@)m`V10U{YV3xi1ylTp z-iv!OsB(}74eEXWGpnuD+m&SfO&^}2F1MxtH4P|P3|RU_-A4FQ5(b*U#i&$eXs6UT zJMo8LB>jgB^wvH=3;P_t7KqaN7+`^pmo=k0T97G%l%~^ptOAxz(!66yP_Fjp8 zh1y#{bMYVk&)d-r9l2u zg`PrRK3EVCZCG)>lyTcfjEJ|kw*K>UIWyB~@OZ2D%?#ZuB$e_MgVL$()oP7!@o>Mj z?zT(DCQ4=F;s%tv9Nn8?v&)QQ!yRnAm=(-^F7!wOA1*~L639Z139D@A&D?CezLfDx5IIVEaSOB?Q&zi?8>uB_|NXXl;Rr-KfE!qB>o2P ztmU-Nzva;~yQlp~lcOnvrhUGb`use91ghxsT?g{lEv10v!AY;j+WN)?Ljyn!)_-s2 zxACGBP|wUQh3)t3N~(P;78nd7g}Frwog^=$@?2ba`a|JwWWiTu`5Nnub49nZ>vBY` z5ZTDhs>T;GW5vd|FfgP{W|q%MFMo?ooNpo~s}E@7O}&hYRR|uCr?PTCH|cGwkK#aY_HqSbbZJd$*S`ZIY%*(2= z=o59l^x~-}CW2w1_q;3K{nx!Kh8o0{a*nk#K#`Gm_?u!RVrA?FckTx@wxZMFo()g- zauQoecfCLqftTz)0?x_ZqVA*PEn zKl)tDtxNo}eYc#yz?Z@uB0qSq84=cDf$#lb;%Z~uLt9?-T#pAr>7PMqvMV#^a#`2T zHYp&(p8CZq@EQmCf~hiP`o@l&&-U%MYaL_#hy|i2h`r+@+bz8tY? z*q

?RM>JBSF5F<}Eo`vHhmgE{P+rsor7g202WoymWnu`l1o(dRf%?{Ib-Pn|>%O z<<~7mnRVL~rY|8O%Kb^aU@L+4^m$;r8n!BnK_9HhvTkxbX~qkY#o5x4{Jxw+x6&qt&l%7g@Z^NNeSzu= zVw_X-3?pBbFYeaT$c5d?f@+=exbB^NMeHxWK40ARD{m-i=MOG$J?JAj)gBIwYj~`= zw)bo;khchtCB8MBof+wTDIM&2QpMD8EWC7G?c&tPq5~77eBeQmF@2rD7xhYZ*gGL- z7^%po)dNNKyt+Gx1pH-;;AX%;Tg$7d#r^p4gUzVt;jk=5k;?HeqE;c_?m4ac+MBw% z0+U_WR4?4|iqD)!DX*p;xvhQP?Yaz(>-ca=4YX{) z!x`#{Wd$&J9n!?Kdeo~{lzggP8C5||MZNEpSFxIFH>Lb*3eJTfb0@!=N5EzRC*K$t zQ%JlMnM}IKow|}uBAYwq$avn7?=-bFlHEg2dC9|77m|kj%{PtA_Cj5et$B*O8xYc3 z=a7WVcs3NJeB=yDmX|-PH@S~4ihirsiOsl~7|LQDbIJ}oJ1S`0suK^BM|n;clzh2P zBce`!aKX&HvO4m}YSdV8c|G#cWLG0@W6KnmOsQHq6l9RSqt+)TATR-9b+AI9lg9Cf zozU_ZPdUF&R+1ACJob~HtUj_PY>-S$?;EiwE-v;D2taQ}=&%GMINYh~-R7jyks?;A;(#bX47uiuA8+2uI%gFV4$Wrq+Hp z)jZ%djZt31b@+wD%+E{ckIwZ8!8EdrX%IG^xh(0!&)YyrZMvTrNnTvw)?ZBO2&?($ z?(@_oV?oxMaVSAJ0VA$Bi^=u%c`nQjUd_ERX;uXJI~;1V zA(P5JJCiEGSx?3A;@{*`TZptsR)M+B?Byc0CIrJ*PRv%tt)K>_i_8O+ZReWI1?!oK zb6$ls(h|d~5qXA}1|LM&N|g|(Mr&IBy?dIrzo@xw&-Lo`49P2+ zUvg$auhJF6TANNOJf!<-Ei6S*r&vHoP(vEJE}dgA=-_X=@VsJNKI z?(&F#V54xJmxi=26O^dOB+ki;EnF%zo#yt1^bQ z{5oDTg+?AXu&yX-dY#DI2CC*R9UaO4P99xJIQ0nl!R)HG-gu!lF)`725uGF2cNp!} z)uM*FXpAsFN+pB63?O2Zb9Af-Eq?IeK_pQ18knbKIaoa^NEe>1ww(mp_O0e?<+)w9 z{atB&idJ~=nEz-7|G30|oc|)kM8nrnt8PVSpLys^;Rtlli3^7fYSNCF&>7V7IBLO+ zj6e>g+D7{yT8^{@wc?r$DHV`T&?q-3ov(BEzP62w;&TEj74H3ovfDJ$)dC@jN5vN- zYbmpywhfLDenAVpkBXkw0esGfnDuM#M)!3K4D(h|iC9kw*V9F}26{k$9bq%b!BqUYBmh(g!O*2qCk`qOn7eXfG6((SK`wOYihUjtDNc43mj$%@}Mpm=XSk%oO2$Sy2y^%+~b>vpv0xv=!~&*1^)|WCgh}QkVeXT$AGTany1$ zyW*x~o<%KDK$+8)faXwRP=1s=zuLS3Sk7F>>;c_Nwy}7!l&?!3i{I=eMGRe70MQ^K zfrQlvhFDUy`i9wrh@rn;zK1V}t>_Z{E15K8SueXM6>Txz+x;d2V{KRF?PVY)Z;h{JmCPtI?b)3nA4GSMOMCg%0D8Zvgs+pT?xO-#G$@r`9biWz} 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%0D;b4tMy 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 tCkI^@nV|OX`RKoltN*O)zv*6gvNkf+k6QR4I literal 0 HcmV?d00001 diff --git a/mindspore_serving/CMakeLists.txt b/mindspore_serving/CMakeLists.txt new file mode 100644 index 0000000..350c1fd --- /dev/null +++ b/mindspore_serving/CMakeLists.txt @@ -0,0 +1,137 @@ +# This branch assumes that gRPC and all its dependencies are already installed +# on this system, so they can be located by find_package(). + +# Find Protobuf installation +# Looks for protobuf-config.cmake file installed by Protobuf's cmake installation. +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-rpath,$ORIGIN:$ORIGIN/lib") + +add_library(protobuf::libprotobuf ALIAS protobuf::protobuf) +add_executable(protobuf::libprotoc ALIAS protobuf::protoc) + +set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf) +if (CMAKE_CROSSCOMPILING) + find_program(_PROTOBUF_PROTOC protoc) +else () + set(_PROTOBUF_PROTOC $) +endif () + +# Find gRPC installation +# Looks for gRPCConfig.cmake file installed by gRPC's cmake installation. +if (EXISTS ${grpc_ROOT}/lib64) + set(gRPC_DIR "${grpc_ROOT}/lib64/cmake/grpc") +else () + set(gRPC_DIR "${grpc_ROOT}/lib/cmake/grpc") +endif () +message("serving using grpc_DIR : " ${gPRC_DIR}) + +find_package(gRPC CONFIG REQUIRED) +message(STATUS "Using gRPC ${gRPC_VERSION}") + +set(_GRPC_GRPCPP gRPC::grpc++) +set(_REFLECTION gRPC::grpc++_reflection) + +if (CMAKE_CROSSCOMPILING) + find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin) + find_program(_GRPC_PYTHON_PLUGIN_EXECUTABLE grpc_python_plugin) +else () + set(_GRPC_CPP_PLUGIN_EXECUTABLE $) + set(_GRPC_PYTHON_PLUGIN_EXECUTABLE $) +endif () + +# Proto file + +# Generated sources +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/proto/") +file(GLOB_RECURSE PROTO_FILE_LIST RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/proto/ *.proto) +foreach (proto_file ${PROTO_FILE_LIST}) + message(------proto file: ${proto_file}) + get_filename_component(proto_I_DIR "../" ABSOLUTE) + get_filename_component(proto_file_absolute "proto/${proto_file}" ABSOLUTE) + set(proto_file_relative "mindspore_serving/proto/${proto_file}") + string(REGEX REPLACE .proto "" proto_file_prefix ${proto_file}) + + set(protoc_output_prefix ${CMAKE_CURRENT_BINARY_DIR}/mindspore_serving/proto) + set(hw_proto_srcs "${protoc_output_prefix}/${proto_file_prefix}.pb.cc") + set(hw_proto_hdrs "${protoc_output_prefix}/${proto_file_prefix}.pb.h") + set(hw_grpc_srcs "${protoc_output_prefix}/${proto_file_prefix}.grpc.pb.cc") + set(hw_grpc_hdrs "${protoc_output_prefix}/${proto_file_prefix}.grpc.pb.h") + set(hw_py_pb2 "${protoc_output_prefix}/${proto_file_prefix}_pb2.py") + set(hw_py_pb2_grpc "${protoc_output_prefix}/${proto_file_prefix}_pb2_grpc.py") + add_custom_command( + OUTPUT "${hw_proto_srcs}" "${hw_proto_hdrs}" "${hw_grpc_srcs}" "${hw_grpc_hdrs}" "${hw_py_pb2}" "${hw_py_pb2_grpc}" + WORKING_DIRECTORY ${proto_I_DIR} + COMMAND ${_PROTOBUF_PROTOC} + ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" + --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" + -I "${proto_I_DIR}" + --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}" + "${proto_file_relative}" + COMMAND ${_PROTOBUF_PROTOC} + ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" + --python_out "${CMAKE_CURRENT_BINARY_DIR}" + -I "${proto_I_DIR}" + --plugin=protoc-gen-grpc="${_GRPC_PYTHON_PLUGIN_EXECUTABLE}" + "${proto_file_relative}" + DEPENDS "${proto_file_absolute}") + + list(APPEND PROTO_SRC_LIST ${hw_proto_srcs} ${hw_grpc_srcs}) +endforeach (proto_file) + +add_library(PROTO_SRC_LIB STATIC ${PROTO_SRC_LIST}) + +include_directories("${CMAKE_CURRENT_BINARY_DIR}/mindspore_serving") # for proto header file +include_directories("${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" "ccsrc") + +# serving_common for c++ server and python interface +file(GLOB_RECURSE MASTER_CORE_SRC_LIST RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "ccsrc/master/*.cc" "ccsrc/common/*.cc" "ccsrc/worker/*.cc" "ccsrc/inference/*.cc") + +file(GLOB_RECURSE RMV_SRC_LIST RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "ccsrc/master/version_control/*.cc") + +#list(REMOVE_ITEM MASTER_CORE_SRC_LIST ${RMV_SRC_LIST}) + +list(APPEND SERVING_SRC ${MASTER_CORE_SRC_LIST}) +add_library(serving_common SHARED ${SERVING_SRC}) + +include(CheckPIESupported) +check_pie_supported() +set_property(TARGET serving_common PROPERTY POSITION_INDEPENDENT_CODE TRUE) + +target_link_libraries(serving_common PRIVATE PROTO_SRC_LIB mindspore_serving::event mindspore_serving::event_pthreads) +target_link_libraries(serving_common PRIVATE ${_REFLECTION} ${_GRPC_GRPCPP} ${_PROTOBUF_LIBPROTOBUF} pthread) + +# python +add_compile_definitions(ENABLE_PYTHON) +file(GLOB_RECURSE PY_SRC_LIST RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "ccsrc/python/*.cc") + +find_package(Python3 3.7 COMPONENTS Interpreter Development) +if (Python3_FOUND) + set(PYTHON_INCLUDE_DIRS "${Python3_INCLUDE_DIRS}") + set(PYTHON_LIBRARIES "${Python3_LIBRARIES}") +else () + find_python_package(py_inc py_lib) + set(PYTHON_INCLUDE_DIRS "${py_inc}") + set(PYTHON_LIBRARIES "${py_lib}") +endif () + +include_directories(${PYTHON_INCLUDE_DIRS}) +pybind11_add_module(_mindspore_serving ${PY_SRC_LIST}) +target_link_libraries(_mindspore_serving PRIVATE "${PYTHON_LIBRARIES}") +target_include_directories(_mindspore_serving PRIVATE ${pybind11_INCLUDE_DIRS}) +target_link_libraries(_mindspore_serving PRIVATE serving_common) +set_property(TARGET _mindspore_serving PROPERTY POSITION_INDEPENDENT_CODE TRUE) + +# user set path + +if (MS_WHL_LIB_PATH) + include_directories(${MS_WHL_LIB_PATH}/../include) + include_directories(${MS_WHL_LIB_PATH}/../) + target_link_libraries(serving_common PRIVATE ${MS_WHL_LIB_PATH}/libmindspore.so) +elseif (MS_BACKEND) + include_directories(${MS_SOURCE_DIR}/build/package/mindspore) + include_directories(${MS_SOURCE_DIR}/build/package/mindspore/include) + target_link_libraries(serving_common PRIVATE ${MS_SOURCE_DIR}/build/package/mindspore/lib/libmindspore.so) +else () + message(FATAL_ERROR "Please check MindSpore path.") +endif () diff --git a/mindspore_serving/__init__.py b/mindspore_serving/__init__.py new file mode 100644 index 0000000..e307743 --- /dev/null +++ b/mindspore_serving/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ diff --git a/mindspore_serving/ccsrc/common/buffer_tensor.cc b/mindspore_serving/ccsrc/common/buffer_tensor.cc new file mode 100644 index 0000000..87a4c21 --- /dev/null +++ b/mindspore_serving/ccsrc/common/buffer_tensor.cc @@ -0,0 +1,63 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/buffer_tensor.h" +namespace mindspore::serving { + +BufferTensor::BufferTensor(DataType type, const std::vector shape, uint8_t *data, size_t data_len, + bool data_readonly) { + set_shape(shape); + set_data_type(type); + data_ = data; + data_len_ = data_len; + data_readonly_ = data_readonly; +} + +std::vector BufferTensor::shape() const { return shape_; } + +void BufferTensor::set_shape(const std::vector &shape) { shape_ = shape; } + +DataType BufferTensor::data_type() const { return type_; } + +void BufferTensor::set_data_type(DataType type) { type_ = type; } + +const uint8_t *BufferTensor::data() const { return data_; } + +size_t BufferTensor::data_size() const { return data_len_; } + +uint8_t *BufferTensor::mutable_data() { + if (data_readonly_) { + MSI_LOG_EXCEPTION << "Buffer tensor is create readonly"; + } + return data_; +} + +size_t BufferTensor::bytes_data_size() const { + if (!is_bytes_val_data()) { + return 0; + } + return 1; +} + +void BufferTensor::get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const { + if (!is_bytes_val_data()) { + MSI_LOG_EXCEPTION << "Buffer tensor data type is not kMSI_Bytes or kMSI_String, cannot get bytes data"; + } + data = data_; + bytes_len = data_len_; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/common/buffer_tensor.h b/mindspore_serving/ccsrc/common/buffer_tensor.h new file mode 100644 index 0000000..a0f404c --- /dev/null +++ b/mindspore_serving/ccsrc/common/buffer_tensor.h @@ -0,0 +1,62 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_BUFFER_TENSOR_H +#define MINDSPORE_BUFFER_TENSOR_H + +#include +#include "common/serving_common.h" + +namespace mindspore::serving { + +class BufferTensor : public TensorBase { + public: + // the data's lifetime must longer than this object + BufferTensor(DataType type, std::vector shape, uint8_t *data, size_t data_len, bool data_readonly); + ~BufferTensor() = default; + + // For all data type + std::vector shape() const override; + void set_shape(const std::vector &shape) override; + DataType data_type() const override; + void set_data_type(DataType type) override; + + // All the following interfaces are not for kMSI_String and kMSI_Bytes + const uint8_t *data() const override; + size_t data_size() const override; + bool resize_data(size_t data_len) override { MSI_LOG_EXCEPTION << "Buffer tensor cannot resize data"; } + uint8_t *mutable_data() override; + + // For kMSI_String and kMSI_Bytes + void clear_bytes_data() override { MSI_LOG_EXCEPTION << "Buffer tensor cannot clear bytes data"; } + void add_bytes_data(const uint8_t *data, size_t bytes_len) override { + MSI_LOG_EXCEPTION << "Buffer tensor cannot add bytes data"; + } + + size_t bytes_data_size() const override; + void get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const override; + + private: + uint8_t *data_ = nullptr; + size_t data_len_ = 0; + std::vector shape_; + DataType type_; + bool data_readonly_ = false; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_BUFFER_TENSOR_H diff --git a/mindspore_serving/ccsrc/common/exit_handle.cc b/mindspore_serving/ccsrc/common/exit_handle.cc new file mode 100644 index 0000000..5a48375 --- /dev/null +++ b/mindspore_serving/ccsrc/common/exit_handle.cc @@ -0,0 +1,62 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/exit_handle.h" +#include +#include + +namespace mindspore { +namespace serving { + +ExitHandle &ExitHandle::Instance() { + static ExitHandle instance; + return instance; +} + +void ExitHandle::InitSignalHandle() { + if (!has_inited_.test_and_set()) { + signal(SIGINT, HandleSignal); + signal(SIGTERM, HandleSignal); + } +} + +void ExitHandle::MasterWait() { + InitSignalHandle(); + auto exit_future = master_exit_requested_.get_future(); + exit_future.wait(); +} + +void ExitHandle::WorkerWait() { + InitSignalHandle(); + auto exit_future = worker_exit_requested_.get_future(); + exit_future.wait(); +} + +void ExitHandle::Stop() { HandleSignal(0); } + +bool ExitHandle::HasStopped() { return is_exit_; } + +void ExitHandle::HandleSignal(int sig) { + auto &instance = Instance(); + if (!instance.has_exited_.test_and_set()) { + instance.master_exit_requested_.set_value(); + instance.worker_exit_requested_.set_value(); + instance.is_exit_.store(true); + } +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/common/exit_handle.h b/mindspore_serving/ccsrc/common/exit_handle.h new file mode 100644 index 0000000..de82daf --- /dev/null +++ b/mindspore_serving/ccsrc/common/exit_handle.h @@ -0,0 +1,49 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_EXIT_HANDLE_H +#define MINDSPORE_SERVING_EXIT_HANDLE_H +#include +#include +#include +#include "common/serving_common.h" + +namespace mindspore { +namespace serving { + +class MS_API ExitHandle { + public: + static ExitHandle &Instance(); + void InitSignalHandle(); + void MasterWait(); + void WorkerWait(); + void Stop(); + bool HasStopped(); + + private: + std::promise master_exit_requested_; + std::promise worker_exit_requested_; + std::atomic_flag has_exited_ = ATOMIC_FLAG_INIT; + std::atomic_flag has_inited_ = ATOMIC_FLAG_INIT; + std::atomic is_exit_ = false; + + static void HandleSignal(int sig); +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_EXIT_HANDLE_H diff --git a/mindspore_serving/ccsrc/common/file_system_operation.cc b/mindspore_serving/ccsrc/common/file_system_operation.cc new file mode 100644 index 0000000..a3e65d4 --- /dev/null +++ b/mindspore_serving/ccsrc/common/file_system_operation.cc @@ -0,0 +1,72 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "common/file_system_operation.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/serving_common.h" + +namespace mindspore { +namespace serving { +bool DirOrFileExist(const std::string &file_path) { + int ret = access(file_path.c_str(), 0); + return (ret == -1) ? false : true; +} + +std::vector GetAllSubDirs(const std::string &dir_path) { + std::vector SubDirs = GetAllSubDirsNotFullPath(dir_path); + for (auto &item : SubDirs) { + item = dir_path + "/" + item; + } + return SubDirs; +} + +std::vector GetAllSubDirsNotFullPath(const std::string &dir_path) { + DIR *dir = nullptr; + struct dirent *ptr = nullptr; + std::vector SubDirs; + + if ((dir = opendir(dir_path.c_str())) == NULL) { + MSI_LOG(ERROR) << "Open " << dir_path << " error!"; + return std::vector(); + } + + while ((ptr = readdir(dir)) != NULL) { + std::string name = ptr->d_name; + if (name == "." || name == "..") { + continue; + } + if (ptr->d_type == DT_DIR) { + SubDirs.push_back(name); + } + } + closedir(dir); + std::sort(SubDirs.begin(), SubDirs.end()); + return SubDirs; +} + +time_t GetModifyTime(const std::string &file_path) { + struct stat info; + (void)stat(file_path.c_str(), &info); + return info.st_mtime; +} +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/common/file_system_operation.h b/mindspore_serving/ccsrc/common/file_system_operation.h new file mode 100644 index 0000000..20261d8 --- /dev/null +++ b/mindspore_serving/ccsrc/common/file_system_operation.h @@ -0,0 +1,33 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MINDSPORE_SERVING_FILE_SYSTEM_OPERATION_H_ +#define MINDSPORE_SERVING_FILE_SYSTEM_OPERATION_H_ + +#include +#include +#include + +namespace mindspore { +namespace serving { +char *ReadFile(const char *file, size_t *size); +bool DirOrFileExist(const std::string &file_path); +std::vector GetAllSubDirs(const std::string &dir_path); +std::vector GetAllSubDirsNotFullPath(const std::string &dir_path); +time_t GetModifyTime(const std::string &file_path); +} // namespace serving +} // namespace mindspore + +#endif // !MINDSPORE_SERVING_FILE_SYSTEM_OPERATION_H_ diff --git a/mindspore_serving/ccsrc/common/grpc_server.cc b/mindspore_serving/ccsrc/common/grpc_server.cc new file mode 100644 index 0000000..18d705d --- /dev/null +++ b/mindspore_serving/ccsrc/common/grpc_server.cc @@ -0,0 +1,68 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/grpc_server.h" + +namespace mindspore::serving { + +Status GrpcServer::Start(std::shared_ptr service, const std::string &ip, uint32_t grpc_port, + int max_msg_mb_size, const std::string &server_tag) { + service_ = service; + Status status; + if (in_running_) { + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Grpc server is running"; + } + + std::string server_address = ip + ":" + std::to_string(grpc_port); + + grpc::EnableDefaultHealthCheckService(true); + grpc::reflection::InitProtoReflectionServerBuilderPlugin(); + // Set the port is not reuseable + auto option = grpc::MakeChannelArgumentOption(GRPC_ARG_ALLOW_REUSEPORT, 0); + grpc::ServerBuilder serverBuilder; + serverBuilder.SetOption(std::move(option)); + if (max_msg_mb_size > 0) { + serverBuilder.SetMaxReceiveMessageSize(static_cast(max_msg_mb_size * (1u << 20))); + } + serverBuilder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + serverBuilder.RegisterService(service.get()); + server_ = serverBuilder.BuildAndStart(); + + if (server_ == nullptr) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Serving Error: create grpc server failed, gRPC address " << server_address; + } + + auto grpc_server_run = [this, server_address, server_tag]() { + MSI_LOG(INFO) << server_tag << " start success, listening on " << server_address; + std::cout << "Serving: " << server_tag << " start success, listening on " << server_address << std::endl; + server_->Wait(); + }; + + grpc_thread_ = std::thread(grpc_server_run); + in_running_ = true; + return SUCCESS; +} + +void GrpcServer::Stop() { + if (in_running_) { + server_->Shutdown(); + grpc_thread_.join(); + } + in_running_ = false; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/common/grpc_server.h b/mindspore_serving/ccsrc/common/grpc_server.h new file mode 100644 index 0000000..55104da --- /dev/null +++ b/mindspore_serving/ccsrc/common/grpc_server.h @@ -0,0 +1,52 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_GRPC_SERVER_H +#define MINDSPORE_GRPC_SERVER_H + +#include +#include +#include +#include +#include +#include +#include +#include "common/serving_common.h" + +namespace mindspore::serving { + +constexpr int gRpcDefaultMsgMBSize = 100; +constexpr int gRpcMaxMBMsgSize = 512; // max 512 MB + +class MS_API GrpcServer { + public: + GrpcServer() = default; + ~GrpcServer() { Stop(); } + + Status Start(std::shared_ptr service, const std::string &ip, uint32_t grpc_port, int max_msg_size, + const std::string &server_tag); + void Stop(); + + private: + std::unique_ptr server_; + std::thread grpc_thread_; + bool in_running_ = false; + std::shared_ptr service_; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_GRPC_SERVER_H diff --git a/mindspore_serving/ccsrc/common/instance.h b/mindspore_serving/ccsrc/common/instance.h new file mode 100644 index 0000000..8f61915 --- /dev/null +++ b/mindspore_serving/ccsrc/common/instance.h @@ -0,0 +1,73 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_INSTANCE_H +#define MINDSPORE_SERVING_INSTANCE_H + +#include +#include +#include +#include +#include +#include "common/serving_common.h" +#include "common/servable.h" + +namespace mindspore::serving { + +using InstanceData = std::vector; +using TensorsData = std::vector; +struct Instance; +using WorkerCallBack = std::function; + +struct WorkerUserContext { + WorkerCallBack worker_call_back = nullptr; + RequestSpec request_spec; + MethodSignature method_def; +}; + +struct InstanceContext { + uint64_t user_id = 0; + uint32_t instance_index = 0; + + WorkerCallBack worker_call_back = nullptr; + std::shared_ptr> promise = nullptr; + std::shared_ptr user_context = nullptr; + + RequestSpec request_spec; + bool operator==(const InstanceContext &other) const { + return user_id == other.user_id && instance_index == other.instance_index; + } +}; + +struct Instance { + InstanceData data; // for inputs of preprocess, predict, postprocess or output + + InstanceData input_data; // input data + InstanceData preprocess_data; // preprocess result + InstanceData predict_data; // predict result + InstanceData postprocess_data; // postprocess result + InstanceContext context; + Status error_msg = SUCCESS; +}; + +struct ResultInstance { + InstanceData data; + Status error_msg = SUCCESS; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_INSTANCE_H diff --git a/mindspore_serving/ccsrc/common/log.cc b/mindspore_serving/ccsrc/common/log.cc new file mode 100644 index 0000000..0366e37 --- /dev/null +++ b/mindspore_serving/ccsrc/common/log.cc @@ -0,0 +1,103 @@ +#include "log.h" + +#include +#include "glog/logging.h" + +namespace mindspore { +namespace serving { + +#undef Dlog +#define Dlog(module_id, level, format, ...) \ + do { \ + DlogInner((module_id), (level), (format), ##__VA_ARGS__); \ + } while (0) + +static std::string GetTime() { +#define BUFLEN 80 + static char buf[BUFLEN]; +#if defined(_WIN32) || defined(_WIN64) + time_t time_seconds = time(0); + struct tm now_time; + localtime_s(&now_time, &time_seconds); + sprintf_s(buf, BUFLEN, "%d-%d-%d %d:%d:%d", now_time.tm_year + 1900, now_time.tm_mon + 1, now_time.tm_mday, + now_time.tm_hour, now_time.tm_min, now_time.tm_sec); +#else + struct timeval cur_time; + (void)gettimeofday(&cur_time, nullptr); + + struct tm now; + (void)localtime_r(&cur_time.tv_sec, &now); + (void)strftime(buf, BUFLEN, "%Y-%m-%d-%H:%M:%S", &now); // format date and time + // set micro-second + buf[27] = '\0'; + int idx = 26; + auto num = cur_time.tv_usec; + for (int i = 5; i >= 0; i--) { + buf[idx--] = static_cast(num % 10 + '0'); + num /= 10; + if (i % 3 == 0) { + buf[idx--] = '.'; + } + } +#endif + return std::string(buf); +} + +static std::string GetProcName() { +#if defined(__APPLE__) || defined(__FreeBSD__) + const char *appname = getprogname(); +#elif defined(_GNU_SOURCE) + const char *appname = program_invocation_name; +#else + const char *appname = "?"; +#endif + // some times, the appname is an absolute path, its too long + std::string app_name(appname); + std::size_t pos = app_name.rfind("/"); + if (pos == std::string::npos) { + return app_name; + } + if (pos + 1 >= app_name.size()) { + return app_name; + } + return app_name.substr(pos + 1); +} + +static std::string GetLogLevel(ERROR_LEVEL level) { + switch (level) { + case LOG_DEBUG: + return "DEBUG"; + case LOG_INFO: + return "INFO"; + case LOG_WARNING: + return "WARNING"; + case LOG_ERROR: + default: + return "ERROR"; + } +} + +// convert MsLogLevel to corresponding glog level +static int GetGlogLevel(ERROR_LEVEL level) { + switch (level) { + case LOG_DEBUG: + case LOG_INFO: + return google::GLOG_INFO; + case LOG_WARNING: + return google::GLOG_WARNING; + case LOG_ERROR: + default: + return google::GLOG_ERROR; + } +} + +void LogWriter::OutputLog(const std::ostringstream &msg) const { + auto submodule_name = "Serving"; + google::LogMessage("", 0, GetGlogLevel(log_level_)).stream() + << "[" << GetLogLevel(log_level_) << "] " << submodule_name << "(" << getpid() << "," << GetProcName() + << "):" << GetTime() << " " + << "[" << file_ << ":" << line_ << "] " << func_ << "] " << msg.str() << std::endl; +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/common/log.h b/mindspore_serving/ccsrc/common/log.h new file mode 100644 index 0000000..3d97a3e --- /dev/null +++ b/mindspore_serving/ccsrc/common/log.h @@ -0,0 +1,137 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_INC_LOG_H +#define MINDSPORE_SERVING_INC_LOG_H + +#include +#include +#include +#include +#include +#include +namespace mindspore::serving { +#define MS_API __attribute__((visibility("default"))) + +class LogStream { + public: + LogStream() { sstream_ = std::make_shared(); } + ~LogStream() = default; + + template + LogStream &operator<<(const T &val) noexcept { + (*sstream_) << val; + return *this; + } + + template + LogStream &operator<<(const std::vector &val) noexcept { + (*sstream_) << "["; + for (size_t i = 0; i < val.size(); i++) { + (*this) << val[i]; + if (i + 1 < val.size()) { + (*sstream_) << ", "; + } + } + (*sstream_) << "]"; + return *this; + } + + template + LogStream &operator<<(const std::unordered_map &val) noexcept { + (*sstream_) << "{"; + for (auto &item : val) { + (*this) << "{" << item.first << ": " << item.second << "} "; + } + (*sstream_) << "}"; + return *this; + } + + LogStream &operator<<(std::ostream &func(std::ostream &os)) noexcept { + (*sstream_) << func; + return *this; + } + + friend class LogWriter; + friend class Status; + + private: + std::shared_ptr sstream_; +}; + +enum ERROR_LEVEL { + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_EXCEPTION, +}; + +class MS_API LogWriter { + public: + LogWriter(const char *file, int line, const char *func, ERROR_LEVEL log_level) + : file_(file), line_(line), func_(func), log_level_(log_level) {} + ~LogWriter() = default; + + std::string operator<(const LogStream &stream) const noexcept __attribute__((visibility("default"))) { + std::ostringstream msg; + msg << stream.sstream_->rdbuf(); + OutputLog(msg); + return msg.str(); + } + + void operator^(const LogStream &stream) const __attribute__((noreturn, visibility("default"))) { + std::ostringstream msg; + msg << stream.sstream_->rdbuf(); + OutputLog(msg); + throw std::runtime_error(msg.str()); + } + + private: + void OutputLog(const std::ostringstream &msg) const; + + const char *file_; + int line_; + const char *func_; + ERROR_LEVEL log_level_; +}; + +#define MSILOG_IF(level) \ + mindspore::serving::LogWriter(__FILE__, __LINE__, __FUNCTION__, mindspore::serving::LOG_##level) < \ + mindspore::serving::LogStream() + +#define MSILOG_THROW \ + mindspore::serving::LogWriter(__FILE__, __LINE__, __FUNCTION__, mindspore::serving::LOG_EXCEPTION) ^ \ + mindspore::serving::LogStream() + +#define MSI_LOG(level) MSI_LOG_##level + +#define MSI_LOG_DEBUG MSILOG_IF(DEBUG) +#define MSI_LOG_INFO MSILOG_IF(INFO) +#define MSI_LOG_WARNING MSILOG_IF(WARNING) +#define MSI_LOG_ERROR MSILOG_IF(ERROR) +#define MSI_LOG_EXCEPTION MSILOG_THROW + +#define MSI_EXCEPTION_IF_NULL(ptr) \ + do { \ + if ((ptr) == nullptr) { \ + MSI_LOG_EXCEPTION << ": The pointer[" << #ptr << "] is null."; \ + } \ + } while (0) + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_INC_LOG_H diff --git a/mindspore_serving/ccsrc/common/proto_tensor.cc b/mindspore_serving/ccsrc/common/proto_tensor.cc new file mode 100644 index 0000000..c1bc7d1 --- /dev/null +++ b/mindspore_serving/ccsrc/common/proto_tensor.cc @@ -0,0 +1,377 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/proto_tensor.h" +#include +#include +#include +#include +#include +#include "common/buffer_tensor.h" +#include "common/servable.h" +#include "master/dispacther.h" + +using std::string; +using std::unordered_map; +using std::vector; + +namespace mindspore::serving { + +const size_t kMaxShapeElementCount = INT32_MAX; +const size_t kMaxDataBufferSize = UINT32_MAX; + +ProtoTensor::ProtoTensor(proto::Tensor &other) : tensor_(other) {} + +ProtoTensor::~ProtoTensor() {} + +DataType ProtoTensor::data_type() const { return TransDataType2Inference(tensor_.dtype()); } + +void ProtoTensor::set_data_type(DataType data_type) { tensor_.set_dtype(TransDataType2Proto(data_type)); } + +std::vector ProtoTensor::shape() const { + std::vector result; + auto dims = tensor_.shape().dims(); + std::transform(dims.begin(), dims.end(), std::back_inserter(result), [](const int64_t dim) { return dim; }); + return result; +} + +void ProtoTensor::set_shape(const std::vector &shape) { + auto tensor_shape = tensor_.mutable_shape(); + tensor_shape->Clear(); + size_t element_count = 1; + for (auto dim : shape) { + if (dim <= 0 || element_count > kMaxShapeElementCount / dim) { + MSI_LOG_ERROR << "failed to set shape, invalid dim num " << dim; + tensor_shape->Clear(); + return; + } + element_count *= dim; + tensor_shape->add_dims(dim); + } +} + +bool ProtoTensor::resize_data(size_t data_len) { + string *buffer = tensor_.mutable_data(); + if (buffer == nullptr) { + MSI_LOG_ERROR << "invalid buffer data"; + return false; + } + buffer->resize(data_len); + return true; +} + +size_t ProtoTensor::data_size() const { return tensor_.data().size(); } + +uint8_t *ProtoTensor::mutable_data() { + if (data_size() == 0) { + return nullptr; + } + return reinterpret_cast(tensor_.mutable_data()->data()); +} + +const uint8_t *ProtoTensor::data() const { + if (data_size() == 0) { + return nullptr; + } + return reinterpret_cast(tensor_.data().data()); +} + +void ProtoTensor::clear_bytes_data() { return tensor_.mutable_bytes_val()->Clear(); } + +void ProtoTensor::add_bytes_data(const uint8_t *data, size_t bytes_len) { tensor_.add_bytes_val(data, bytes_len); } + +size_t ProtoTensor::bytes_data_size() const { return tensor_.bytes_val().size(); } + +void ProtoTensor::get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const { + if (index >= static_cast(tensor_.bytes_val().size())) { + MSI_LOG_EXCEPTION << "visit invalid index " << index << " total size " << tensor_.bytes_val().size(); + } + auto &bytes = tensor_.bytes_val(index); + data = reinterpret_cast(bytes.data()); + bytes_len = bytes.size(); +} + +proto::DataType ProtoTensor::TransDataType2Proto(DataType data_type) { + const std::unordered_map id2type_map{ + {serving::kMSI_Unknown, proto::MS_UNKNOWN}, {serving::kMSI_Bool, proto::MS_BOOL}, + {serving::kMSI_Float64, proto::MS_FLOAT64}, {serving::kMSI_Int8, proto::MS_INT8}, + {serving::kMSI_Uint8, proto::MS_UINT8}, {serving::kMSI_Int16, proto::MS_INT16}, + {serving::kMSI_Uint16, proto::MS_UINT16}, {serving::kMSI_Int32, proto::MS_INT32}, + {serving::kMSI_Uint32, proto::MS_UINT32}, {serving::kMSI_Int64, proto::MS_INT64}, + {serving::kMSI_Uint64, proto::MS_UINT64}, {serving::kMSI_Float16, proto::MS_FLOAT16}, + {serving::kMSI_Float32, proto::MS_FLOAT32}, {serving::kMSI_String, proto::MS_STRING}, + {serving::kMSI_Bytes, proto::MS_BYTES}, + }; + auto it = id2type_map.find(data_type); + if (it == id2type_map.end()) { + MSI_LOG_WARNING << "failed to set data type, undefined data type " << data_type; + return proto::MS_UNKNOWN; + } else { + return it->second; + } +} + +DataType ProtoTensor::TransDataType2Inference(proto::DataType data_type) { + const std::unordered_map type2id_map{ + {proto::MS_UNKNOWN, kMSI_Unknown}, {proto::MS_BOOL, kMSI_Bool}, {proto::MS_INT8, kMSI_Int8}, + {proto::MS_UINT8, kMSI_Uint8}, {proto::MS_INT16, kMSI_Int16}, {proto::MS_UINT16, kMSI_Uint16}, + {proto::MS_INT32, kMSI_Int32}, {proto::MS_UINT32, kMSI_Uint32}, {proto::MS_INT64, kMSI_Int64}, + {proto::MS_UINT64, kMSI_Uint64}, {proto::MS_FLOAT16, kMSI_Float16}, {proto::MS_FLOAT32, kMSI_Float32}, + {proto::MS_FLOAT64, kMSI_Float64}, {proto::MS_STRING, kMSI_String}, {proto::MS_BYTES, kMSI_Bytes}, + }; + auto it = type2id_map.find(data_type); + if (it == type2id_map.end()) { + MSI_LOG_WARNING << "failed to get data type, undefined data type " << data_type; + return kMSI_Unknown; + } else { + return it->second; + } +} + +void GrpcTensorHelper::GetRequestSpec(const proto::PredictRequest &request, RequestSpec &request_spec) { + request_spec.servable_name = request.servable_spec().name(); + request_spec.method_name = request.servable_spec().method_name(); + request_spec.version_number = request.servable_spec().version_number(); +} + +void GrpcTensorHelper::GetWorkerSpec(const proto::RegisterRequest &request, std::vector &worker_specs) { + for (auto &proto_spec : request.worker_spec()) { + WorkerSpec worker_spec; + worker_spec.worker_address = request.address(); + worker_spec.servable_name = proto_spec.name(); + worker_spec.version_number = proto_spec.version_number(); + for (const auto &proto_method : proto_spec.methods()) { + WorkerMethodInfo method_info; + method_info.name = proto_method.name(); + for (auto &name : proto_method.input_names()) { + method_info.input_names.push_back(name); + } + worker_spec.methods.push_back(method_info); + } + worker_specs.push_back(worker_spec); + } +} + +void GrpcTensorHelper::GetWorkerSpec(const proto::AddWorkerRequest &request, WorkerSpec &worker_spec) { + worker_spec.worker_address = request.address(); + worker_spec.servable_name = request.worker_spec().name(); + worker_spec.version_number = request.worker_spec().version_number(); + for (const auto &proto_method : request.worker_spec().methods()) { + WorkerMethodInfo method_info; + method_info.name = proto_method.name(); + for (auto &name : proto_method.input_names()) { + method_info.input_names.push_back(name); + } + worker_spec.methods.push_back(method_info); + } +} + +void GrpcTensorHelper::GetWorkerSpec(const proto::RemoveWorkerRequest &request, WorkerSpec &worker_spec) { + worker_spec.worker_address = request.address(); + worker_spec.servable_name = request.worker_spec().name(); + worker_spec.version_number = request.worker_spec().version_number(); + for (const auto &proto_method : request.worker_spec().methods()) { + WorkerMethodInfo method_info; + method_info.name = proto_method.name(); + for (auto &name : proto_method.input_names()) { + method_info.input_names.push_back(name); + } + worker_spec.methods.push_back(method_info); + } +} + +Status GrpcTensorHelper::CreateInstanceFromRequest(const proto::PredictRequest &request, RequestSpec &request_spec, + vector &results) { + results.clear(); + + Status status; + GetRequestSpec(request, request_spec); + + auto servable_name = request_spec.servable_name; + auto method_name = request_spec.method_name; + + ServableSignature servable_signature; + if (!ServableStorage::Instance()->GetServableDef(servable_name, servable_signature)) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Servable " << servable_name << " is not declared"; + } + MethodSignature method_signature; + if (!servable_signature.GetMethodDeclare(request_spec.method_name, method_signature)) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Method " << method_name << " is not registed for servable " << servable_name; + } + + // instance + if (request.instances_size() > 0) { + status = CreateInstanceFromRequestInstances(request, method_signature.inputs, results); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Create instances from request instances failed"; + return status; + } + } + return SUCCESS; +} + +Status GrpcTensorHelper::CreateReplyFromInstances(const proto::PredictRequest &request, const vector &outputs, + proto::PredictReply &reply) { + auto servable_name = request.servable_spec().name(); + auto method_name = request.servable_spec().method_name(); + Status status; + ServableSignature servable_signature; + if (!ServableStorage::Instance()->GetServableDef(servable_name, servable_signature)) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Servable " << servable_name << " is not declared"; + } + MethodSignature method_signature; + if (!servable_signature.GetMethodDeclare(method_name, method_signature)) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Method " << method_name << " is not registed for servable " << servable_name; + } + *reply.mutable_servable_spec() = request.servable_spec(); + + size_t err_cnt = 0; + for (auto &output_intance : outputs) { + if (output_intance.error_msg != SUCCESS) { + err_cnt++; + } else if (output_intance.data.size() != method_signature.outputs.size()) { + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) + << "Result data tensor size " << output_intance.data.size() << " not equal outputs size " + << method_signature.outputs.size() << "defined in method signature"; + } + } + if (err_cnt > 0) { + for (auto &output_intance : outputs) { + auto proto_err_msg = reply.add_error_msg(); + proto_err_msg->set_error_code(output_intance.error_msg.StatusCode()); + if (output_intance.error_msg == INVALID_INPUTS) { + proto_err_msg->set_error_msg(output_intance.error_msg.StatusMessage()); + } else if (output_intance.error_msg != SUCCESS) { + proto_err_msg->set_error_msg(output_intance.error_msg.StatusMessage()); + } + } + } + // create instance reply, same with request + if (request.instances_size() > 0) { + for (auto &output_intance : outputs) { + auto proto_instance = reply.add_instances(); + if (output_intance.data.empty()) { + continue; + } + auto proto_items = proto_instance->mutable_items(); + for (size_t i = 0; i < method_signature.outputs.size(); i++) { + auto &output_tensor = output_intance.data[i]; + ProtoTensor proto_tensor((*proto_items)[method_signature.outputs[i]]); + proto_tensor.assgin(*output_tensor); + } + } + } + return SUCCESS; +} + +Status GrpcTensorHelper::CreateInstanceFromRequestInstances(const proto::PredictRequest &request, + const std::vector &input_names, + std::vector &results) { + auto servable_name = request.servable_spec().name(); + auto method_name = request.servable_spec().method_name(); + Status status; + for (auto &proto_instance : request.instances()) { + InstanceData instance_data; + for (const auto &input_name : input_names) { + auto it = proto_instance.items().find(input_name); + if (it == proto_instance.items().end()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Cannot find input " << input_name << " in instance input , servable " << servable_name << ", method " + << method_name; + } + status = CheckRequestTensor(it->second, true, 1); + if (status != SUCCESS) { + auto status2 = INFER_STATUS(INVALID_INPUTS) << "Instances input " << input_name << " check failed"; + MSI_LOG_ERROR << status2.StatusMessage(); + return Status(INVALID_INPUTS, status2.StatusMessage() + ", detail: " + status.StatusMessage()); + } + auto add_tensor = std::make_shared(const_cast(it->second)); + instance_data.push_back(add_tensor); + } + results.push_back(instance_data); + } + return SUCCESS; +} + +Status GrpcTensorHelper::CheckRequestTensor(const proto::Tensor &tensor, bool is_instance_tensor, uint32_t batch_size) { + Status status; + ProtoTensor tensor_input(const_cast(tensor)); + auto shape = tensor_input.shape(); + if (tensor.dtype() == proto::MS_BYTES || tensor.dtype() == proto::MS_STRING) { + if (is_instance_tensor) { + if (tensor.bytes_val_size() != 1) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Instance tensor check failed: bytes or string type shape batch size " << batch_size + << " not equal to bytes val size " << tensor.bytes_val_size(); + } + if (!(shape.size() == 1 && shape[0] == 1) && !shape.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Instance tensor check failed: bytes or string type input " + << " shape can only be (1,) or empty, but given shape is " << shape; + } + } else { + if (static_cast(tensor.bytes_val_size()) != batch_size) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Inputs tensor check failed: bytes or string type shape batch size " << batch_size + << " not equal to bytes val size " << tensor.bytes_val_size(); + } + if (shape.size() != 1) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Inputs Tensor check failed: bytes or string type input " + << " shape can only be (batch_size,), but given shape is " << shape; + } + } + } else { + size_t element_num = tensor_input.element_cnt(); + if (element_num == 0) { // shape dim invalid or element count > max element num + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Tensor check failed: input " + << " shape " << tensor_input.shape() << " invalid"; + } + if (tensor_input.data_type() == kMSI_Unknown || tensor_input.itemsize() == 0) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Tensor check failed: input " + << " data type " << tensor.dtype() << " invalid"; + } + if (element_num * tensor_input.itemsize() != tensor_input.data_size()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Tensor check failed: input " + << " data size " << tensor.data().size() << " invalid"; + } + } + return SUCCESS; +} + +serving::LogStream &operator<<(serving::LogStream &stream, proto::DataType data_type) { + const std::map type_name_map{ + {proto::MS_UNKNOWN, "proto::MS_UNKNOWN"}, {proto::MS_BOOL, "proto::kMSI_Bool"}, + {proto::MS_INT8, "proto::MS_INT8"}, {proto::MS_UINT8, "proto::MS_UINT8"}, + {proto::MS_INT16, "proto::MS_INT16"}, {proto::MS_UINT16, "proto::MS_UINT16"}, + {proto::MS_INT32, "proto::MS_INT32"}, {proto::MS_UINT32, "proto::MS_UINT32"}, + {proto::MS_INT64, "proto::MS_INT64"}, {proto::MS_UINT64, "proto::MS_UINT64"}, + {proto::MS_FLOAT16, "proto::MS_FLOAT16"}, {proto::MS_FLOAT32, "proto::MS_FLOAT32"}, + {proto::MS_FLOAT64, "proto::MS_FLOAT64"}, {proto::MS_STRING, "proto::MS_STRING"}, + {proto::MS_BYTES, "proto::MS_BYTES"}, + }; + auto it = type_name_map.find(data_type); + if (it != type_name_map.end()) { + stream << it->second; + } else { + stream << "proto::MS_UNKNOWN"; + } + return stream; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/common/proto_tensor.h b/mindspore_serving/ccsrc/common/proto_tensor.h new file mode 100644 index 0000000..d82a94d --- /dev/null +++ b/mindspore_serving/ccsrc/common/proto_tensor.h @@ -0,0 +1,82 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_TENSOR_H_ +#define MINDSPORE_SERVING_TENSOR_H_ + +#include +#include +#include +#include +#include "common/serving_common.h" +#include "proto/ms_service.pb.h" +#include "proto/ms_master.pb.h" +#include "common/instance.h" +#include "common/servable.h" + +namespace mindspore::serving { + +class MS_API ProtoTensor : public TensorBase { + public: + // the other's lifetime must longer than this object + explicit ProtoTensor(proto::Tensor &other); + ~ProtoTensor(); + + DataType data_type() const override; + void set_data_type(DataType type) override; + std::vector shape() const override; + void set_shape(const std::vector &shape) override; + const uint8_t *data() const override; + size_t data_size() const override; + bool resize_data(size_t data_len) override; + uint8_t *mutable_data() override; + + void clear_bytes_data() override; + void add_bytes_data(const uint8_t *data, size_t bytes_len) override; + size_t bytes_data_size() const override; + void get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const override; + + static proto::DataType TransDataType2Proto(DataType data_type); + static DataType TransDataType2Inference(proto::DataType data_type); + + private: + // if tensor_ is reference from other ms_serving::Tensor, the other's lifetime must + // longer than this object + proto::Tensor &tensor_; +}; + +class MS_API GrpcTensorHelper { + public: + static void GetRequestSpec(const proto::PredictRequest &request, RequestSpec &request_spec); + static void GetWorkerSpec(const proto::RegisterRequest &request, std::vector &worker_specs); + static void GetWorkerSpec(const proto::AddWorkerRequest &request, WorkerSpec &worker_spec); + static void GetWorkerSpec(const proto::RemoveWorkerRequest &request, WorkerSpec &worker_spec); + static Status CreateInstanceFromRequest(const proto::PredictRequest &request, RequestSpec &request_spec, + std::vector &results); + static Status CreateReplyFromInstances(const proto::PredictRequest &request, const std::vector &inputs, + proto::PredictReply &reply); + + private: + static Status CreateInstanceFromRequestInstances(const proto::PredictRequest &request, + const std::vector &input_names, + std::vector &results); + static Status CheckRequestTensor(const proto::Tensor &tensor, bool is_instance_tensor, uint32_t batch_size); +}; + +extern MS_API LogStream &operator<<(serving::LogStream &stream, proto::DataType data_type); + +} // namespace mindspore::serving +#endif // MINDSPORE_SERVING_TENSOR_H_ diff --git a/mindspore_serving/ccsrc/common/servable.cc b/mindspore_serving/ccsrc/common/servable.cc new file mode 100644 index 0000000..e001a49 --- /dev/null +++ b/mindspore_serving/ccsrc/common/servable.cc @@ -0,0 +1,295 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/servable.h" +#include +#include +#include "worker/preprocess.h" +#include "worker/postprocess.h" + +namespace mindspore::serving { + +std::string ServableMeta::Repr() const { + std::ostringstream stream; + stream << "path(" << servable_name << ") file(" << servable_file + ")"; + return stream.str(); +} + +void ServableMeta::SetModelFormat(const std::string &format) { + if (format == "om") { + model_format = kOM; + } else if (format == "mindir") { + model_format = kMindIR; + } else { + MSI_LOG_ERROR << "Invalid model format " << format; + } +} + +std::string LoadServableSpec::Repr() const { + std::string version; + if (version_number > 0) { + version = " version(" + std::to_string(version_number) + ") "; + } + return "servable(" + servable_name + ") " + version; +} + +std::string WorkerSpec::Repr() const { + std::string version; + if (version_number > 0) { + version = " version(" + std::to_string(version_number) + ") "; + } + return "servable(" + servable_name + ") " + version + " address(" + worker_address + ") "; +} + +std::string RequestSpec::Repr() const { + std::string version; + if (version_number > 0) { + version = " version(" + std::to_string(version_number) + ") "; + } + return "servable(" + servable_name + ") " + "method(" + method_name + ") " + version; +} + +Status ServableSignature::Check() const { + std::set method_set; + std::string model_str = servable_meta.Repr(); + + for (auto &method : methods) { + if (method_set.count(method.method_name) > 0) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " " << method.method_name << " has been defined repeatly"; + } + method_set.emplace(method.method_name); + + size_t preprocess_outputs_count = 0; + size_t postprocess_outputs_count = 0; + + const auto &preprocess_name = method.preprocess_name; + if (!preprocess_name.empty()) { + auto preprocess = PreprocessStorage::Instance().GetPreprocess(preprocess_name); + if (preprocess == nullptr) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Model " << model_str << " method " << method.method_name + << " preprocess " << preprocess_name << " not defined"; + } + preprocess_outputs_count = preprocess->GetOutputsCount(preprocess_name); + + for (size_t i = 0; i < method.preprocess_inputs.size(); i++) { + auto &input = method.preprocess_inputs[i]; + if (input.first != kPredictPhaseTag_Input) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the preprocess " << i + << "th input phase tag " << input.first << " invalid"; + } + if (input.second >= method.inputs.size()) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the preprocess " << i + << "th input uses method " << input.second << "th input, that is greater than the method inputs size " + << method.inputs.size(); + } + } + } + + for (size_t i = 0; i < method.servable_inputs.size(); i++) { + auto &input = method.servable_inputs[i]; + if (input.first == kPredictPhaseTag_Input) { + if (input.second >= method.inputs.size()) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the servable " << i + << "th input uses method " << input.second << "th input, that is greater than the method inputs size " + << method.inputs.size(); + } + } else if (input.first == kPredictPhaseTag_Preproces) { + if (input.second >= preprocess_outputs_count) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the servable " << i + << "th input uses preprocess " << input.second + << "th output, that is greater than the preprocess outputs size " << preprocess_outputs_count; + } + } else { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the servable " << i + << "th input phase tag " << input.first << " invalid"; + } + } + + const auto &postprocess_name = method.postprocess_name; + if (!method.postprocess_name.empty()) { + auto postprocess = PostprocessStorage::Instance().GetPostprocess(postprocess_name); + if (postprocess == nullptr) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Model " << model_str << " method " << method.method_name + << " postprocess " << postprocess_name << " not defined"; + } + postprocess_outputs_count = postprocess->GetOutputsCount(postprocess_name); + + for (size_t i = 0; i < method.postprocess_inputs.size(); i++) { + auto &input = method.postprocess_inputs[i]; + if (input.first == kPredictPhaseTag_Input) { + if (input.second >= method.inputs.size()) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the postprocess " << i + << "th input uses method " << input.second + << "th input, that is greater than the method inputs size " << method.inputs.size(); + } + } else if (input.first == kPredictPhaseTag_Preproces) { + if (input.second >= preprocess_outputs_count) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the postprocess " << i + << "th input uses preprocess " << input.second + << "th output, that is greater than the preprocess outputs size " << preprocess_outputs_count; + } + } else if (input.first == kPredictPhaseTag_Predict) { + if (input.second >= servable_meta.outputs_count) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the postprocess " << i + << "th input uses servable " << input.second + << "th output, that is greater than the servable outputs size " << servable_meta.outputs_count; + } + } else { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the postprocess " << i + << "th input phase tag " << input.first << " invalid"; + } + } + } + for (size_t i = 0; i < method.returns.size(); i++) { + auto &input = method.returns[i]; + if (input.first == kPredictPhaseTag_Input) { + if (input.second >= method.inputs.size()) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the method " << i + << "th output uses method " << input.second << "th input, that is greater than the method inputs size " + << method.inputs.size(); + } + } else if (input.first == kPredictPhaseTag_Preproces) { + if (input.second >= preprocess_outputs_count) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the method " << i + << "th output uses preprocess " << input.second + << "th output, that is greater than the preprocess outputs size " << preprocess_outputs_count; + } + } else if (input.first == kPredictPhaseTag_Predict) { + if (input.second >= servable_meta.outputs_count) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the method " << i + << "th output uses servable " << input.second + << "th output, that is greater than the servable outputs size " << servable_meta.outputs_count; + } + } else if (input.first == kPredictPhaseTag_Postprocess) { + if (input.second >= postprocess_outputs_count) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the method " << i + << "th output uses postprocess " << input.second + << "th output, that is greater than the postprocess outputs size " << postprocess_outputs_count; + } + } else { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model " << model_str << " method " << method.method_name << ", the method " << i + << "th output phase tag " << input.first << " invalid"; + } + } + } + return SUCCESS; +} + +bool ServableSignature::GetMethodDeclare(const std::string &method_name, MethodSignature &method) { + auto item = + find_if(methods.begin(), methods.end(), [&](const MethodSignature &v) { return v.method_name == method_name; }); + if (item != methods.end()) { + method = *item; + return true; + } + return false; +} + +void ServableStorage::Register(const ServableSignature &def) { + auto model_name = def.servable_meta.servable_name; + if (servable_signatures_map_.find(model_name) == servable_signatures_map_.end()) { + MSI_LOG_WARNING << "Servable " << model_name << " has already been defined"; + } + servable_signatures_map_[model_name] = def; +} + +bool ServableStorage::GetServableDef(const std::string &model_name, ServableSignature &def) const { + auto it = servable_signatures_map_.find(model_name); + if (it == servable_signatures_map_.end()) { + return false; + } + def = it->second; + return true; +} + +std::shared_ptr ServableStorage::Instance() { + static std::shared_ptr storage; + if (storage == nullptr) { + storage = std::make_shared(); + } + return storage; +} + +void ServableStorage::RegisterMethod(const MethodSignature &method) { + MSI_LOG_INFO << "Declare method " << method.method_name << ", servable " << method.servable_name; + auto it = servable_signatures_map_.find(method.servable_name); + if (it == servable_signatures_map_.end()) { + ServableSignature signature; + signature.methods.push_back(method); + servable_signatures_map_[method.servable_name] = signature; + return; + } + it->second.methods.push_back(method); +} + +void ServableStorage::DeclareServable(const mindspore::serving::ServableMeta &servable) { + MSI_LOG_INFO << "Declare servable " << servable.servable_name; + auto it = servable_signatures_map_.find(servable.servable_name); + if (it == servable_signatures_map_.end()) { + ServableSignature signature; + signature.servable_meta = servable; + servable_signatures_map_[servable.servable_name] = signature; + return; + } + it->second.servable_meta = servable; +} + +void ServableStorage::RegisterInputOutputInfo(const std::string &servable_name, size_t inputs_count, + size_t outputs_count) { + auto it = servable_signatures_map_.find(servable_name); + if (it == servable_signatures_map_.end()) { + MSI_LOG_EXCEPTION << "RegisterInputOutputInfo failed, cannot find servable " << servable_name; + } + auto &servable_meta = it->second.servable_meta; + if (servable_meta.inputs_count != 0 && servable_meta.inputs_count != inputs_count) { + MSI_LOG_EXCEPTION << "RegisterInputOutputInfo failed, inputs count " << inputs_count << " not match old count " + << servable_meta.inputs_count << ",servable name " << servable_name; + } + if (servable_meta.outputs_count != 0 && servable_meta.outputs_count != outputs_count) { + MSI_LOG_EXCEPTION << "RegisterInputOutputInfo failed, outputs count " << outputs_count << " not match old count " + << servable_meta.outputs_count << ",servable name " << servable_name; + } + servable_meta.inputs_count = inputs_count; + servable_meta.outputs_count = outputs_count; +} + +std::vector ServableStorage::GetInputOutputInfo(const std::string &servable_name) const { + std::vector result; + auto it = servable_signatures_map_.find(servable_name); + if (it == servable_signatures_map_.end()) { + return result; + } + result.push_back(it->second.servable_meta.inputs_count); + result.push_back(it->second.servable_meta.outputs_count); + return result; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/common/servable.h b/mindspore_serving/ccsrc/common/servable.h new file mode 100644 index 0000000..6079a72 --- /dev/null +++ b/mindspore_serving/ccsrc/common/servable.h @@ -0,0 +1,122 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_SERVABLE_H +#define MINDSPORE_SERVING_SERVABLE_H + +#include +#include +#include +#include +#include +#include "common/serving_common.h" +#include "worker/inference/inference.h" + +namespace mindspore::serving { + +enum PredictPhaseTag { + kPredictPhaseTag_Input, + kPredictPhaseTag_Preproces, + kPredictPhaseTag_Predict, + kPredictPhaseTag_Postprocess, + kPredictPhaseTag_Output, +}; + +struct MethodSignature { + std::string method_name; + std::vector inputs; + std::vector outputs; + + std::string preprocess_name; + // the output index of the 'predict phase' + std::vector> preprocess_inputs; + + std::string postprocess_name; + std::vector> postprocess_inputs; + + std::string servable_name; + std::vector> servable_inputs; + + std::vector> returns; +}; + +struct LoadServableSpec { + std::string servable_directory; + std::string servable_name; + uint64_t version_number = 0; + std::string Repr() const; +}; + +struct WorkerMethodInfo { + std::string name; + std::vector input_names; +}; + +struct WorkerSpec { + std::string servable_name; + uint64_t version_number = 0; + std::string worker_address; + std::vector methods; + std::string Repr() const; +}; + +struct RequestSpec { + std::string servable_name; + std::string method_name; + uint64_t version_number = 0; // not specified + std::string Repr() const; +}; + +struct MS_API ServableMeta { + std::string servable_name; + std::string servable_file; // file name + ModelType model_format; // OM, MindIR + bool with_batch_dim = true; // whether there is batch dim in model's inputs/outputs + size_t inputs_count = 0; + size_t outputs_count = 0; + std::string Repr() const; + void SetModelFormat(const std::string &format); +}; + +struct ServableSignature { + ServableMeta servable_meta; + std::vector methods; + + Status Check() const; + bool GetMethodDeclare(const std::string &method_name, MethodSignature &method); +}; + +class MS_API ServableStorage { + public: + void Register(const ServableSignature &def); + void RegisterMethod(const MethodSignature &method); + + bool GetServableDef(const std::string &model_name, ServableSignature &def) const; + + void DeclareServable(const ServableMeta &servable); + + void RegisterInputOutputInfo(const std::string &servable_name, size_t inputs_count, size_t outputs_count); + std::vector GetInputOutputInfo(const std::string &servable_name) const; + + static std::shared_ptr Instance(); + + private: + std::unordered_map servable_signatures_map_; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_SERVABLE_H diff --git a/mindspore_serving/ccsrc/common/serving_common.h b/mindspore_serving/ccsrc/common/serving_common.h new file mode 100644 index 0000000..d9d32c4 --- /dev/null +++ b/mindspore_serving/ccsrc/common/serving_common.h @@ -0,0 +1,26 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_COMMON_H +#define MINDSPORE_SERVING_COMMON_H + +#include + +#include "common/status.h" +#include "common/log.h" +#include "common/tensor.h" + +#endif // MINDSPORE_SERVING_COMMON_H diff --git a/mindspore_serving/ccsrc/common/status.h b/mindspore_serving/ccsrc/common/status.h new file mode 100644 index 0000000..d2556e2 --- /dev/null +++ b/mindspore_serving/ccsrc/common/status.h @@ -0,0 +1,75 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_INC_STATUS_H +#define MINDSPORE_SERVING_INC_STATUS_H + +#include +#include +#include + +#include "log.h" + +namespace mindspore::serving { + +enum StatusCode { SUCCESS = 0, FAILED, INVALID_INPUTS, SYSTEM_ERROR }; + +class Status { + public: + Status() : status_code_(FAILED) {} + Status(enum StatusCode status_code, const std::string &status_msg = "") + : status_code_(status_code), status_msg_(status_msg) {} + bool IsSuccess() const { return status_code_ == SUCCESS; } + enum StatusCode StatusCode() const { return status_code_; } + std::string StatusMessage() const { return status_msg_; } + bool operator==(const Status &other) const { return status_code_ == other.status_code_; } + bool operator==(enum StatusCode other_code) const { return status_code_ == other_code; } + bool operator!=(const Status &other) const { return status_code_ != other.status_code_; } + bool operator!=(enum StatusCode other_code) const { return status_code_ != other_code; } + operator bool() const = delete; + Status &operator<(const LogStream &stream) noexcept __attribute__((visibility("default"))) { + status_msg_ = stream.sstream_->str(); + return *this; + } + Status &operator=(const std::string &msg) noexcept __attribute__((visibility("default"))) { + status_msg_ = msg; + return *this; + } + + private: + enum StatusCode status_code_; + std::string status_msg_; +}; + +#define MSI_TIME_STAMP_START(name) auto time_start_##name = std::chrono::steady_clock::now(); +#define MSI_TIME_STAMP_END(name) \ + { \ + auto time_end_##name = std::chrono::steady_clock::now(); \ + auto time_cost = std::chrono::duration(time_end_##name - time_start_##name).count(); \ + MSI_LOG_INFO << #name " Time Cost # " << time_cost << " ms ---------------------"; \ + } + +#define INFER_STATUS(code) mindspore::serving::Status(code) < mindspore::serving::LogStream() +#define ERROR_INFER_STATUS(status, type, msg) \ + MSI_LOG_ERROR << msg; \ + status = mindspore::serving::Status(type, msg) + +#define INFER_STATUS_LOG_ERROR(code) mindspore::serving::Status(code) = MSI_LOG_ERROR +#define INFER_STATUS_LOG_WARNING(code) mindspore::serving::Status(code) = MSI_LOG_WARNING + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_INC_STATUS_H diff --git a/mindspore_serving/ccsrc/common/tensor.cc b/mindspore_serving/ccsrc/common/tensor.cc new file mode 100644 index 0000000..0587e4a --- /dev/null +++ b/mindspore_serving/ccsrc/common/tensor.cc @@ -0,0 +1,113 @@ +#include "tensor.h" +#include +#include "log.h" +#include "securec.h" + +namespace mindspore::serving { + +Tensor::Tensor() = default; + +Tensor::Tensor(DataType type, std::vector shape, const void *data, size_t data_len) { + set_data_type(type); + set_shape(shape); + set_data(data, data_len); +} + +const uint8_t *Tensor::data() const { + if (data_size() == 0) { + return nullptr; + } + return data_.data(); +} + +size_t Tensor::data_size() const { return data_.size(); } + +bool Tensor::resize_data(size_t data_len) { + data_.resize(data_len); + return true; +} + +uint8_t *Tensor::mutable_data() { + if (data_size() == 0) { + return nullptr; + } + return data_.data(); +} + +// For kMSI_String and kMSI_Bytes +void Tensor::clear_bytes_data() { bytes_.clear(); } + +void Tensor::add_bytes_data(const uint8_t *data, size_t bytes_len) { + std::vector bytes(bytes_len); + memcpy_s(bytes.data(), bytes.size(), data, bytes_len); + bytes_.push_back(std::move(bytes)); +} + +size_t Tensor::bytes_data_size() const { return bytes_.size(); } + +void Tensor::get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const { + bytes_len = bytes_[index].size(); + if (bytes_len == 0) { + data = nullptr; + } else { + data = bytes_[index].data(); + } +} + +TensorBase *VectorTensorWrapReply::operator[](size_t index) { + if (index >= tensor_list_.size()) { + MSI_LOG_EXCEPTION << "visit invalid index " << index << " total size " << tensor_list_.size(); + } + return &(tensor_list_[index]); +} + +const TensorBase *VectorTensorWrapReply::operator[](size_t index) const { + if (index >= tensor_list_.size()) { + MSI_LOG_EXCEPTION << "visit invalid index " << index << " total size " << tensor_list_.size(); + } + return &(tensor_list_[index]); +} + +TensorBase *VectorTensorWrapReply::add() { + tensor_list_.push_back(Tensor()); + return &(tensor_list_.back()); +} + +const TensorBase *VectorTensorWrapRequest::operator[](size_t index) const { + if (index >= tensor_list_.size()) { + MSI_LOG_EXCEPTION << "visit invalid index " << index << " total size " << tensor_list_.size(); + } + return &(tensor_list_[index]); +} + +TensorBase *VectorTensorPtrWrapReply::operator[](size_t index) { + if (index >= tensor_list_.size()) { + MSI_LOG_EXCEPTION << "visit invalid index " << index << " total size " << tensor_list_.size(); + } + return tensor_list_[index].get(); +} + +const TensorBase *VectorTensorPtrWrapReply::operator[](size_t index) const { + if (index >= tensor_list_.size()) { + MSI_LOG_EXCEPTION << "visit invalid index " << index << " total size " << tensor_list_.size(); + } + return tensor_list_[index].get(); +} + +TensorBase *VectorTensorPtrWrapReply::add() { + auto tensor = tensor_create_fun_(); + if (tensor == nullptr) { + MSI_LOG_EXCEPTION << "create tensor failed"; + } + tensor_list_.push_back(tensor); + return tensor.get(); +} + +const TensorBase *VectorTensorPtrWrapRequest::operator[](size_t index) const { + if (index >= tensor_list_.size()) { + MSI_LOG_EXCEPTION << "visit invalid index " << index << " total size " << tensor_list_.size(); + } + return tensor_list_[index].get(); +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/common/tensor.h b/mindspore_serving/ccsrc/common/tensor.h new file mode 100644 index 0000000..ab83b27 --- /dev/null +++ b/mindspore_serving/ccsrc/common/tensor.h @@ -0,0 +1,119 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_INC_TENSOR_IMP_H +#define MINDSPORE_SERVING_INC_TENSOR_IMP_H + +#include +#include "tensor_base.h" + +namespace mindspore::serving { + +class MS_API Tensor : public TensorBase { + public: + Tensor(); + Tensor(DataType type, std::vector shape, const void *data, size_t data_len); + ~Tensor() = default; + + void set_data_type(DataType type) override { type_ = type; } + DataType data_type() const override { return type_; } + + void set_shape(const std::vector &shape) override { shape_ = shape; } + std::vector shape() const override { return shape_; } + + const uint8_t *data() const override; + size_t data_size() const override; + + bool resize_data(size_t data_len) override; + uint8_t *mutable_data() override; + + // For kMSI_String and kMSI_Bytes + void clear_bytes_data() override; + void add_bytes_data(const uint8_t *data, size_t bytes_len) override; + size_t bytes_data_size() const override; + void get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const override; + + private: + DataType type_; + std::vector shape_; + std::vector data_; + // For kMSI_String and kMSI_Bytes + std::vector> bytes_; +}; + +class MS_API VectorTensorWrapReply : public ReplyBase { + public: + explicit VectorTensorWrapReply(std::vector &tensor_list) : tensor_list_(tensor_list) {} + ~VectorTensorWrapReply() = default; + + size_t size() const override { return tensor_list_.size(); } + TensorBase *operator[](size_t index) override; + const TensorBase *operator[](size_t index) const override; + TensorBase *add() override; + void clear() override { tensor_list_.clear(); } + + private: + std::vector &tensor_list_; +}; + +class MS_API VectorTensorWrapRequest : public RequestBase { + public: + explicit VectorTensorWrapRequest(const std::vector &tensor_list) : tensor_list_(tensor_list) {} + ~VectorTensorWrapRequest() = default; + + size_t size() const override { return tensor_list_.size(); } + const TensorBase *operator[](size_t index) const override; + + private: + const std::vector &tensor_list_; +}; + +class MS_API VectorTensorPtrWrapReply : public ReplyBase { + public: + explicit VectorTensorPtrWrapReply(std::vector &tensor_list, std::function create_fun) + : tensor_list_(tensor_list), tensor_create_fun_(create_fun) { + if (tensor_create_fun_ == nullptr) { + MSI_LOG_EXCEPTION << "tensor create function cannot be nullptr"; + } + } + ~VectorTensorPtrWrapReply() = default; + + size_t size() const override { return tensor_list_.size(); } + TensorBase *operator[](size_t index) override; + const TensorBase *operator[](size_t index) const override; + TensorBase *add() override; + void clear() override { tensor_list_.clear(); } + + private: + std::vector &tensor_list_; + std::function tensor_create_fun_; +}; + +class MS_API VectorTensorPtrWrapRequest : public RequestBase { + public: + explicit VectorTensorPtrWrapRequest(const std::vector &tensor_list) : tensor_list_(tensor_list) {} + ~VectorTensorPtrWrapRequest() = default; + + size_t size() const override { return tensor_list_.size(); } + const TensorBase *operator[](size_t index) const override; + + private: + std::vector tensor_list_; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_INC_TENSOR_IMP_H diff --git a/mindspore_serving/ccsrc/common/tensor_base.cc b/mindspore_serving/ccsrc/common/tensor_base.cc new file mode 100644 index 0000000..2564ca8 --- /dev/null +++ b/mindspore_serving/ccsrc/common/tensor_base.cc @@ -0,0 +1,147 @@ +#include "tensor_base.h" +#include +#include "log.h" +#include "securec.h" + +#define TENSOR_MAX_ELEMENT_COUNT UINT32_MAX + +namespace mindspore::serving { + +bool TensorBase::set_data(const void *data, size_t data_len) { + resize_data(data_len); + if (mutable_data() == nullptr) { + MSI_LOG_ERROR << "set data failed, data len " << data_len; + return false; + } + if (data_size() != data_len) { + MSI_LOG_ERROR << "set data failed, tensor current data size " << data_size() << " not match data len " << data_len; + return false; + } + if (data_len == 0) { + return true; + } + memcpy_s(mutable_data(), data_size(), data, data_len); + return true; +} + +size_t TensorBase::itemsize() const { return GetTypeSize(data_type()); } + +size_t TensorBase::element_cnt() const { + size_t element_num = 1; + for (auto dim : shape()) { + if (dim <= 0 || TENSOR_MAX_ELEMENT_COUNT / static_cast(dim) < element_num) { + return 0; + } + element_num *= dim; + } + return element_num; +} + +size_t TensorBase::GetTypeSize(DataType type) { + const std::map type_size_map{ + {kMSI_Bool, sizeof(bool)}, {kMSI_Float64, sizeof(double)}, {kMSI_Int8, sizeof(int8_t)}, + {kMSI_Uint8, sizeof(uint8_t)}, {kMSI_Int16, sizeof(int16_t)}, {kMSI_Uint16, sizeof(uint16_t)}, + {kMSI_Int32, sizeof(int32_t)}, {kMSI_Uint32, sizeof(uint32_t)}, {kMSI_Int64, sizeof(int64_t)}, + {kMSI_Uint64, sizeof(uint64_t)}, {kMSI_Float16, sizeof(uint16_t)}, {kMSI_Float32, sizeof(float)}, + }; + auto it = type_size_map.find(type); + if (it != type_size_map.end()) { + return it->second; + } + return 0; +} + +void TensorBase::assgin(const TensorBase &other) { + if (is_bytes_val_data()) { + clear_bytes_data(); + } + set_shape(other.shape()); + set_data_type(other.data_type()); + if (other.is_bytes_val_data()) { + for (size_t i = 0; i < other.bytes_data_size(); i++) { + const uint8_t *data; + size_t data_len; + other.get_bytes_data(i, data, data_len); + add_bytes_data(data, data_len); + } + } else { + set_data(other.data(), other.data_size()); + } +} +Status TensorBase::concat(const std::vector &inputs) { + if (inputs.empty()) { + MSI_LOG_ERROR << "inputs is empty"; + return FAILED; + } + if (is_bytes_val_data()) { + clear_bytes_data(); + } + auto &input0 = inputs[0]; + std::vector new_shape = input0->shape(); + new_shape.insert(new_shape.begin(), static_cast(inputs.size())); + set_shape(new_shape); + set_data_type(input0->data_type()); + if (input0->is_bytes_val_data()) { + if (input0->bytes_data_size() != 1) { + MSI_LOG_ERROR << "input 0 bytes data batch size " << input0->bytes_data_size() << " is not 1"; + return FAILED; + } + const uint8_t *data; + size_t data_len; + input0->get_bytes_data(0, data, data_len); + add_bytes_data(data, data_len); + } else { + resize_data(input0->data_size() * inputs.size()); + memcpy_s(mutable_data(), data_size(), input0->data(), input0->data_size()); + } + for (size_t i = 1; i < inputs.size(); i++) { + auto &other = inputs[i]; + if (input0->data_type() != other->data_type()) { + MSI_LOG_ERROR << "input " << i << " data type " << other->data_type() << " not match input 0 " + << input0->data_type(); + return FAILED; + } + if (input0->shape() != other->shape()) { + MSI_LOG_ERROR << "input " << i << " shape " << other->shape() << " not match input 0 " << input0->shape(); + return FAILED; + } + if (input0->is_bytes_val_data()) { + if (other->bytes_data_size() != 1) { + MSI_LOG_ERROR << "input " << i << " bytes data batch size " << other->bytes_data_size() << " is not 1"; + return FAILED; + } + const uint8_t *data = nullptr; + size_t data_len = 0; + other->get_bytes_data(0, data, data_len); + add_bytes_data(data, data_len); + } else { + if (input0->data_size() != other->data_size()) { + MSI_LOG_ERROR << "input " << i << " data size " << other->data_size() << " not match input 0 " + << input0->data_size(); + return FAILED; + } + memcpy_s(mutable_data() + input0->data_size() * i, data_size() - input0->data_size() * i, other->data(), + other->data_size()); + } + } + return SUCCESS; +} + +LogStream &operator<<(LogStream &stream, DataType data_type) { + const std::map type_name_map{ + {kMSI_Unknown, "kMSI_Unknown"}, {kMSI_Bool, "kMSI_Bool"}, {kMSI_Int8, "kMSI_Int8"}, + {kMSI_Uint8, "kMSI_Uint8"}, {kMSI_Int16, "kMSI_Int16"}, {kMSI_Uint16, "kMSI_Uint16"}, + {kMSI_Int32, "kMSI_Int32"}, {kMSI_Uint32, "kMSI_Uint32"}, {kMSI_Int64, "kMSI_Int64"}, + {kMSI_Uint64, "kMSI_Uint64"}, {kMSI_Float16, "kMSI_Float16"}, {kMSI_Float32, "kMSI_Float32"}, + {kMSI_Float64, "kMSI_Float64"}, {kMSI_Bytes, "kMSI_Bytes"}, {kMSI_String, "kMSI_String"}, + }; + auto it = type_name_map.find(data_type); + if (it != type_name_map.end()) { + stream << it->second; + } else { + stream << "kMSI_Unknown"; + } + return stream; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/common/tensor_base.h b/mindspore_serving/ccsrc/common/tensor_base.h new file mode 100644 index 0000000..e6c5641 --- /dev/null +++ b/mindspore_serving/ccsrc/common/tensor_base.h @@ -0,0 +1,116 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_INC_TENSOR_H +#define MINDSPORE_SERVING_INC_TENSOR_H + +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "status.h" + +namespace mindspore { +namespace serving { + +enum DataType { + kMSI_Unknown = 0, + kMSI_Bool = 1, + kMSI_Int8 = 2, + kMSI_Int16 = 3, + kMSI_Int32 = 4, + kMSI_Int64 = 5, + kMSI_Uint8 = 6, + kMSI_Uint16 = 7, + kMSI_Uint32 = 8, + kMSI_Uint64 = 9, + kMSI_Float16 = 10, + kMSI_Float32 = 11, + kMSI_Float64 = 12, + kMSI_String = 13, // for model STRING input + kMSI_Bytes = 14, // for image etc. +}; + +class TensorBase; +using TensorBasePtr = std::shared_ptr; + +class MS_API TensorBase { + public: + TensorBase() = default; + virtual ~TensorBase() = default; + + // For all data type + virtual std::vector shape() const = 0; + virtual void set_shape(const std::vector &shape) = 0; + virtual DataType data_type() const = 0; + virtual void set_data_type(DataType type) = 0; + + // All the following interfaces are not for kMSI_String and kMSI_Bytes + virtual const uint8_t *data() const = 0; + virtual size_t data_size() const = 0; + virtual bool resize_data(size_t data_len) = 0; + virtual uint8_t *mutable_data() = 0; + + // Byte size of a single element. + size_t itemsize() const; + // Total number of elements. + size_t element_cnt() const; + // resize and copy data + bool set_data(const void *data, size_t data_len); + static size_t GetTypeSize(DataType type); + + // For kMSI_String and kMSI_Bytes + virtual void clear_bytes_data() = 0; + virtual void add_bytes_data(const uint8_t *data, size_t bytes_len) = 0; + virtual size_t bytes_data_size() const = 0; + virtual void get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const = 0; + + // TensorBase(const TensorBase& other) = delete; + // TensorBase& operator=(const TensorBase& other) = delete; + void assgin(const TensorBase &other); + Status concat(const std::vector &inputs); + bool is_bytes_val_data() const { return data_type() == kMSI_Bytes || data_type() == kMSI_String; } +}; + +class RequestBase { + public: + RequestBase() = default; + virtual ~RequestBase() = default; + virtual size_t size() const = 0; + virtual const TensorBase *operator[](size_t index) const = 0; +}; + +class ReplyBase { + public: + ReplyBase() = default; + virtual ~ReplyBase() = default; + virtual size_t size() const = 0; + virtual TensorBase *operator[](size_t index) = 0; + virtual const TensorBase *operator[](size_t index) const = 0; + virtual TensorBase *add() = 0; + virtual void clear() = 0; +}; + +extern MS_API LogStream &operator<<(LogStream &stream, DataType data_type); + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_INC_TENSOR_H diff --git a/mindspore_serving/ccsrc/common/thread_pool.cc b/mindspore_serving/ccsrc/common/thread_pool.cc new file mode 100644 index 0000000..d86da22 --- /dev/null +++ b/mindspore_serving/ccsrc/common/thread_pool.cc @@ -0,0 +1,69 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "common/thread_pool.h" +#include +#include +#include +#include +#include +#include + +namespace mindspore::serving { + +ThreadPool::ThreadPool(uint32_t size) : is_stoped_(false), idle_thrd_num_(size < 1 ? 1 : size) { + for (uint32_t i = 0; i < idle_thrd_num_; ++i) { + pool_.emplace_back(ThreadFunc, this); + } +} + +ThreadPool::~ThreadPool() { + is_stoped_.store(true); + cond_var_.notify_all(); + + for (std::thread &thd : pool_) { + if (thd.joinable()) { + try { + thd.join(); + } catch (const std::system_error &) { + } catch (...) { + } + } + } +} + +void ThreadPool::ThreadFunc(ThreadPool *thread_pool) { + if (thread_pool == nullptr) { + return; + } + while (!thread_pool->is_stoped_) { + std::function task; + { + std::unique_lock lock{thread_pool->m_lock_}; + thread_pool->cond_var_.wait( + lock, [thread_pool] { return thread_pool->is_stoped_.load() || !thread_pool->tasks_.empty(); }); + if (thread_pool->is_stoped_ && thread_pool->tasks_.empty()) { + return; + } + task = std::move(thread_pool->tasks_.front()); + thread_pool->tasks_.pop(); + } + --thread_pool->idle_thrd_num_; + task(); + ++thread_pool->idle_thrd_num_; + } +} +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/common/thread_pool.h b/mindspore_serving/ccsrc/common/thread_pool.h new file mode 100644 index 0000000..a63d078 --- /dev/null +++ b/mindspore_serving/ccsrc/common/thread_pool.h @@ -0,0 +1,74 @@ +/** + * Copyright 2019-2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MS_SERVING_UTILS_THREAD_POOL_H_ +#define MS_SERVING_UTILS_THREAD_POOL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mindspore::serving { +using ThreadTask = std::function; + +class ThreadPool { + public: + explicit ThreadPool(uint32_t size = 4); + + ~ThreadPool(); + + template + auto commit(Func &&func, Args &&... args) -> std::future { + using retType = decltype(func(args...)); + std::future fail_future; + if (is_stoped_.load()) { + return fail_future; + } + + auto bindFunc = std::bind(std::forward(func), std::forward(args)...); + auto task = std::make_shared>(bindFunc); + if (task == nullptr) { + return fail_future; + } + std::future future = task->get_future(); + { + std::lock_guard lock{m_lock_}; + tasks_.emplace([task]() { (*task)(); }); + } + cond_var_.notify_one(); + return future; + } + + static void ThreadFunc(ThreadPool *thread_pool); + + private: + std::vector pool_; + std::queue tasks_; + std::mutex m_lock_; + std::condition_variable cond_var_; + std::atomic is_stoped_; + std::atomic idle_thrd_num_; +}; +} // namespace mindspore::serving + +#endif // MS_SERVING_UTILS_THREAD_POOL_H_ diff --git a/mindspore_serving/ccsrc/master/dispacther.cc b/mindspore_serving/ccsrc/master/dispacther.cc new file mode 100644 index 0000000..e3e4737 --- /dev/null +++ b/mindspore_serving/ccsrc/master/dispacther.cc @@ -0,0 +1,346 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "master/dispacther.h" +#include "worker/worker.h" +#include "common/proto_tensor.h" + +namespace mindspore::serving { + +Dispatcher::Dispatcher() {} + +Dispatcher::~Dispatcher() { Clear(); } + +DispatcherWorkerContext Dispatcher::GetWorkSession(const RequestSpec &request_spec) const { + Status status; + DispatcherWorkerContext context; + auto it = servable_map_.find(request_spec.servable_name); + if (it == servable_map_.end()) { + return context; + } + if (request_spec.version_number > 0) { + auto item = find_if(it->second.begin(), it->second.end(), [&](const DispatcherWorkerContext &v) { + return v.worker_spec.version_number == request_spec.version_number; + }); + if (item != it->second.end()) { + context.worker_spec = item->worker_spec; + context.stub_ = item->stub_; + context.worker_running_in_master = item->worker_running_in_master; + } + return context; + } + uint64_t max_version_number = 0; + for (const auto &item : it->second) { + if (max_version_number < item.worker_spec.version_number) { + context.worker_spec = item.worker_spec; + context.stub_ = item.stub_; + context.worker_running_in_master = item.worker_running_in_master; + max_version_number = item.worker_spec.version_number; + } + } + return context; +} + +Status Dispatcher::Dispatch(const proto::PredictRequest &request, proto::PredictReply &reply) { + std::shared_lock lock(servable_shared_lock_); + RequestSpec request_spec; + GrpcTensorHelper::GetRequestSpec(request, request_spec); + auto worker = GetWorkSession(request_spec); + if (!worker.stub_ && !worker.worker_running_in_master) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Request " << request_spec.Repr() << ", servable is not available"; + } + bool find_method = + std::any_of(worker.worker_spec.methods.begin(), worker.worker_spec.methods.end(), + [&](const WorkerMethodInfo &method) { return method.name == request_spec.method_name; }); + if (!find_method) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Request " << request_spec.Repr() << ", method is not available"; + } + /// TODO spec request version_number + if (worker.stub_ != nullptr) { + grpc::ClientContext context; + auto status = worker.stub_->Predict(&context, request, &reply); + if (!status.ok()) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Predict failed, worker gRPC error: " << status.error_code() << ", " << status.error_message(); + } + } else { + return Worker::GetInstance().Run(request, reply); + } + return SUCCESS; +} + +Status Dispatcher::RegisterServable(const proto::RegisterRequest &request, proto::RegisterReply &reply) { + std::unique_lock lock(servable_shared_lock_); + std::vector worker_specs; + GrpcTensorHelper::GetWorkerSpec(request, worker_specs); + if (worker_specs.empty()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Register failed, servable cannot be empty"; + } + for (auto &worker_spec : worker_specs) { + if (worker_spec.servable_name.empty()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Register failed, servable name cannot be empty"; + } + if (worker_spec.version_number <= 0) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Register failed, servable name " << worker_spec.servable_name + << " version number " << worker_spec.version_number << " cannot be 0"; + } + auto target_str = request.address(); + auto it = servable_map_.find(worker_spec.servable_name); + + std::shared_ptr channel = grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()); + + bool find_registered = false; + if (it != servable_map_.end()) { + std::shared_ptr worker; + auto item = find_if(it->second.begin(), it->second.end(), [&](const DispatcherWorkerContext &v) { + return v.worker_spec.version_number == worker_spec.version_number && + v.worker_spec.worker_address == worker_spec.worker_address; + }); + if (item != it->second.end()) { + MSI_LOG_WARNING << "Servable " << worker_spec.servable_name << " version " << worker_spec.version_number + << " has been registered, old registered info will be replaced"; + item->worker_spec = worker_spec; + item->stub_ = proto::MSWorker::NewStub(channel); + find_registered = true; + } + } + if (!find_registered) { + DispatcherWorkerContext context; + context.worker_spec = worker_spec; + context.stub_ = proto::MSWorker::NewStub(channel); + servable_map_[worker_spec.servable_name].push_back(context); + } + } + return SUCCESS; +} + +Status Dispatcher::UnregisterServable(const proto::ExitRequest &request, proto::ExitReply &reply) { + if (clearing_flag) { + return SUCCESS; + } + std::unique_lock lock(servable_shared_lock_); + auto target_str = request.address(); + Status status; + for (auto iter = servable_map_.begin(); iter != servable_map_.end();) { + for (auto it = iter->second.begin(); it != iter->second.end();) { + if (target_str == it->worker_spec.worker_address) { + it = iter->second.erase(it); + } else { + ++it; + } + } + if (iter->second.size() == 0) { + iter = servable_map_.erase(iter); + } else { + ++iter; + } + } + return SUCCESS; +} +Status Dispatcher::AddServable(const proto::AddWorkerRequest &request, proto::AddWorkerReply &reply) { + std::unique_lock lock(servable_shared_lock_); + WorkerSpec worker_spec; + GrpcTensorHelper::GetWorkerSpec(request, worker_spec); + auto target_str = request.address(); + if (worker_spec.servable_name.empty()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "AddServable failed, servable name cannot be empty"; + } + if (worker_spec.version_number <= 0) { + return INFER_STATUS_LOG_ERROR(FAILED) << "AddServable failed, servable name " << worker_spec.servable_name + << " version number " << worker_spec.version_number << " cannot be 0"; + } + Status status; + auto it = servable_map_.find(worker_spec.servable_name); + if (it != servable_map_.end()) { + bool find = std::any_of(it->second.begin(), it->second.end(), [&](const DispatcherWorkerContext &item) { + return item.worker_spec.version_number == worker_spec.version_number && + item.worker_spec.worker_address == worker_spec.worker_address; + }); + if (find) { + MSI_LOG_WARNING << "Servable " << worker_spec.servable_name << " version " << worker_spec.version_number + << " has been registered"; + return SUCCESS; + } + } + DispatcherWorkerContext context; + context.worker_spec = worker_spec; + std::shared_ptr channel = grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()); + context.stub_ = proto::MSWorker::NewStub(channel); + servable_map_[worker_spec.servable_name].push_back(context); + return SUCCESS; +} + +Status Dispatcher::RemoveServable(const proto::RemoveWorkerRequest &request, proto::RemoveWorkerReply &reply) { + std::unique_lock lock(servable_shared_lock_); + WorkerSpec worker_spec; + GrpcTensorHelper::GetWorkerSpec(request, worker_spec); + auto target_str = request.address(); + Status status; + for (auto iter = servable_map_.begin(); iter != servable_map_.end();) { + for (auto it = iter->second.begin(); it != iter->second.end();) { + if (target_str == it->worker_spec.worker_address && it->worker_spec.servable_name == worker_spec.servable_name && + it->worker_spec.version_number == worker_spec.version_number) { + it = iter->second.erase(it); + } else { + ++it; + } + } + if (iter->second.size() == 0) { + iter = servable_map_.erase(iter); + } else { + ++iter; + } + } + return SUCCESS; +} + +void Dispatcher::Clear() { + std::unique_lock lock(servable_shared_lock_); + clearing_flag = true; + + for (auto iter = servable_map_.begin(); iter != servable_map_.end(); ++iter) { + for (auto it = iter->second.begin(); it != iter->second.end(); ++it) { + proto::ExitRequest request; + request.set_address(it->worker_spec.worker_address); + proto::ExitReply reply; + grpc::ClientContext context; + const int32_t TIME_OUT = 1; + std::chrono::system_clock::time_point deadline = + std::chrono::system_clock::now() + std::chrono::seconds(TIME_OUT); + context.set_deadline(deadline); + if (it->stub_) { + (void)it->stub_->Exit(&context, request, &reply); + } else { + Worker::GetInstance().Clear(); + } + } + } + servable_map_.clear(); +} + +Status Dispatcher::RegisterLocalServable(const std::vector &worker_specs) { + std::unique_lock lock(servable_shared_lock_); + if (worker_specs.empty()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Register failed, servable cannot be empty"; + } + for (auto &worker_spec : worker_specs) { + if (worker_spec.servable_name.empty()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Register failed, servable name cannot be empty"; + } + if (worker_spec.version_number <= 0) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Register failed, servable name " << worker_spec.servable_name + << " version number " << worker_spec.version_number << " cannot be 0"; + } + auto it = servable_map_.find(worker_spec.servable_name); + + bool find_registered = false; + if (it != servable_map_.end()) { + std::shared_ptr worker; + auto item = find_if(it->second.begin(), it->second.end(), [&](const DispatcherWorkerContext &v) { + return v.worker_spec.version_number == worker_spec.version_number && + v.worker_spec.worker_address == worker_spec.worker_address; + }); + if (item != it->second.end()) { + MSI_LOG_WARNING << "Servable " << worker_spec.servable_name << " version " << worker_spec.version_number + << " has been registered, old registered info will be replaced"; + item->worker_spec = worker_spec; + find_registered = true; + } + } + if (!find_registered) { + DispatcherWorkerContext context; + context.worker_spec = worker_spec; + context.worker_running_in_master = true; + servable_map_[worker_spec.servable_name].push_back(context); + } + } + return SUCCESS; +} + +Status Dispatcher::UnregisterLocalServable() { + if (clearing_flag) { + return SUCCESS; + } + std::unique_lock lock(servable_shared_lock_); + Status status; + for (auto iter = servable_map_.begin(); iter != servable_map_.end();) { + for (auto it = iter->second.begin(); it != iter->second.end();) { + if (it->worker_running_in_master) { + it = iter->second.erase(it); + } else { + ++it; + } + } + if (iter->second.size() == 0) { + iter = servable_map_.erase(iter); + } else { + ++iter; + } + } + return SUCCESS; +} + +Status Dispatcher::AddLocalServable(const WorkerSpec &worker_spec) { + std::unique_lock lock(servable_shared_lock_); + if (worker_spec.servable_name.empty()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "AddServable failed, servable name cannot be empty"; + } + if (worker_spec.version_number <= 0) { + return INFER_STATUS_LOG_ERROR(FAILED) << "AddServable failed, servable name " << worker_spec.servable_name + << " version number " << worker_spec.version_number << " cannot be 0"; + } + Status status; + auto it = servable_map_.find(worker_spec.servable_name); + if (it != servable_map_.end()) { + bool find = std::any_of(it->second.begin(), it->second.end(), [&](const DispatcherWorkerContext &item) { + return item.worker_spec.version_number == worker_spec.version_number && + item.worker_spec.worker_address == worker_spec.worker_address; + }); + if (find) { + MSI_LOG_WARNING << "Servable " << worker_spec.servable_name << " version " << worker_spec.version_number + << " has been registered"; + return SUCCESS; + } + } + DispatcherWorkerContext context; + context.worker_spec = worker_spec; + context.worker_running_in_master = true; + servable_map_[worker_spec.servable_name].push_back(context); + return SUCCESS; +} + +Status Dispatcher::RemoveLocalServable(const WorkerSpec &worker_spec) { + std::unique_lock lock(servable_shared_lock_); + Status status; + for (auto iter = servable_map_.begin(); iter != servable_map_.end();) { + for (auto it = iter->second.begin(); it != iter->second.end();) { + if (it->worker_running_in_master && it->worker_spec.servable_name == worker_spec.servable_name && + it->worker_spec.version_number == worker_spec.version_number) { + it = iter->second.erase(it); + } else { + ++it; + } + } + if (iter->second.size() == 0) { + iter = servable_map_.erase(iter); + } else { + ++iter; + } + } + return SUCCESS; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/master/dispacther.h b/mindspore_serving/ccsrc/master/dispacther.h new file mode 100644 index 0000000..b80f598 --- /dev/null +++ b/mindspore_serving/ccsrc/master/dispacther.h @@ -0,0 +1,71 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_SERVER_DISPACTHER_H +#define MINDSPORE_SERVING_SERVER_DISPACTHER_H + +#include +#include +#include +#include +#include +#include "proto/ms_worker.grpc.pb.h" +#include "common/serving_common.h" +#include "common/instance.h" +#include "common/servable.h" +#include "worker/worker.h" + +namespace mindspore::serving { + +using DispatchCallback = std::function &outputs)>; + +struct DispatcherWorkerContext { + WorkerSpec worker_spec; + std::shared_ptr stub_ = nullptr; + bool worker_running_in_master = false; +}; + +class MS_API Dispatcher { + public: + Dispatcher(); + ~Dispatcher(); + Status Dispatch(const proto::PredictRequest &request, proto::PredictReply &reply); + + Status RegisterServable(const proto::RegisterRequest &request, proto::RegisterReply &reply); + Status UnregisterServable(const proto::ExitRequest &request, proto::ExitReply &reply); + + Status AddServable(const proto::AddWorkerRequest &request, proto::AddWorkerReply &reply); + Status RemoveServable(const proto::RemoveWorkerRequest &request, proto::RemoveWorkerReply &reply); + + void Clear(); + + Status RegisterLocalServable(const std::vector &worker_specs); + Status UnregisterLocalServable(); + Status AddLocalServable(const WorkerSpec &worker_spec); + Status RemoveLocalServable(const WorkerSpec &worker_spec); + + private: + std::unordered_map> servable_map_{}; + std::shared_mutex servable_shared_lock_; + // avoid invoke Clear and then UnregisterServable is invoked by Clear in other thread + std::atomic_bool clearing_flag = false; + + DispatcherWorkerContext GetWorkSession(const RequestSpec &request_spec) const; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_SERVER_DISPACTHER_H diff --git a/mindspore_serving/ccsrc/master/grpc/grpc_process.cc b/mindspore_serving/ccsrc/master/grpc/grpc_process.cc new file mode 100644 index 0000000..760815f --- /dev/null +++ b/mindspore_serving/ccsrc/master/grpc/grpc_process.cc @@ -0,0 +1,153 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "master/grpc/grpc_process.h" +#include "master/dispacther.h" + +namespace mindspore { +namespace serving { + +namespace { +std::string GetProtorWorkerSpecRepr(const proto::WorkerSpec &worker_spec) { + std::stringstream str; + str << "{name:" << worker_spec.name() << ", version:" << worker_spec.version_number() << ", method:["; + for (int k = 0; k < worker_spec.methods_size(); k++) { + str << worker_spec.methods(k).name(); + if (k + 1 < worker_spec.methods_size()) { + str << ","; + } + } + str << "]}"; + return str.str(); +} +} // namespace + +grpc::Status MSServiceImpl::Predict(grpc::ServerContext *context, const proto::PredictRequest *request, + proto::PredictReply *reply) { + Status status(FAILED); + try { + MSI_TIME_STAMP_START(Predict) + status = dispatcher_->Dispatch(*request, *reply); + MSI_TIME_STAMP_END(Predict) + } catch (const std::bad_alloc &ex) { + MSI_LOG(ERROR) << "Serving Error: malloc memory failed"; + std::cout << "Serving Error: malloc memory failed" << std::endl; + } catch (const std::runtime_error &ex) { + MSI_LOG(ERROR) << "Serving Error: runtime error occurred: " << ex.what(); + std::cout << "Serving Error: runtime error occurred: " << ex.what() << std::endl; + } catch (const std::exception &ex) { + MSI_LOG(ERROR) << "Serving Error: exception occurred: " << ex.what(); + std::cout << "Serving Error: exception occurred: " << ex.what() << std::endl; + } catch (...) { + MSI_LOG(ERROR) << "Serving Error: exception occurred"; + std::cout << "Serving Error: exception occurred"; + } + MSI_LOG(INFO) << "Finish call service Eval"; + + if (status != SUCCESS) { + auto proto_error_msg = reply->add_error_msg(); + proto_error_msg->set_error_code(FAILED); + std::string error_msg = status.StatusMessage(); + if (error_msg.empty()) { + proto_error_msg->set_error_msg("Predict failed"); + } else { + proto_error_msg->set_error_msg(error_msg); + } + return grpc::Status::OK; + } + return grpc::Status::OK; +} + +grpc::Status MSMasterImpl::Register(grpc::ServerContext *context, const proto::RegisterRequest *request, + proto::RegisterReply *reply) { + auto worker_sig = [request]() { + std::stringstream str; + str << "worker address: " << request->address() << ", servables: ["; + for (int i = 0; i < request->worker_spec_size(); i++) { + str << GetProtorWorkerSpecRepr(request->worker_spec(i)); + if (i + 1 < request->worker_spec_size()) { + str << ", "; + } + } + str << "]"; + return str.str(); + }; + Status status(FAILED); + status = dispatcher_->RegisterServable(*request, *reply); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Register servable failed, " << worker_sig(); + return grpc::Status::OK; + } + MSI_LOG(INFO) << "Register success: " << worker_sig(); + return grpc::Status::OK; +} + +grpc::Status MSMasterImpl::AddWorker(grpc::ServerContext *context, const proto::AddWorkerRequest *request, + proto::AddWorkerReply *reply) { + auto worker_sig = [request]() { + std::stringstream str; + str << "worker address: " << request->address() + << ", servables: " << GetProtorWorkerSpecRepr(request->worker_spec()); + return str.str(); + }; + Status status(FAILED); + status = dispatcher_->AddServable(*request, *reply); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Add servable failed, " << worker_sig(); + return grpc::Status::OK; + } + MSI_LOG(INFO) << "Add success, " << worker_sig(); + return grpc::Status::OK; +} + +grpc::Status MSMasterImpl::RemoveWorker(grpc::ServerContext *context, const proto::RemoveWorkerRequest *request, + proto::RemoveWorkerReply *reply) { + auto worker_sig = [request]() { + std::stringstream str; + str << "worker address: " << request->address() + << ", servables: " << GetProtorWorkerSpecRepr(request->worker_spec()); + return str.str(); + }; + Status status(FAILED); + status = dispatcher_->RemoveServable(*request, *reply); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Add servable failed, " << worker_sig(); + return grpc::Status::OK; + } + MSI_LOG(INFO) << "Add success, " << worker_sig(); + return grpc::Status::OK; +} + +grpc::Status MSMasterImpl::Exit(grpc::ServerContext *context, const proto::ExitRequest *request, + proto::ExitReply *reply) { + auto worker_sig = [request]() { + std::stringstream str; + str << "worker address: " << request->address(); + return str.str(); + }; + + MSI_LOG(INFO) << "Worker Exit, " << worker_sig(); + Status status(FAILED); + status = dispatcher_->UnregisterServable(*request, *reply); + if (status != SUCCESS) { + MSI_LOG_ERROR << "UnRegister servable failed, " << worker_sig(); + return grpc::Status::OK; + } + return grpc::Status::OK; +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/master/grpc/grpc_process.h b/mindspore_serving/ccsrc/master/grpc/grpc_process.h new file mode 100644 index 0000000..95ef75e --- /dev/null +++ b/mindspore_serving/ccsrc/master/grpc/grpc_process.h @@ -0,0 +1,68 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_GRPC_PROCESS_H +#define MINDSPORE_GRPC_PROCESS_H + +#include +#include +#include +#include +#include "common/serving_common.h" +#include "proto/ms_service.pb.h" +#include "proto/ms_service.grpc.pb.h" +#include "proto/ms_master.pb.h" +#include "proto/ms_master.grpc.pb.h" +#include "master/dispacther.h" + +namespace mindspore { +namespace serving { + +// Service Implement +class MSServiceImpl final : public proto::MSService::Service { + public: + explicit MSServiceImpl(std::shared_ptr dispatcher) : dispatcher_(dispatcher) {} + ~MSServiceImpl() = default; + + grpc::Status Predict(grpc::ServerContext *context, const proto::PredictRequest *request, + proto::PredictReply *reply) override; + + private: + std::shared_ptr dispatcher_; +}; + +// Service Implement +class MSMasterImpl final : public proto::MSMaster::Service { + public: + explicit MSMasterImpl(std::shared_ptr dispatcher) : dispatcher_(dispatcher) {} + ~MSMasterImpl() = default; + + grpc::Status Register(grpc::ServerContext *context, const proto::RegisterRequest *request, + proto::RegisterReply *reply) override; + grpc::Status Exit(grpc::ServerContext *context, const proto::ExitRequest *request, proto::ExitReply *reply) override; + grpc::Status AddWorker(grpc::ServerContext *context, const proto::AddWorkerRequest *request, + proto::AddWorkerReply *reply) override; + grpc::Status RemoveWorker(grpc::ServerContext *context, const proto::RemoveWorkerRequest *request, + proto::RemoveWorkerReply *reply) override; + + private: + std::shared_ptr dispatcher_; +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_GRPC_PROCESS_H diff --git a/mindspore_serving/ccsrc/master/restful/http_handle.cc b/mindspore_serving/ccsrc/master/restful/http_handle.cc new file mode 100644 index 0000000..1a92a61 --- /dev/null +++ b/mindspore_serving/ccsrc/master/restful/http_handle.cc @@ -0,0 +1,146 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "master/restful/http_handle.h" +#include +#include +#include +#include + +#include "master/restful/http_process.h" +#include "master/server.h" + +namespace mindspore { +namespace serving { + +static std::vector encode_table = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; +static std::vector decode_table = { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, + 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255, 255, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255}; + +size_t Base64Encode(const uint8_t *input, size_t length, uint8_t *output) { + if (length == 0) { + return 0; + } + + size_t i, j; + for (i = 0, j = 0; i + 3 <= length; i += 3) { + output[j++] = encode_table[input[i] >> 2]; + output[j++] = encode_table[((input[i] << 4) & 0x30) | (input[i + 1] >> 4)]; + output[j++] = encode_table[((input[i + 1] << 2) & 0x3c) | (input[i + 2] >> 6)]; + output[j++] = encode_table[input[i + 2] & 0x3f]; + } + + if (i < length) { + uint32_t left_num = length - i; + if (left_num == 1) { + output[j++] = encode_table[input[i] >> 2]; + output[j++] = encode_table[(input[i] << 4) & 0x30]; + output[j++] = '='; + output[j++] = '='; + } else { + output[j++] = encode_table[input[i] >> 2]; + output[j++] = encode_table[((input[i] << 4) & 0x30) | (input[i + 1] >> 4)]; + output[j++] = encode_table[(input[i + 1] << 2) & 0x3c]; + output[j++] = '='; + } + } + return j; +} + +size_t Base64Decode(const uint8_t *target, size_t target_length, uint8_t *origin) { + if (target_length == 0 || target_length % 4 != 0) { + return 0; + } + size_t i, j = 0; + uint8_t value[4]; + for (i = 0; i < target_length; i += 4) { + for (size_t k = 0; k < 4; k++) { + value[k] = decode_table[target[i + k]]; + } + + // value[2], value[3]:may be '=' + if (value[0] >= 64 || value[1] >= 64) { + MSI_LOG_EXCEPTION << "Decode value is not more than max value 64"; + } + + origin[j++] = (value[0] << 2) | (value[1] >> 4); + + if (value[2] >= 64) { + break; + } else if (value[3] >= 64) { + origin[j++] = (value[1] << 4) | (value[2] >> 2); + break; + } else { + origin[j++] = (value[1] << 4) | (value[2] >> 2); + origin[j++] = (value[2] << 6) | value[3]; + } + } + return j; +} + +size_t GetB64TargetSize(size_t origin_len) { + size_t target_size = 0; + if (origin_len % 3 == 0) { + target_size = (origin_len / 3) * 4; + } else { + target_size = (origin_len / 3 + 1) * 4; + } + return target_size; +} + +size_t GetB64OriginSize(size_t target_len, size_t tail_size) { + size_t origin_length = 0; + if (target_len == 0 || target_len % 4 != 0) { + return origin_length; + } + origin_length = 3 * (target_len / 4) - tail_size; + return origin_length; +} + +size_t GetTailEqualSize(const std::string &str) { + size_t length = str.size(); + if (length == 0 || length % 4 != 0) { + return UINT32_MAX; + } + size_t count = 0; + if (str.at(str.size() - 1) == '=') { + count++; + } + + if (str.at(str.size() - 2) == '=') { + count++; + } + + return count; +} + +Status HandleRestfulRequest(const std::shared_ptr &restful_request, json *const out_json) { + Status status(SUCCESS); + std::shared_ptr dispatcher_ = Server::Instance().GetDispatcher(); + RestfulService restful_service(dispatcher_); + status = restful_service.RunRestful(restful_request, out_json); + return status; +} +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/master/restful/http_handle.h b/mindspore_serving/ccsrc/master/restful/http_handle.h new file mode 100644 index 0000000..ea9ce5d --- /dev/null +++ b/mindspore_serving/ccsrc/master/restful/http_handle.h @@ -0,0 +1,38 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_HTTP_HANDLE_H +#define MINDSPORE_SERVING_HTTP_HANDLE_H + +#include +#include +#include "common/serving_common.h" +#include "master/restful/restful_request.h" + +using nlohmann::json; +namespace mindspore { +namespace serving { +Status HandleRestfulRequest(const std::shared_ptr &restful_request, json *const out_json); + +size_t Base64Encode(const uint8_t *input, size_t length, uint8_t *output); +size_t Base64Decode(const uint8_t *target, size_t target_length, uint8_t *origin); +size_t GetB64TargetSize(size_t origin_len); +size_t GetB64OriginSize(size_t target_len, size_t tail_size); +size_t GetTailEqualSize(const std::string &str); + +} // namespace serving +} // namespace mindspore +#endif // MINDSPORE_SERVING_HTTP_HANDLE_H diff --git a/mindspore_serving/ccsrc/master/restful/http_process.cc b/mindspore_serving/ccsrc/master/restful/http_process.cc new file mode 100644 index 0000000..0cf0ece --- /dev/null +++ b/mindspore_serving/ccsrc/master/restful/http_process.cc @@ -0,0 +1,1224 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "master/restful/http_process.h" +#include +#include +#include +#include +#include +#include +#include +#include "common/serving_common.h" +#include "master/restful/http_handle.h" + +using mindspore::serving::proto::Instance; +using mindspore::serving::proto::PredictReply; +using mindspore::serving::proto::PredictRequest; + +namespace mindspore { +namespace serving { +const int BUF_MAX = 0x7FFFFFFF; + +static const std::map infer_type2_http_type{{DataType::kMSI_Int32, HTTP_DATA_INT}, + {DataType::kMSI_Float32, HTTP_DATA_FLOAT}}; + +static const std::map http_type2_infer_type{{HTTP_DATA_INT, DataType::kMSI_Int32}, + {HTTP_DATA_FLOAT, DataType::kMSI_Float32}, + {HTTP_DATA_BOOL, DataType::kMSI_Bool}, + {HTTP_DATA_STR, DataType::kMSI_String}, + {HTTP_DATA_OBJ, DataType::kMSI_Bytes}}; + +static const std::map str2_infer_type{ + {"int8", DataType::kMSI_Int8}, {"int16", DataType::kMSI_Int16}, {"int32", DataType::kMSI_Int32}, + {"int64", DataType::kMSI_Int64}, {"uint8", DataType::kMSI_Uint8}, {"uint16", DataType::kMSI_Uint16}, + {"uint32", DataType::kMSI_Uint32}, {"uint64", DataType::kMSI_Uint64}, {"fp16", DataType::kMSI_Float16}, + {"fp32", DataType::kMSI_Float32}, {"fp64", DataType::kMSI_Float64}, {"bool", DataType::kMSI_Bool}, + {"str", DataType::kMSI_String}, {"bytes", DataType::kMSI_Bytes}}; + +template +bool RestfulService::IsString() { + return typeid(T).hash_code() == typeid(std::string).hash_code(); +} + +std::string RestfulService::GetString(const uint8_t *ptr, size_t length) { + std::string str; + for (size_t i = 0; i < length; i++) { + str += ptr[i]; + } + return str; +} + +Status RestfulService::CheckObjTypeMatchShape(DataType data_type, const std::vector &shape) { + if (data_type == kMSI_String || data_type == kMSI_Bytes) { + size_t elements_nums = std::accumulate(shape.begin(), shape.end(), 1LL, std::multiplies()); + if (elements_nums != 1) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Json object, only support scalar when data type is string or bytes"; + } + } + return SUCCESS; +} + +RequestType RestfulService::GetReqType(const std::string &str) { + auto it = std::find(request_type_list_.begin(), request_type_list_.end(), str); + if (it == request_type_list_.end()) { + return kInvalidType; + } + + if (*it == kInstancesRequest) { + return kInstanceType; + } + + return kInvalidType; +} + +std::string RestfulService::GetReqTypeStr(RequestType req_type) { + switch (req_type) { + case kInstanceType: + return kInstancesRequest; + default: + break; + } + return ""; +} + +Status RestfulService::CheckObjType(const string &type) { + Status status(SUCCESS); + auto it = str2_infer_type.find(type); + + if (it == str2_infer_type.end()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, specified type:" + << "'" << type << "' is illegal"; + } + + return status; +} + +DataType RestfulService::GetObjDataType(const json &js) { + DataType type = kMSI_Unknown; + if (!js.is_object()) { + return type; + } + + auto it1 = js.find(kType); + if (it1 == js.end()) { + type = kMSI_Bytes; + } else { + auto type_str = it1.value(); + auto it2 = str2_infer_type.find(type_str); + if (it2 != str2_infer_type.end()) { + type = it2->second; + } + } + + return type; +} + +std::string RestfulService::GetStringByDataType(DataType type) { + for (const auto &item : str2_infer_type) { + if (item.second == type) { + return item.first; + } + } + + return ""; +} + +bool RestfulService::JsonMatchDataType(const json &js, DataType type) { + bool flag = false; + if (js.is_number_integer()) { + if (type >= kMSI_Int8 && type <= kMSI_Uint64) { + flag = true; + } + } else if (js.is_number_float()) { + if (type >= kMSI_Float16 && type <= kMSI_Float64) { + flag = true; + } + } else if (js.is_string()) { + // string value can express all kinds type + flag = true; + } else if (js.is_boolean()) { + if (type == kMSI_Bool) { + flag = true; + } + } + + return flag; +} + +std::vector RestfulService::GetObjShape(const json &js) { + std::vector shape; + auto it = js.find(kShape); + if (it == js.end()) { + shape.push_back(1); + } else { + shape = GetSpecifiedShape(it.value()); + } + + return shape; +} + +std::vector RestfulService::GetArrayShape(const json &json_array) { + std::vector json_shape; + const json *tmp_json = &json_array; + while (tmp_json->is_array()) { + if (tmp_json->empty()) { + break; + } + + json_shape.emplace_back(tmp_json->size()); + tmp_json = &tmp_json->at(0); + } + + return json_shape; +} + +std::vector RestfulService::GetSpecifiedShape(const json &js) { + std::vector shape; + if (!js.is_array()) { + return shape; + } + if (js.empty()) { + return shape; + } + + for (size_t i = 0; i < js.size(); i++) { + auto &item = js.at(i); + if (!item.is_number_unsigned()) { + return {}; + } else { + shape.push_back(item.get()); + } + } + + return shape; +} + +DataType RestfulService::GetArrayDataType(const json &json_array, HTTP_DATA_TYPE &type_format) { + DataType data_type = kMSI_Unknown; + const json *tmp_json = &json_array; + while (tmp_json->is_array()) { + if (tmp_json->empty()) { + return data_type; + } + + tmp_json = &tmp_json->at(0); + } + + if (tmp_json->is_number_integer()) { + type_format = HTTP_DATA_INT; + data_type = http_type2_infer_type.at(type_format); + } else if (tmp_json->is_number_float()) { + type_format = HTTP_DATA_FLOAT; + data_type = http_type2_infer_type.at(type_format); + } else if (tmp_json->is_boolean()) { + type_format = HTTP_DATA_BOOL; + data_type = http_type2_infer_type.at(type_format); + } else if (tmp_json->is_object()) { + type_format = HTTP_DATA_OBJ; + data_type = GetObjDataType(*tmp_json); + } else if (tmp_json->is_string()) { + type_format = HTTP_DATA_STR; + data_type = http_type2_infer_type.at(type_format); + } + + return data_type; +} + +Status RestfulService::CheckReqJsonValid(const json &js_msg) { + int count = 0; + for (size_t i = 0; i < request_type_list_.size(); i++) { + std::string item = request_type_list_.at(i); + auto it = js_msg.find(item.c_str()); + if (it != js_msg.end()) { + count++; + auto request_type = GetReqType(item); + if (request_type == kInvalidType) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Only support instances mode"; + } + + request_type_ = request_type; + } + } + + if (count != 1) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Key 'instances' should exit and only exit one time"; + } + return SUCCESS; +} + +Status RestfulService::GetInstancesType(const json &instances) { + Status status{SUCCESS}; + // Eg:{"instances" : 1} + if (!(instances.is_array() || instances.is_object())) { + instances_type_ = kNokeyWay; + return status; + } + + // Eg:{"instances":{"A":1, "B":2}} + if (instances.is_object()) { + instances_type_ = kKeyWay; + return status; + } + + // array: + if (instances.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Instances value is array type, but no value"; + } + auto first_instance = instances.at(0); + if (first_instance.is_object()) { + instances_type_ = kKeyWay; + } else { + instances_type_ = kNokeyWay; + } + + return status; +} + +Status RestfulService::CheckObj(const json &js) { + if (!js.is_object()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json is not object" << js.dump(); + } + + if (js.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, value is empty"; + } + + // 1)required:b64 2)optional:type 3)optional:shape + if (js.size() > 3) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Json object, items size is more than 3, only support specified ['b64', 'type', 'shape']"; + } + + int b64_count = 0; + int shape_count = 0; + int type_count = 0; + for (auto item = js.begin(); item != js.end(); ++item) { + auto key = item.key(); + auto value = item.value(); + if (key != kB64 && key != kType && key != kShape) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Json object, key is not ['b64', 'type', 'shape'], fail key:" << key; + } + if (key == kB64) { + b64_count++; + } else if (key == kType) { + if (!value.is_string()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, key is 'type', value should be string type"; + } + auto status = CheckObjType(value); + if (status != SUCCESS) { + return status; + } + type_count++; + } else if (key == kShape) { + if (!value.is_array()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, key is 'shape', value should be array type"; + } + if (value.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, key is 'shape', array value should no be empty"; + } + for (auto it = value.begin(); it != value.end(); ++it) { + if (!(it->is_number())) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, key is 'shape', array value should be number"; + } + auto number = it->get(); + if (number <= 0) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Json object, key is 'shape', number value should be positive number"; + } + } + shape_count++; + } + } + + if (b64_count != 1) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, 'b64' should be specified only one time"; + } + + if (type_count > 1) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, 'type' should be specified no more than one time"; + } + + if (shape_count > 1) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, 'shape' should be specified no more than one time"; + } + + return SUCCESS; +} + +// 1. parse request common func +Status RestfulService::ParseItem(const json &value, ProtoTensor *const pb_tensor) { + Status status(SUCCESS); + if (value.is_number_integer()) { + DataType type = kMSI_Int32; + pb_tensor->set_data_type(type); + pb_tensor->set_shape({1}); + pb_tensor->resize_data(pb_tensor->GetTypeSize(type)); + status = GetScalarByType(type, value, 0, pb_tensor); + } else if (value.is_number_float()) { + DataType type = kMSI_Float32; + pb_tensor->set_data_type(type); + pb_tensor->set_shape({1}); + pb_tensor->resize_data(pb_tensor->GetTypeSize(type)); + status = GetScalarByType(type, value, 0, pb_tensor); + } else if (value.is_boolean()) { + DataType type = kMSI_Bool; + pb_tensor->set_data_type(type); + pb_tensor->set_shape({1}); + pb_tensor->resize_data(pb_tensor->GetTypeSize(type)); + status = GetScalarByType(type, value, 0, pb_tensor); + } else if (value.is_string()) { + DataType type = kMSI_String; + pb_tensor->set_data_type(type); + pb_tensor->set_shape({1}); + status = GetScalarByType(type, value, 0, pb_tensor); + } else if (value.is_object()) { + status = CheckObj(value); + if (status != SUCCESS) { + return status; + } + + DataType type = GetObjDataType(value); + if (type == kMSI_Unknown) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, type is unknown"; + } + + std::vector shape = GetObjShape(value); + if (shape.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Object json, shape is empty"; + } + + bool is_tensor = false; + if (type != kMSI_String && type != kMSI_Bytes) { + is_tensor = true; + } + if (is_tensor) { + size_t shape_size = std::accumulate(shape.begin(), shape.end(), 1LL, std::multiplies()); + size_t type_size = pb_tensor->GetTypeSize(type); + pb_tensor->resize_data(shape_size * type_size); + MSI_LOG_INFO << "Shape size:" << shape_size << "; type size:" << type_size + << "; data size:" << shape_size * type_size; + } + + status = CheckObjTypeMatchShape(type, shape); + if (status != SUCCESS) { + return status; + } + pb_tensor->set_data_type(type); + pb_tensor->set_shape(shape); + status = GetScalarByType(serving::kMSI_Bytes, value[kB64], 0, pb_tensor); + } else if (value.is_array()) { + HTTP_DATA_TYPE type_format = HTTP_DATA_NONE; + auto shape = GetArrayShape(value); + if (shape.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, shape is empty"; + } + DataType data_type = GetArrayDataType(value, type_format); + if (data_type == kMSI_Unknown) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, data type is unknown"; + } + + bool is_tensor = false; + if (data_type != kMSI_String && data_type != kMSI_Bytes) { + is_tensor = true; + } + + // intances mode:only support one item + if (request_type_ == kInstanceType) { + if (!is_tensor) { + size_t elements_nums = std::accumulate(shape.begin(), shape.end(), 1LL, std::multiplies()); + if (elements_nums != 1) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, string or bytes type only support one item"; + } + } + } + + // set real data type + pb_tensor->set_data_type(data_type); + pb_tensor->set_shape(shape); + + if (is_tensor) { + size_t shape_size = std::accumulate(shape.begin(), shape.end(), 1LL, std::multiplies()); + size_t type_size = pb_tensor->GetTypeSize(data_type); + pb_tensor->resize_data(shape_size * type_size); + MSI_LOG_INFO << "Shape size:" << shape_size << "; type size:" << type_size + << "; data size:" << shape_size * type_size; + } + + if (type_format == HTTP_DATA_OBJ) { + if (data_type != kMSI_Bytes && data_type != kMSI_String) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Json array, item is object type, object only support string or bytes type"; + } + } + status = RecursiveGetArray(value, 0, 0, type_format, pb_tensor); + if (status != SUCCESS) { + return status; + } + } else if (value.is_null()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json value is null, it is not supported"; + } else if (value.is_discarded()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json value is discarded type, it is not supported"; + } + return status; +} + +Status RestfulService::RecursiveGetArray(const json &json_data, size_t depth, size_t data_index, + HTTP_DATA_TYPE type_format, ProtoTensor *const request_tensor) { + Status status(SUCCESS); + std::vector required_shape = request_tensor->shape(); + if (depth >= required_shape.size()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Current depth:" << depth << " is more than shape dims:" << required_shape.size(); + } + if (!json_data.is_array()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json type is not array"; + } + if (json_data.size() != static_cast(required_shape[depth])) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Json size is:" << json_data.size() << "; the " << depth << " dim need" + << " shape size:" << required_shape[depth]; + } + if (depth + 1 < required_shape.size()) { + size_t sub_element_cnt = + std::accumulate(required_shape.begin() + depth + 1, required_shape.end(), 1LL, std::multiplies()); + for (size_t k = 0; k < json_data.size(); k++) { + status = + RecursiveGetArray(json_data[k], depth + 1, data_index + sub_element_cnt * k, type_format, request_tensor); + if (status != SUCCESS) { + return status; + } + } + } else { + status = GetArrayData(json_data, data_index, type_format, request_tensor); + if (status != SUCCESS) { + return status; + } + } + return status; +} + +Status RestfulService::GetArrayData(const json &js, size_t data_index, HTTP_DATA_TYPE type, + ProtoTensor *const request_tensor) { + Status status(SUCCESS); + size_t element_nums = js.size(); + MSI_LOG_INFO << "element nums:" << element_nums << "; data index:" << data_index; + if (type != HTTP_DATA_OBJ) { + for (size_t k = 0; k < element_nums; k++) { + auto &json_data = js[k]; + if (!(json_data.is_number() || json_data.is_boolean() || json_data.is_string())) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, data should be number, bool, string or bytes"; + } + auto flag = JsonMatchDataType(json_data, request_tensor->data_type()); + if (!flag) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, elements type is not equal"; + } + status = GetScalarByType(request_tensor->data_type(), json_data, data_index + k, request_tensor); + if (status != SUCCESS) { + return status; + } + } + } else { + for (size_t k = 0; k < element_nums; k++) { + auto &json_data = js[k]; + auto value_type = GetObjDataType(json_data); + // Array:object only support string or bytes + if (value_type != kMSI_String && value_type != kMSI_Bytes) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, object type only support string or bytes type"; + } + + if (value_type != request_tensor->data_type()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, elements type is not equal"; + } + + status = GetScalarByType(value_type, json_data[kB64], data_index + k, request_tensor); + if (status != SUCCESS) { + return status; + } + } + } + return status; +} + +Status RestfulService::GetScalarByType(DataType type, const json &js, size_t index, ProtoTensor *const request_tensor) { + Status status(SUCCESS); + if (type == kMSI_Unknown) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Data type is unknown"; + } + MSI_LOG_INFO << "Data type:" << type << " ;real data type:" << request_tensor->data_type() + << " ;data index:" << index; + switch (type) { + case kMSI_Bool: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Int8: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Int16: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Int32: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Int64: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Uint8: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Uint16: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Uint32: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Uint64: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Float16: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Float32: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Float64: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_String: + status = GetScalarData(js, index, false, request_tensor); + break; + case kMSI_Bytes: + status = GetScalarData(js, index, true, request_tensor); + break; + default: + auto type_str = GetStringByDataType(type); + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Data type:" << type_str << " is not supported"; + } + return status; +} + +template +Status RestfulService::GetScalarData(const json &js, size_t index, bool is_bytes, ProtoTensor *const request_tensor) { + Status status(SUCCESS); + if (IsString()) { + // 1.string + if (!js.is_string()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Get scalar data failed, type is string, but json is not string type"; + } + + auto value = js.get(); + if (value.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "string value is empty"; + } + if (is_bytes) { + auto tail_equal_size = GetTailEqualSize(value); + if (tail_equal_size == UINT32_MAX) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "'" << value << "' is not legal b64 encode string"; + } + auto origin_size = GetB64OriginSize(value.length(), tail_equal_size); + uint8_t buffer[origin_size]; + auto target_size = Base64Decode(reinterpret_cast(value.data()), value.length(), buffer); + if (target_size != origin_size) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Decode base64 failed, size is not matched."; + } + DataType real_type = request_tensor->data_type(); + if (real_type == kMSI_Bytes || real_type == kMSI_String) { + request_tensor->add_bytes_data(buffer, origin_size); + } else { + auto type_size = request_tensor->GetTypeSize(real_type); + auto element_cnt = request_tensor->element_cnt(); + if (origin_size != type_size * element_cnt) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Size is not matched, decode base64 size:" << origin_size << "; given: type:" << real_type + << "; type size:" << type_size << "; element nums:" << element_cnt; + } + + auto data = reinterpret_cast(request_tensor->mutable_data()) + index; + memcpy_s(data, origin_size, buffer, origin_size); + } + } else { + request_tensor->add_bytes_data(reinterpret_cast(value.data()), value.length()); + } + } else { + DataType data_type = request_tensor->data_type(); + auto flag = JsonMatchDataType(js, data_type); + if (!flag) { + auto type_str = GetStringByDataType(data_type); + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) + << "Data type and json type is not matched, data type is:" << type_str; + } + + // 2.number + if ((js.is_number() || js.is_boolean())) { + // 1)common number + auto data = reinterpret_cast(request_tensor->mutable_data()) + index; + *data = js.get(); + } + } + + return status; +} + +// 2.main +Status RestfulService::RunRestful(const std::shared_ptr &restful_request, json *const out_json) { + PredictRequest request; + PredictReply reply; + + MSI_TIME_STAMP_START(ParseRequest) + auto status = ParseRequest(restful_request, &request); + MSI_TIME_STAMP_END(ParseRequest) + if (status != SUCCESS) { + std::string error_msg = status.StatusMessage(); + std::string msg = "Paser reqeust failed, " + error_msg; + status = msg; + return status; + } + + MSI_TIME_STAMP_START(Predict) + // status = dispatcher_->Dispatch(request, reply); + + FadeReply(request, reply); + MSI_TIME_STAMP_END(Predict) + if (status != SUCCESS) { + std::string error_msg = status.StatusMessage(); + std::string msg = "Predict failed, " + error_msg; + status = msg; + return status; + } + + MSI_TIME_STAMP_START(CreateReplyJson) + status = ParseReply(reply, out_json); + MSI_TIME_STAMP_END(CreateReplyJson) + if (status != SUCCESS) { + std::string error_msg = status.StatusMessage(); + std::string msg = "Parse reply failed, " + error_msg; + status = msg; + return status; + } + return SUCCESS; +} + +// 3.parse request +Status RestfulService::ParseRequest(const std::shared_ptr &restful_request, + PredictRequest *const request) { + Status status(SUCCESS); + // 1. parse common msg + status = ParseReqCommonMsg(restful_request, request); + if (status != SUCCESS) { + return status; + } + + // 2. parse json + auto request_ptr = restful_request->decompose_event_request(); + auto &js_msg = request_ptr->request_message_; + status = CheckReqJsonValid(js_msg); + if (status != SUCCESS) { + return status; + } + + switch (request_type_) { + case kInstanceType: + status = ParseInstancesMsg(js_msg, request); + break; + default: + return INFER_STATUS_LOG_ERROR(FAILED) << "Restful reqeust only support instances mode"; + } + + PrintRequest(request); + return status; +} + +Status RestfulService::ParseReqCommonMsg(const std::shared_ptr &restful_request, + PredictRequest *const request) { + Status status(SUCCESS); + auto request_ptr = restful_request->decompose_event_request(); + if (request_ptr == nullptr) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "Decompose event request is nullptr"); + return status; + } + request->mutable_servable_spec()->set_name(request_ptr->model_name_); + request->mutable_servable_spec()->set_version_number(request_ptr->version_); + request->mutable_servable_spec()->set_method_name(request_ptr->service_method_); + MSI_LOG_INFO << "Restful req, model name:" << request->servable_spec().name(); + MSI_LOG_INFO << "Version number:" << request->servable_spec().version_number(); + MSI_LOG_INFO << "Method name:" << request->servable_spec().method_name(); + return status; +} + +Status RestfulService::ParseInstancesMsg(const json &js_msg, PredictRequest *const request) { + Status status = SUCCESS; + auto type = GetReqTypeStr(request_type_); + auto instances = js_msg.find(type); + if (instances == js_msg.end()) { + ERROR_INFER_STATUS(status, FAILED, "Instances request json should have instances key word"); + return status; + } + + // get instances way:{key, value} or {value} + status = GetInstancesType(*instances); + if (status != SUCCESS) { + return status; + } + + switch (instances_type_) { + case kKeyWay: { + status = ParseKeyInstances(*instances, request); + break; + } + case kNokeyWay: { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Instances no key mode is not supported"; + } + case kInvalidWay: { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Invalid request type"; + } + } + return status; +} + +Status RestfulService::ParseKeyInstances(const json &instances, PredictRequest *const request) { + Status status(SUCCESS); + if (instances.is_object()) { + // one instance:{"instances":{"A":1, "B": 2}} + if (instances.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json object, value is empty"; + } + status = PaserKeyOneInstance(instances, request); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Instances:parse one instance failed"; + return status; + } + instances_nums_ = 1; + } else { + // multi instance:{"instances":[{}, {}]} + for (size_t i = 0; i < instances.size(); i++) { + auto &instance = instances.at(i); + if (!instance.is_object()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, instance is not object type"; + } + + if (instance.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Json array, instance is object type, but no value"; + } + + status = PaserKeyOneInstance(instance, request); + if (status != SUCCESS) { + return status; + } + } + instances_nums_ = instances.size(); + } + return status; +} + +// instance_mgs:one instance, type is object +Status RestfulService::PaserKeyOneInstance(const json &instance_msg, PredictRequest *const request) { + Status status(SUCCESS); + auto instance = request->add_instances(); + + for (auto it = instance_msg.begin(); it != instance_msg.end(); ++it) { + auto key = it.key(); + if (key.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "string key is empty"; + } + auto value = it.value(); + + proto::Tensor tensor; + ProtoTensor pb_tensor(tensor); + + status = ParseItem(value, &pb_tensor); + if (status != SUCCESS) { + return status; + } + + auto &map_item = *(instance->mutable_items()); + map_item[key] = tensor; + } + return status; +} + +/************************************************************************************/ +// 4.parse reply common func +Status RestfulService::ParseReplyDetail(proto::Tensor tensor, json *const js) { + Status status(SUCCESS); + ProtoTensor pb_tensor(tensor); + auto shape = pb_tensor.shape(); + size_t shape_size = std::accumulate(shape.begin(), shape.end(), 1LL, std::multiplies()); + + if (shape_size == 1) { + status = ParseScalar(pb_tensor, 0, js); + if (status != SUCCESS) { + return status; + } + } else { + status = CheckReply(pb_tensor); + if (status != SUCCESS) { + return status; + } + status = RecursiveParseArray(pb_tensor, 0, 0, js); + if (status != SUCCESS) { + return status; + } + } + return status; +} + +Status RestfulService::ParseScalar(const ProtoTensor &pb_tensor, size_t index, json *const js) { + Status status(SUCCESS); + DataType data_type = pb_tensor.data_type(); + if (data_type == kMSI_Unknown) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Data type is unknown"; + } + MSI_LOG_INFO << "Data type:" << data_type << "; index:" << index; + switch (data_type) { + case kMSI_Bool: + status = ParseScalarData(pb_tensor, false, index, js); + break; + case kMSI_Int8: + status = ParseScalarData(pb_tensor, false, index, js); + break; + case kMSI_Int16: + status = ParseScalarData(pb_tensor, false, index, js); + break; + case kMSI_Int32: + status = ParseScalarData(pb_tensor, false, index, js); + MSI_LOG_INFO << "parse int32:" << js->get(); + break; + case kMSI_Int64: + status = ParseScalarData(pb_tensor, false, index, js); + MSI_LOG_INFO << "parse int64:" << js->get(); + break; + case kMSI_Uint8: + status = ParseScalarData(pb_tensor, false, index, js); + break; + case kMSI_Uint16: + status = ParseScalarData(pb_tensor, false, index, js); + break; + case kMSI_Uint32: + status = ParseScalarData(pb_tensor, false, index, js); + MSI_LOG_INFO << "parse uint32:" << js->get(); + break; + case kMSI_Uint64: + status = ParseScalarData(pb_tensor, false, index, js); + MSI_LOG_INFO << "parse uint64:" << js->get(); + break; + case kMSI_Float16: + ERROR_INFER_STATUS(status, FAILED, "Fp16 reply is not supported"); + break; + case kMSI_Float32: + status = ParseScalarData(pb_tensor, false, index, js); + break; + case kMSI_Float64: + status = ParseScalarData(pb_tensor, false, index, js); + break; + case kMSI_String: + status = ParseScalarData(pb_tensor, false, index, js); + break; + case kMSI_Bytes: + status = ParseScalarData(pb_tensor, true, index, js); + break; + default: + ERROR_INFER_STATUS(status, FAILED, "Reply data type is not supported"); + break; + } + return status; +} + +template +Status RestfulService::ParseScalarData(const ProtoTensor &pb_tensor, bool is_bytes, size_t index, json *const js) { + Status status(SUCCESS); + + if (!IsString()) { + const T *data = reinterpret_cast(pb_tensor.data()) + index; + T value = *data; + *js = value; + } else if (IsString()) { + if (!is_bytes) { + auto str_nums = pb_tensor.bytes_data_size(); + if (str_nums == 0) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Reply string, size is 0"; + } + if (index >= str_nums) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Reply string, index:" << index << " is more than size:" << str_nums; + } + + std::string value; + size_t length; + const uint8_t *ptr = nullptr; + pb_tensor.get_bytes_data(index, ptr, length); + value.resize(length); + memcpy_s(value.data(), length, reinterpret_cast(ptr), length); + MSI_LOG_INFO << "Parse string value:" << value; + *js = value; + } else { + auto str_nums = pb_tensor.bytes_data_size(); + if (str_nums == 0) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Reply bytes, size is 0"; + } + + if (index >= str_nums) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Reply bytes, index:" << index << " is more than size:" << str_nums; + } + + std::string value; + size_t length; + const uint8_t *ptr = nullptr; + pb_tensor.get_bytes_data(index, ptr, length); + value.resize(length); + memcpy_s(value.data(), length, reinterpret_cast(ptr), length); + MSI_LOG_INFO << "bytes type, origin str:" << value; + + auto target_size = GetB64TargetSize(length); + uint8_t buffer[target_size]; + auto size = Base64Encode(reinterpret_cast(value.data()), value.length(), buffer); + if (size != target_size) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Reply bytes, size is not matched, expected size:" << target_size << ", encode size:" << size; + } + std::string str = GetString(buffer, target_size); + MSI_LOG_INFO << "bytes type, decoded str:" << str; + (*js)[kB64] = str; + } + } + return status; +} + +Status RestfulService::RecursiveParseArray(ProtoTensor pb_tensor, size_t depth, size_t pos, json *const out_json) { + Status status(SUCCESS); + std::vector required_shape = pb_tensor.shape(); + if (depth >= 10) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Result shape dims is larger than 10"; + } + if (depth == required_shape.size() - 1) { + for (int i = 0; i < required_shape[depth]; i++) { + out_json->push_back(json()); + json &scalar_json = out_json->back(); + status = ParseScalar(pb_tensor, pos + i, &scalar_json); + if (status != SUCCESS) { + return status; + } + } + } else { + for (int i = 0; i < required_shape[depth]; i++) { + // array: + out_json->push_back(json()); + json &tensor_json = out_json->back(); + size_t sub_element_cnt = + std::accumulate(required_shape.begin() + depth + 1, required_shape.end(), 1LL, std::multiplies()); + status = RecursiveParseArray(pb_tensor, depth + 1, i * sub_element_cnt + pos, &tensor_json); + if (status != SUCCESS) { + return status; + } + } + } + return status; +} + +Status RestfulService::CheckReply(const ProtoTensor &pb_tensor) { + Status status(SUCCESS); + DataType data_type = pb_tensor.data_type(); + if (data_type == kMSI_Unknown) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Reply data type is unknown"; + } + + if (data_type == kMSI_String || data_type == kMSI_Bytes) { + auto shape = pb_tensor.shape(); + if (shape.size() != 1) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Reply string or bytes, shape should be 1, given shape size:" << shape.size(); + } + } + return status; +} + +void RestfulService::ParseErrorMsg(const proto::ErrorMsg &error, json *const js) { + std::string str = error.error_msg(); + *js = str; +} + +// 5.Parse reply +Status RestfulService::ParseReply(const PredictReply &reply, json *const out_json) { + Status status(SUCCESS); + switch (request_type_) { + case kInstanceType: + status = ParseInstancesReply(reply, out_json); + break; + default: + return INFER_STATUS_LOG_ERROR(FAILED) << "Restful request only support instance mode"; + } + + PrintReply(reply); + return status; +} + +Status RestfulService::ParseInstancesReply(const PredictReply &reply, json *const out_json) { + Status status(SUCCESS); + if (reply.instances_size() != instances_nums_) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Reply Instances size:" << reply.instances_size() << " is not equal to request size:" << instances_nums_; + } + + auto error_size = reply.error_msg_size(); + if (error_size != 0 && error_size != 1 && error_size != instances_nums_) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Reply error size:" << error_size << " is not 0,1 or instances size"; + } + + (*out_json)[kInstancesReply] = json(); + json &instances_json = (*out_json)[kInstancesReply]; + + for (int32_t i = 0; i < instances_nums_; i++) { + bool success_flag = true; + if (i < error_size) { + auto &cur_error = reply.error_msg().at(i); + success_flag = (cur_error.error_code() == 0); + } + + if (success_flag) { + auto &cur_instance = reply.instances(i); + auto &items = cur_instance.items(); + if (items.empty()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Reply instance items is empty"; + } + instances_json.push_back(json()); + auto &instance = instances_json.back(); + + for (auto &item : items) { + instance[item.first] = json(); + auto &value_json = instance[item.first]; + status = ParseReplyDetail(item.second, &value_json); + if (status != SUCCESS) { + return status; + } + } + } else { + instances_json.push_back(json()); + auto &obj = instances_json.back(); + obj[kErrorMsg] = json(); + auto &js = obj[kErrorMsg]; + ParseErrorMsg(reply.error_msg(i), &js); + } + } + return status; +} + +// For test, to be deleted +void RestfulService::FadeReply(const proto::PredictRequest &request, proto::PredictReply &reply) { + MSI_LOG_INFO << "Start"; + if (request_type_ == kInstanceType) { + size_t instances_size = request.instances_size(); + for (size_t i = 0; i < instances_size; i++) { + auto cur_instance = request.instances(i); + auto target_ptr = reply.add_instances(); + for (const auto &item : cur_instance.items()) { + auto key = item.first; + auto tensor = item.second; + auto &map_item = *(target_ptr->mutable_items()); + map_item[key] = tensor; + } + } + } else { + MSI_LOG_ERROR << "only support two kind type"; + } + MSI_LOG_INFO << "End"; +} // namespace serving + +void RestfulService::PrintRequest(const proto::PredictRequest *const request) { + MSI_LOG_INFO << "=============start print quest=================="; + if (request_type_ == kInstanceType) { + size_t size = request->instances_size(); + MSI_LOG_INFO << "instance size:" << request->instances_size(); + for (size_t i = 0; i < size; i++) { + auto instance = request->instances(i); + auto &cur_map = instance.items(); + MSI_LOG_INFO << "map size:" << cur_map.size(); + for (const auto &item : cur_map) { + MSI_LOG_INFO << "======deail instance======="; + auto key = item.first; + auto tensor = item.second; + ProtoTensor pb_tensor(tensor); + MSI_LOG_INFO << "key:" << key; + DataType type = pb_tensor.data_type(); + MSI_LOG_INFO << "data type:" << type; + auto shape = pb_tensor.shape(); + for (const auto &item1 : shape) { + MSI_LOG_INFO << "shape:" << item1; + } + + size_t data_size = pb_tensor.data_size(); + MSI_LOG_INFO << "data size:" << data_size; + + auto data = reinterpret_cast(pb_tensor.data()); + MSI_LOG_INFO << "data value:" << data; + + size_t bytes_data_size = pb_tensor.bytes_data_size(); + MSI_LOG_INFO << "bytest data size:" << bytes_data_size; + for (size_t k = 0; k < bytes_data_size; k++) { + const uint8_t *data1; + size_t bytes_len; + pb_tensor.get_bytes_data(k, data1, bytes_len); + MSI_LOG_INFO << "start ptr:" << data1 << "; bytes length:" << bytes_len; + } + } + } + } else { + MSI_LOG_ERROR << "=========Print Failed"; + } + MSI_LOG_INFO << "=============End print request=================="; +} + +void RestfulService::PrintReply(const proto::PredictReply &reply) { + MSI_LOG_INFO << "=============start print reply=================="; + if (request_type_ == kInstanceType) { + size_t size = reply.instances_size(); + MSI_LOG_INFO << "instance size:" << reply.instances_size(); + for (size_t i = 0; i < size; i++) { + auto instance = reply.instances(i); + auto &cur_map = instance.items(); + MSI_LOG_INFO << "map size:" << cur_map.size(); + for (const auto &item : cur_map) { + MSI_LOG_INFO << "======deail instance======="; + auto key = item.first; + auto tensor = item.second; + ProtoTensor pb_tensor(tensor); + MSI_LOG_INFO << "key:" << key; + DataType type = pb_tensor.data_type(); + MSI_LOG_INFO << "data type:" << type; + auto shape = pb_tensor.shape(); + for (const auto &item1 : shape) { + MSI_LOG_INFO << "shape:" << item1; + } + + size_t data_size = pb_tensor.data_size(); + MSI_LOG_INFO << "data size:" << data_size; + size_t bytes_data_size = pb_tensor.bytes_data_size(); + MSI_LOG_INFO << "bytest data size:" << bytes_data_size; + for (size_t k = 0; k < bytes_data_size; k++) { + const uint8_t *data; + size_t bytes_len; + pb_tensor.get_bytes_data(k, data, bytes_len); + MSI_LOG_INFO << "start ptr:" << data << "; bytes length:" << bytes_len; + } + } + } + } else { + MSI_LOG_ERROR << "=========Print Failed"; + } + MSI_LOG_INFO << "=============End print reply=================="; +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/master/restful/http_process.h b/mindspore_serving/ccsrc/master/restful/http_process.h new file mode 100644 index 0000000..56ed271 --- /dev/null +++ b/mindspore_serving/ccsrc/master/restful/http_process.h @@ -0,0 +1,110 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_HTTP_PROCESS_H +#define MINDSPORE_SERVING_HTTP_PROCESS_H + +#include +#include +#include +#include +#include "proto/ms_service.pb.h" +#include "master/dispacther.h" +#include "common/proto_tensor.h" +#include "master/restful/restful_request.h" + +using nlohmann::json; +using std::string; + +namespace mindspore { +namespace serving { +constexpr auto kInstancesRequest = "instances"; +constexpr auto kInstancesReply = "instances"; +constexpr auto kErrorMsg = "error_msg"; +constexpr auto kType = "type"; +constexpr auto kShape = "shape"; +constexpr auto kB64 = "b64"; + +enum RequestType { kInstanceType = 0, kInvalidType }; +enum InstancesType { kNokeyWay = 0, kKeyWay, kInvalidWay }; +enum HTTP_DATA_TYPE { HTTP_DATA_NONE, HTTP_DATA_INT, HTTP_DATA_FLOAT, HTTP_DATA_BOOL, HTTP_DATA_STR, HTTP_DATA_OBJ }; +class RestfulService { + public: + explicit RestfulService(std::shared_ptr dispatcher) : dispatcher_(dispatcher) {} + ~RestfulService() = default; + Status RunRestful(const std::shared_ptr &restful_request, json *const out_json); + + private: + Status CheckObjTypeMatchShape(DataType data_type, const std::vector &shape); + std::string GetString(const uint8_t *ptr, size_t length); + Status CheckObj(const json &js); + Status CheckObjType(const std::string &type); + DataType GetObjDataType(const json &js); + std::vector GetObjShape(const json &js); + std::vector GetArrayShape(const json &json_array); + std::vector GetSpecifiedShape(const json &js); + DataType GetArrayDataType(const json &json_array, HTTP_DATA_TYPE &type_format); + Status CheckReqJsonValid(const json &js_msg); + std::string GetStringByDataType(DataType type); + bool JsonMatchDataType(const json &js, DataType type); + + template + Status GetScalarData(const json &js, size_t index, bool is_bytes, ProtoTensor *const request_tensor); + Status GetScalarByType(DataType type, const json &js, size_t index, ProtoTensor *const request_tensor); + + Status RecursiveGetArray(const json &json_data, size_t depth, size_t data_index, HTTP_DATA_TYPE type_format, + ProtoTensor *const request_tensor); + Status GetArrayData(const json &js, size_t data_index, HTTP_DATA_TYPE type, ProtoTensor *const request_tensor); + + Status ParseReqCommonMsg(const std::shared_ptr &restful_request, + proto::PredictRequest *const request); + Status ParseRequest(const std::shared_ptr &restful_request, proto::PredictRequest *const request); + Status ParseInstancesMsg(const json &js_msg, proto::PredictRequest *const request); + Status GetInstancesType(const json &instances); + Status ParseKeyInstances(const json &instances, proto::PredictRequest *const request); + Status PaserKeyOneInstance(const json &instance_msg, proto::PredictRequest *const request); + Status ParseItem(const json &value, ProtoTensor *const pb_tensor); + + // parse reply:trans RequestReply to http msg + RequestType GetReqType(const std::string &str); + std::string GetReqTypeStr(RequestType req_type); + Status ParseReply(const proto::PredictReply &reply, json *const out_json); + Status CheckReply(const ProtoTensor &pb_tensor); + Status ParseInstancesReply(const proto::PredictReply &reply, json *const out_json); + Status ParseReplyDetail(proto::Tensor tensor, json *const js); + Status ParseScalar(const ProtoTensor &pb_tensor, size_t index, json *const js); + Status RecursiveParseArray(ProtoTensor pb_tensor, size_t depth, size_t pos, json *const out_json); + + template + Status ParseScalarData(const ProtoTensor &pb_tensor, bool is_bytes, size_t index, json *const js); + template + bool IsString(); + void ParseErrorMsg(const proto::ErrorMsg &error_msg, json *const js); + + void PrintRequest(const proto::PredictRequest *const request); + void PrintReply(const proto::PredictReply &reply); + void FadeReply(const proto::PredictRequest &request, proto::PredictReply &reply); + + RequestType request_type_{kInvalidType}; + InstancesType instances_type_{kInvalidWay}; + int64_t instances_nums_{0}; + std::shared_ptr dispatcher_; + std::vector request_type_list_ = {kInstancesRequest}; +}; + +} // namespace serving +} // namespace mindspore +#endif // MINDSPORE_SERVER_H diff --git a/mindspore_serving/ccsrc/master/restful/restful_request.cc b/mindspore_serving/ccsrc/master/restful/restful_request.cc new file mode 100644 index 0000000..d6c7c29 --- /dev/null +++ b/mindspore_serving/ccsrc/master/restful/restful_request.cc @@ -0,0 +1,208 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "master/restful/restful_request.h" +#include +#include +#include +#include +#include + +static const char UrlKeyModel[] = "model"; +static const char UrlKeyVersion[] = "version"; +static const char UrlSplit[] = "/"; +static const char UrlKeyEnd[] = ":"; + +namespace mindspore { +namespace serving { + +DecomposeEvRequest::DecomposeEvRequest(struct evhttp_request *request, int max_msg_size) + : event_request_(request), max_msg_size_(max_msg_size) {} + +DecomposeEvRequest::~DecomposeEvRequest() { + if (event_request_ && evhttp_request_is_owned(event_request_)) { + evhttp_request_free(event_request_); + } +} + +std::string DecomposeEvRequest::UrlQuery(const std::string &url, const std::string &key) { + std::string::size_type start_pos(0); + if (key == UrlKeyEnd) { + if ((start_pos = url_.find(UrlKeyEnd)) != std::string::npos) { + return url_.substr(start_pos + 1, url_.size()); + } + } + + int key_size = key.size() + 1; + std::string::size_type end_pos(0); + if ((start_pos = url.find(key)) != std::string::npos) { + end_pos = std::min(url.find(UrlSplit, start_pos + key_size), url.find(UrlKeyEnd, start_pos + key_size)); + if (end_pos != std::string::npos) { + return url.substr(start_pos + key_size, end_pos - start_pos - key_size); + } + } + return ""; +} + +Status DecomposeEvRequest::GetPostMessageToJson() { + Status status(SUCCESS); + std::string message; + size_t input_size = evbuffer_get_length(event_request_->input_buffer); + if (input_size == 0) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "http message invalid"); + return status; + } else if (input_size > max_msg_size_) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "http message is bigger than " + std::to_string(max_msg_size_)); + return status; + } else { + message.resize(input_size); + auto src_data = evbuffer_pullup(event_request_->input_buffer, -1); + if (src_data == nullptr) { + ERROR_INFER_STATUS(status, FAILED, "get http message failed."); + return status; + } + if (memcpy_s(message.data(), input_size, src_data, input_size) != EOK) { + ERROR_INFER_STATUS(status, FAILED, "copy http message failed."); + return status; + } + } + MSI_LOG_INFO << "get request message: [" << message << "]"; + MSI_TIME_STAMP_START(ParseJson) + try { + request_message_ = nlohmann::json::parse(message); + } catch (nlohmann::json::exception &e) { + std::string json_exception = e.what(); + std::string error_message = "Illegal JSON format." + json_exception; + ERROR_INFER_STATUS(status, INVALID_INPUTS, error_message); + return status; + } + MSI_TIME_STAMP_END(ParseJson) + + return status; +} + +Status DecomposeEvRequest::CheckRequestMethodValid() { + Status status(SUCCESS); + switch (evhttp_request_get_command(event_request_)) { + case EVHTTP_REQ_POST: + request_method_ = "POST"; + return status; + default: + ERROR_INFER_STATUS(status, INVALID_INPUTS, "http message only support POST right now"); + return status; + } +} + +Status DecomposeEvRequest::Decompose() { + Status status(SUCCESS); + status = CheckRequestMethodValid(); + if (status != SUCCESS) { + return status; + } + + status = GetPostMessageToJson(); + if (status != SUCCESS) { + return status; + } + + // eg: /model/resnet/version/1:predict + url_ = evhttp_request_get_uri(event_request_); + if (url_.empty()) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "evhttp url is empty."); + return status; + } + MSI_LOG_INFO << "url_: " << url_; + + model_name_ = UrlQuery(url_, UrlKeyModel); + if (model_name_.empty()) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "please check url, the keyword:[model] must contain."); + return status; + } + MSI_LOG_INFO << "model_name_: " << model_name_; + if (url_.find(UrlKeyVersion) != std::string::npos) { + auto version_str = UrlQuery(url_, UrlKeyVersion); + try { + version_ = std::stol(version_str); + } catch (const std::invalid_argument &) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "please check url, the keyword:[version] must contain."); + return status; + } catch (const std::out_of_range &) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "please check url, the keyword:[version] out of range."); + return status; + } + MSI_LOG_INFO << "version_: " << version_; + } + + service_method_ = UrlQuery(url_, UrlKeyEnd); + if (service_method_.empty()) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "please check url, the keyword:[service method] must contain."); + return status; + } + MSI_LOG_INFO << "service_method_: " << service_method_; + return status; +} + +RestfulRequest::RestfulRequest(std::shared_ptr request) + : decompose_event_request_(std::move(request)) {} + +RestfulRequest::~RestfulRequest() { + if (replay_buffer_ != nullptr) { + evbuffer_free(replay_buffer_); + } +} + +Status RestfulRequest::RestfulReplayBufferInit() { + Status status(SUCCESS); + replay_buffer_ = evbuffer_new(); + if (replay_buffer_ == nullptr) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "creat restful replay buffer fail"); + return status; + } + return status; +} + +Status RestfulRequest::RestfulReplay(const std::string &replay) { + Status status(SUCCESS); + if (replay_buffer_ == nullptr) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "replay_buffer_ is nullptr"); + return status; + } + if (decompose_event_request_ == nullptr) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "replay_buffer_ is nullptr"); + return status; + } + if (decompose_event_request_->event_request_ == nullptr) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "replay_buffer_ is nullptr"); + return status; + } + MSI_LOG_INFO << "replay message: " << replay; + evbuffer_add(replay_buffer_, replay.data(), replay.size()); + evhttp_send_reply(decompose_event_request_->event_request_, HTTP_OK, "Client", replay_buffer_); + return status; +} + +Status RestfulRequest::ErrorMessage(Status status) { + Status error_status(SUCCESS); + nlohmann::json error_json = {{"error_message", status.StatusMessage()}}; + std::string out_error_str = error_json.dump(); + if ((error_status = RestfulReplay(out_error_str)) != SUCCESS) { + return error_status; + } + return error_status; +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/master/restful/restful_request.h b/mindspore_serving/ccsrc/master/restful/restful_request.h new file mode 100644 index 0000000..a1b13d3 --- /dev/null +++ b/mindspore_serving/ccsrc/master/restful/restful_request.h @@ -0,0 +1,70 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_RESTFUL_REQUEST_H +#define MINDSPORE_RESTFUL_REQUEST_H + +#include +#include +#include +#include +#include +#include "common/serving_common.h" + +namespace mindspore { +namespace serving { + +class DecomposeEvRequest { + public: + explicit DecomposeEvRequest(struct evhttp_request *request, int max_msg_size); + ~DecomposeEvRequest(); + std::string UrlQuery(const std::string &url, const std::string &key); + Status CheckRequestMethodValid(); + Status Decompose(); + Status GetPostMessageToJson(); + + evhttp_request *event_request_; + std::string request_method_; + std::string model_name_; + std::string url_; + std::string service_method_; + uint32_t version_{}; + uint32_t max_msg_size_{}; + nlohmann::json request_message_; +}; + +class RestfulRequest { + public: + explicit RestfulRequest(std::shared_ptr request); + ~RestfulRequest(); + + RestfulRequest(const RestfulRequest &other) = delete; + RestfulRequest &operator=(const RestfulRequest &other) = delete; + + Status RestfulReplayBufferInit(); + Status RestfulReplay(const std::string &replay); + Status ErrorMessage(Status status); + std::shared_ptr decompose_event_request() { return decompose_event_request_; } + + private: + std::shared_ptr decompose_event_request_{nullptr}; + evbuffer *replay_buffer_ = nullptr; +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_RESTFUL_REQUEST_H diff --git a/mindspore_serving/ccsrc/master/restful/restful_server.cc b/mindspore_serving/ccsrc/master/restful/restful_server.cc new file mode 100644 index 0000000..60de127 --- /dev/null +++ b/mindspore_serving/ccsrc/master/restful/restful_server.cc @@ -0,0 +1,211 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "master/restful/restful_server.h" +#include +#include +#include +#include "master/restful/http_handle.h" +namespace mindspore::serving { + +int id = 0; +Status RestfulServer::Run(const std::shared_ptr &restful_request) { + Status status(SUCCESS); + // MSI_TIME_STAMP_START(DoSometing) + // // ke cai start --- sync + // ++id; + // for (int i = 0; i < 10; ++i) { + // MSI_LOG_INFO << "id:" << id << "--" << i << " "; + // usleep(600); + // } + // MSI_TIME_STAMP_END(DoSometing) + // // ----------------- + + // // st error + // if (id == 5) { + // ERROR_INFER_STATUS(status, INVALID_INPUTS, "id == 5"); + // if ((status = restful_request->ErrorMessage(status)) != SUCCESS) { + // return status; + // } + // return status; + // } + nlohmann::json predict_json; + std::string predict_str; + status = HandleRestfulRequest(restful_request, &predict_json); + if (status != SUCCESS) { + predict_str = status.StatusMessage(); + nlohmann::json js; + js["error_msg"] = predict_str; + predict_str = js.dump(); + } else { + predict_str = predict_json.dump(); + } + restful_request->RestfulReplay(predict_str); + return status; +} + +void RestfulServer::Committer(const std::shared_ptr &restful_request) { + thread_pool_.commit([restful_request, this]() { Run(restful_request); }); +} + +void RestfulServer::DispatchEvHttpRequest(evhttp_request *request) { + Status status(SUCCESS); + + auto de_request = std::make_unique(request, max_msg_size_); + if (de_request == nullptr) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "de_request is nullptr."); + return; + } + Status de_status = de_request->Decompose(); + auto restful_request = std::make_shared(std::move(de_request)); + if (restful_request == nullptr) { + ERROR_INFER_STATUS(status, INVALID_INPUTS, "restful_request is nullptr."); + return; + } + status = restful_request->RestfulReplayBufferInit(); + if (status != SUCCESS) { + if ((status = restful_request->ErrorMessage(status)) != SUCCESS) { + return; + } + return; + } + + if (de_status != SUCCESS) { + (void)restful_request->ErrorMessage(de_status); + return; + } + Committer(restful_request); +} + +void RestfulServer::EvCallBack(evhttp_request *request, void *arg) { + auto *restful_server = static_cast(arg); + restful_server->DispatchEvHttpRequest(const_cast(request)); +} + +Status RestfulServer::CreatRestfulServer(int time_out_second) { + evthread_use_pthreads(); + auto free_event_base = [this] { + if (event_base_ != nullptr) { + event_base_free(event_base_); + event_base_ = nullptr; + } + }; + + event_base_ = event_base_new(); + Status status(SUCCESS); + if (event_base_ == nullptr) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) + << "Serving Error: RESTful server start failed, new http event failed"; + return status; + } + event_http_ = evhttp_new(event_base_); + if (event_http_ == nullptr) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) + << "Serving Error: RESTful server start failed, create http server faild"; + free_event_base(); + return status; + } + + evhttp_set_gencb(event_http_, &EvCallBack, this); + evhttp_set_timeout(event_http_, time_out_second); + return SUCCESS; +} + +Status RestfulServer::StartRestfulServer() { + auto free_event_base = [this] { + if (event_base_ != nullptr) { + event_base_free(event_base_); + event_base_ = nullptr; + } + }; + auto free_evhttp = [this] { + if (event_http_ != nullptr) { + evhttp_free(event_http_); + event_http_ = nullptr; + } + }; + + Status status(SUCCESS); + struct sockaddr_in sin = {}; + sin.sin_family = AF_INET; + sin.sin_port = htons(restful_port_); + auto listener = evconnlistener_new_bind(event_base_, nullptr, nullptr, + LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_EXEC | LEV_OPT_CLOSE_ON_FREE, -1, + reinterpret_cast(&sin), sizeof(sin)); + + if (listener == nullptr) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) + << "Serving Error: RESTful server start failed, create http listener faild, port " << restful_port_; + free_event_base(); + free_evhttp(); + return status; + } + auto bound = evhttp_bind_listener(event_http_, listener); + if (bound == nullptr) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) + << "Serving Error: RESTful server start failed, bind http listener to server faild, port " + << restful_port_; + evconnlistener_free(listener); + free_event_base(); + free_evhttp(); + return status; + } + + auto event_http_run = [this]() { + MSI_LOG(INFO) << "MS Serving restful listening on " << restful_ip_ << ":" << restful_port_; + std::cout << "Serving: MS Serving RESTful start success, listening on " << restful_ip_ << ":" << restful_port_ + << std::endl; + event_base_dispatch(event_base_); + }; + event_thread_ = std::thread(event_http_run); + in_running_ = true; + return SUCCESS; +} + +Status RestfulServer::Start(const std::string &ip, uint32_t restful_port, int max_msg_size, int time_out_second) { + Status status(SUCCESS); + + restful_ip_ = ip; + restful_port_ = restful_port; + max_msg_size_ = static_cast(max_msg_size * (uint32_t(1) << 20)); + + status = CreatRestfulServer(time_out_second); + if (status != SUCCESS) { + return status; + } + status = StartRestfulServer(); + if (status != SUCCESS) { + return status; + } + return status; +} + +void RestfulServer::Stop() { + if (in_running_) { + event_base_loopexit(event_base_, nullptr); + event_thread_.join(); + } + if (event_base_ != nullptr) { + event_base_free(event_base_); + event_base_ = nullptr; + } + if (event_http_ != nullptr) { + evhttp_free(event_http_); + event_http_ = nullptr; + } +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/master/restful/restful_server.h b/mindspore_serving/ccsrc/master/restful/restful_server.h new file mode 100644 index 0000000..50c46ce --- /dev/null +++ b/mindspore_serving/ccsrc/master/restful/restful_server.h @@ -0,0 +1,70 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_RESTFUL_SERVER_H +#define MINDSPORE_RESTFUL_SERVER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "master/restful/restful_request.h" +#include "common/serving_common.h" +#include "common/thread_pool.h" + +namespace mindspore::serving { + +constexpr const uint32_t kDefaultRestfulThreadPoolNum = 20; + +typedef void (*RestfulFunc)(struct evhttp_request *const req, void *const arg); + +class MS_API RestfulServer { + public: + RestfulServer() : thread_pool_(kDefaultRestfulThreadPoolNum) {} + ~RestfulServer() { Stop(); } + + Status Start(const std::string &ip, uint32_t restful_port, int max_msg_size, int time_out_second); + void Stop(); + + private: + Status CreatRestfulServer(int time_out_second); + static void EvCallBack(evhttp_request *request, void *arg); + void DispatchEvHttpRequest(evhttp_request *request); + void Committer(const std::shared_ptr &restful_request); + Status Run(const std::shared_ptr &restful_request); + Status StartRestfulServer(); + + std::string restful_ip_; + uint32_t restful_port_{}; + int max_msg_size_{}; + bool in_running_ = false; + + struct evhttp *event_http_ = nullptr; + struct event_base *event_base_ = nullptr; + std::thread event_thread_; + ThreadPool thread_pool_; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_RESTFUL_SERVER_H diff --git a/mindspore_serving/ccsrc/master/server.cc b/mindspore_serving/ccsrc/master/server.cc new file mode 100644 index 0000000..2e21017 --- /dev/null +++ b/mindspore_serving/ccsrc/master/server.cc @@ -0,0 +1,70 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "master/server.h" + +#include +#include +#include +#include +#include +#include + +#include "common/proto_tensor.h" +#include "common/serving_common.h" +#include "common/exit_handle.h" +#include "master/dispacther.h" +#include "master/grpc/grpc_process.h" +#include "master/restful/http_process.h" +#include "worker/context.h" + +namespace mindspore { +namespace serving { + +Status Server::StartGrpcServer(const std::string &ip, uint32_t grpc_port, int max_msg_mb_size) { + if (max_msg_mb_size > gRpcMaxMBMsgSize) { + MSI_LOG_WARNING << "The maximum Serving gRPC message size is 512MB and will be updated from " << max_msg_mb_size + << "MB to 512MB"; + max_msg_mb_size = gRpcMaxMBMsgSize; + } + return grpc_server_.Start(std::make_shared(dispatcher_), ip, grpc_port, max_msg_mb_size, + "Serving gRPC"); +} + +Status Server::StartGrpcMasterServer(const std::string &ip, uint32_t grpc_port) { + return grpc_manager_server_.Start(std::make_shared(dispatcher_), ip, grpc_port, -1, "Master gRPC"); +} + +Status Server::StartRestfulServer(const std::string &ip, uint32_t restful_port, int max_msg_mb_size, + int time_out_second) { + return restful_server_.Start(ip, restful_port, max_msg_mb_size, time_out_second); + // restful_server.Start(http_handler_msg, ip, restful_port, max_msg_mb_size, time_out_second); +} + +void Server::Clear() { dispatcher_->Clear(); } + +Server::Server() = default; + +Server &Server::Instance() { + static Server server; + ExitHandle::Instance().InitSignalHandle(); + return server; +} + +Server::~Server() = default; + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/master/server.h b/mindspore_serving/ccsrc/master/server.h new file mode 100644 index 0000000..953de1c --- /dev/null +++ b/mindspore_serving/ccsrc/master/server.h @@ -0,0 +1,51 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MINDSPORE_SERVER_H +#define MINDSPORE_SERVER_H + +#include +#include +#include "common/serving_common.h" +#include "common/grpc_server.h" +#include "master/restful/restful_server.h" +#include "master/dispacther.h" + +namespace mindspore { +namespace serving { + +class MS_API Server { + public: + Server(); + ~Server(); + Status StartGrpcServer(const std::string &ip, uint32_t grpc_port, int max_msg_mb_size = gRpcDefaultMsgMBSize); + Status StartGrpcMasterServer(const std::string &ip, uint32_t grpc_port); + Status StartRestfulServer(const std::string &ip, uint32_t restful_port, int max_msg_mb_size = gRpcDefaultMsgMBSize, + int time_out_second = 100); + void Clear(); + + std::shared_ptr GetDispatcher() { return dispatcher_; } + + static Server &Instance(); + + private: + GrpcServer grpc_server_; + GrpcServer grpc_manager_server_; + RestfulServer restful_server_; + std::shared_ptr dispatcher_ = std::make_shared(); +}; +} // namespace serving +} // namespace mindspore +#endif // MINDSPORE_SERVER_H diff --git a/mindspore_serving/ccsrc/python/master/master_py.cc b/mindspore_serving/ccsrc/python/master/master_py.cc new file mode 100644 index 0000000..11fcd2f --- /dev/null +++ b/mindspore_serving/ccsrc/python/master/master_py.cc @@ -0,0 +1,52 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "python/master/master_py.h" +#include "common/exit_handle.h" +#include "worker/task_queue.h" + +namespace mindspore::serving { + +void PyMaster::StartGrpcServer(const std::string &ip, uint32_t grpc_port, int max_msg_mb_size) { + auto status = Server::Instance().StartGrpcServer(ip, grpc_port, max_msg_mb_size); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } +} + +void PyMaster::StartGrpcMasterServer(const std::string &ip, uint32_t grpc_port) { + auto status = Server::Instance().StartGrpcMasterServer(ip, grpc_port); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } +} + +void PyMaster::StartRestfulServer(const std::string &ip, uint32_t grpc_port, int max_msg_mb_size) { + auto status = Server::Instance().StartRestfulServer(ip, grpc_port, max_msg_mb_size); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } +} + +void PyMaster::WaitAndClear() { + ExitHandle::Instance().MasterWait(); + Server::Instance().Clear(); + MSI_LOG_INFO << "Python server end wait and clear"; +} + +void PyMaster::Stop() { ExitHandle::Instance().Stop(); } + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/python/master/master_py.h b/mindspore_serving/ccsrc/python/master/master_py.h new file mode 100644 index 0000000..f000da9 --- /dev/null +++ b/mindspore_serving/ccsrc/python/master/master_py.h @@ -0,0 +1,49 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVER_PY_H +#define MINDSPORE_SERVER_PY_H + +#include +#include +#include +#include +#include +#include "common/serving_common.h" +#include "master/grpc/grpc_process.h" +#include "common/grpc_server.h" +#include "master/restful/restful_server.h" +#include "master/server.h" +#include "master/dispacther.h" + +namespace py = pybind11; + +namespace mindspore { +namespace serving { + +class MS_API PyMaster { + public: + static void StartGrpcServer(const std::string &ip, uint32_t grpc_port, int max_msg_mb_size = 100); + static void StartGrpcMasterServer(const std::string &ip, uint32_t grpc_port); + static void StartRestfulServer(const std::string &ip, uint32_t grpc_port, int max_msg_mb_size = 100); + static void WaitAndClear(); + static void Stop(); +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVER_PY_H diff --git a/mindspore_serving/ccsrc/python/serving_py.cc b/mindspore_serving/ccsrc/python/serving_py.cc new file mode 100644 index 0000000..008f385 --- /dev/null +++ b/mindspore_serving/ccsrc/python/serving_py.cc @@ -0,0 +1,146 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "python/worker/preprocess_py.h" +#include "python/worker/postprocess_py.h" +#include "python/worker/worker_py.h" +#include "python/tensor_py.h" +#include "common/servable.h" +#include "worker/context.h" +#include "python/master/master_py.h" +#include "master/dispacther.h" + +namespace mindspore::serving { + +PYBIND11_MODULE(_mindspore_serving, m) { + py::class_>(m, "PreprocessStorage_") + .def(py::init<>()) + .def_static("get_instance", &PyPreprocessStorage::Instance) + .def("register", &PyPreprocessStorage::Register) + .def("get_pycpp_preprocess_info", &PyPreprocessStorage::GetPyCppPreprocessInfo); + + py::class_>(m, "PostprocessStorage_") + .def(py::init<>()) + .def_static("get_instance", &PyPostprocessStorage::Instance) + .def("register", &PyPostprocessStorage::Register) + .def("get_pycpp_postprocess_info", &PyPostprocessStorage::GetPyCppPostprocessInfo); + + py::enum_(m, "PredictPhaseTag_") + .value("kPredictPhaseTag_Input", PredictPhaseTag::kPredictPhaseTag_Input) + .value("kPredictPhaseTag_Preproces", PredictPhaseTag::kPredictPhaseTag_Preproces) + .value("kPredictPhaseTag_Predict", PredictPhaseTag::kPredictPhaseTag_Predict) + .value("kPredictPhaseTag_Postprocess", PredictPhaseTag::kPredictPhaseTag_Postprocess) + .export_values(); + + py::class_(m, "MethodSignature_") + .def(py::init<>()) + .def_readwrite("method_name", &MethodSignature::method_name) + .def_readwrite("inputs", &MethodSignature::inputs) + .def_readwrite("outputs", &MethodSignature::outputs) + .def_readwrite("preprocess_name", &MethodSignature::preprocess_name) + .def_readwrite("preprocess_inputs", &MethodSignature::preprocess_inputs) + .def_readwrite("postprocess_name", &MethodSignature::postprocess_name) + .def_readwrite("postprocess_inputs", &MethodSignature::postprocess_inputs) + .def_readwrite("servable_name", &MethodSignature::servable_name) + .def_readwrite("servable_inputs", &MethodSignature::servable_inputs) + .def_readwrite("returns", &MethodSignature::returns); + + py::class_(m, "RequestSpec_") + .def(py::init<>()) + .def_readwrite("servable_name", &RequestSpec::servable_name) + .def_readwrite("version_number", &RequestSpec::version_number) + .def_readwrite("method_name", &RequestSpec::method_name); + + py::class_(m, "ServableMeta_") + .def(py::init<>()) + .def_readwrite("servable_name", &ServableMeta::servable_name) + .def_readwrite("inputs_count", &ServableMeta::inputs_count) + .def_readwrite("outputs_count", &ServableMeta::outputs_count) + .def_readwrite("servable_file", &ServableMeta::servable_file) + .def_readwrite("with_batch_dim", &ServableMeta::with_batch_dim) + .def("set_model_format", &ServableMeta::SetModelFormat); + + py::class_(m, "ServableSignature_") + .def(py::init<>()) + .def_readwrite("servable_meta", &ServableSignature::servable_meta) + .def_readwrite("methods", &ServableSignature::methods); + + py::class_>(m, "ServableStorage_") + .def(py::init<>()) + .def_static("get_instance", &ServableStorage::Instance) + .def("register_servable", &ServableStorage::Register) + .def("register_servable_input_output_info", &ServableStorage::RegisterInputOutputInfo) + .def("get_servable_input_output_info", &ServableStorage::GetInputOutputInfo) + .def("register_method", &ServableStorage::RegisterMethod) + .def("declare_servable", &ServableStorage::DeclareServable); + + py::class_(m, "TaskContext_").def(py::init<>()); + + py::class_(m, "TaskItem_") + .def(py::init<>()) + .def_readwrite("task_type", &TaskItem::task_type) + .def_readwrite("name", &TaskItem::name) + .def_property_readonly("instance_list", + [](const TaskItem &item) { + py::tuple instances(item.instance_list.size()); + for (size_t i = 0; i < item.instance_list.size(); i++) { + instances[i] = PyTensor::AsNumpyTuple(item.instance_list[i].data); + } + return instances; + }) + .def_readwrite("context_list", &TaskItem::context_list); + + py::class_(m, "Worker_") + .def_static("start_servable", &PyWorker::StartServable) + .def_static("start_servable_in_master", &PyWorker::StartServableInMaster) + .def_static("get_batch_size", &PyWorker::GetBatchSize) + .def_static("wait_and_clear", &PyWorker::WaitAndClear, py::call_guard()) + .def_static("stop", PyWorker::Stop) + .def_static("get_py_task", &PyWorker::GetPyTask, py::call_guard()) + .def_static("try_get_preprocess_py_task", &PyWorker::TryGetPreprocessPyTask) + .def_static("try_get_postprocess_py_task", &PyWorker::TryGetPostprocessPyTask) + .def_static("push_preprocess_result", &PyWorker::PushPreprocessPyResult) + .def_static("push_preprocess_failed", &PyWorker::PushPreprocessPyFailed) + .def_static("push_postprocess_result", &PyWorker::PushPostprocessPyResult) + .def_static("push_postprocess_failed", &PyWorker::PushPostprocessPyFailed); + + py::class_>(m, "Context_") + .def(py::init<>()) + .def_static("get_instance", &ServableContext::Instance) + .def("set_device_type_str", + [](ServableContext &context, const std::string &device_type) { + auto status = context.SetDeviceTypeStr(device_type); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } + }) + .def("set_device_id", &ServableContext::SetDeviceId); + + py::class_>(m, "Master_") + .def_static("start_grpc_server", &PyMaster::StartGrpcServer) + .def_static("start_grpc_master_server", &PyMaster::StartGrpcMasterServer) + .def_static("start_restful_server", &PyMaster::StartRestfulServer) + .def_static("wait_and_clear", &PyMaster::WaitAndClear, py::call_guard()) + .def_static("stop", &PyMaster::Stop); + + (void)py::module::import("atexit").attr("register")(py::cpp_function{[&]() -> void { + Server::Instance().Clear(); + Worker::GetInstance().Clear(); + }}); +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/python/tensor_py.cc b/mindspore_serving/ccsrc/python/tensor_py.cc new file mode 100644 index 0000000..cc70a57 --- /dev/null +++ b/mindspore_serving/ccsrc/python/tensor_py.cc @@ -0,0 +1,259 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "python/tensor_py.h" +#include +#include +#include +#include +#include "mindspore_serving/ccsrc/common/tensor.h" + +namespace mindspore::serving { + +static std::vector GetStrides(const std::vector &shape, ssize_t item_size) { + std::vector strides; + strides.reserve(shape.size()); + const auto ndim = shape.size(); + for (size_t i = 0; i < ndim; ++i) { + auto stride = item_size; + for (size_t j = i + 1; j < ndim; ++j) { + stride *= shape[j]; + } + strides.push_back(stride); + } + return strides; +} + +DataType NumpyTensor::GetDataType(const py::buffer_info &buf) { + if (buf.format.size() == 1) { + switch (buf.format.front()) { + case 'e': + case 'f': + case 'd': + switch (buf.itemsize) { + case 2: + return kMSI_Float16; + case 4: + return kMSI_Float32; + case 8: + return kMSI_Float64; + } + break; + case 'b': + case 'h': + case 'i': + case 'l': + case 'q': + switch (buf.itemsize) { + case 1: + return kMSI_Int8; + case 2: + return kMSI_Int16; + case 4: + return kMSI_Int32; + case 8: + return kMSI_Int64; + } + break; + case 'B': + case 'H': + case 'I': + case 'L': + case 'Q': + switch (buf.itemsize) { + case 1: + return kMSI_Uint8; + case 2: + return kMSI_Uint16; + case 4: + return kMSI_Uint32; + case 8: + return kMSI_Uint64; + } + break; + case '?': + return kMSI_Bool; + } + } + MSI_LOG(WARNING) << "Unsupported DataType format " << buf.format << " item size " << buf.itemsize; + return kMSI_Unknown; +} + +static std::string GetPyTypeFormat(DataType data_type) { + switch (data_type) { + case kMSI_Float16: + return "e"; + case kMSI_Float32: + return py::format_descriptor::format(); + case kMSI_Float64: + return py::format_descriptor::format(); + case kMSI_Uint8: + return py::format_descriptor::format(); + case kMSI_Uint16: + return py::format_descriptor::format(); + case kMSI_Uint32: + return py::format_descriptor::format(); + case kMSI_Uint64: + return py::format_descriptor::format(); + case kMSI_Int8: + return py::format_descriptor::format(); + case kMSI_Int16: + return py::format_descriptor::format(); + case kMSI_Int32: + return py::format_descriptor::format(); + case kMSI_Int64: + return py::format_descriptor::format(); + case kMSI_Bool: + return py::format_descriptor::format(); + default: + MSI_LOG(WARNING) << "Unsupported DataType " << data_type << "."; + return ""; + } +} + +static bool IsCContiguous(const py::array &input) { + auto flags = static_cast(input.flags()); + return (flags & pybind11::detail::npy_api::NPY_ARRAY_C_CONTIGUOUS_) != 0; +} + +TensorBasePtr PyTensor::MakeTensor(const py::array &input) { + // Get input buffer info. + py::buffer_info buf = input.request(); + // Check data types. + auto buf_type = NumpyTensor::GetDataType(buf); + if (buf_type == kMSI_Unknown) { + MSI_LOG(EXCEPTION) << "Unsupported tensor type!"; + } + // Convert input array to C contiguous if need. + std::unique_ptr tmp_buf; + if (!IsCContiguous(input)) { + Py_buffer pybuf; + if (PyObject_GetBuffer(input.ptr(), &pybuf, PyBUF_ANY_CONTIGUOUS)) { + MSI_LOG(EXCEPTION) << "Failed to get buffer from the input!"; + } + tmp_buf = std::make_unique(pybuf.len); + if (PyBuffer_ToContiguous(tmp_buf.get(), &pybuf, pybuf.len, 'C')) { + MSI_LOG(EXCEPTION) << "Can't copy numpy.ndarray to a contiguous buffer."; + } + PyBuffer_Release(&pybuf); + buf.ptr = tmp_buf.get(); + } + // Get tensor shape. + std::vector shape(buf.shape.begin(), buf.shape.end()); + return std::make_shared(buf_type, shape, buf.ptr, buf.size * buf.itemsize); +} + +/// Creates a Tensor from a numpy array without copy +TensorBasePtr PyTensor::MakeTensorNoCopy(const py::array &input) { + // Check format. + if (!IsCContiguous(input)) { + MSI_LOG(EXCEPTION) << "Array should be C contiguous."; + } + // Get input buffer info. + py::buffer_info buf = input.request(); + // Get tensor dtype and check it. + auto dtype = NumpyTensor::GetDataType(buf); + if (dtype == kMSI_Unknown) { + MSI_LOG(EXCEPTION) << "Unsupported data type!"; + } + // Make a tensor with shared data with numpy array. + auto tensor_data = std::make_shared(std::move(buf)); + return tensor_data; +} + +py::array PyTensor::AsNumpy(TensorBasePtr tensor, bool copy) { + auto data_numpy = std::dynamic_pointer_cast(tensor); + if (data_numpy) { + return data_numpy->py_array(); + } + if (tensor->is_bytes_val_data()) { + if (tensor->bytes_data_size() != 1) { + return py::array(); + } + const uint8_t *data = nullptr; + size_t bytes_len = 0; + tensor->get_bytes_data(0, data, bytes_len); + std::vector shape{static_cast(bytes_len)}; + std::vector strides = GetStrides(shape, sizeof(uint8_t)); + py::buffer_info info(reinterpret_cast(const_cast(data)), sizeof(uint8_t), + py::format_descriptor::format(), 1, shape, strides); + if (!copy) { + py::array self; + return py::array(py::dtype(info), info.shape, info.strides, info.ptr, self); + } else { + return py::array(py::dtype(info), info.shape, info.strides, info.ptr); + } + } else { + const auto &tensor_shape = tensor->shape(); + std::vector shape(tensor_shape.begin(), tensor_shape.end()); + std::vector strides = GetStrides(shape, tensor->itemsize()); + py::buffer_info info(reinterpret_cast(const_cast(tensor->data())), + static_cast(tensor->itemsize()), GetPyTypeFormat(tensor->data_type()), + static_cast(tensor_shape.size()), shape, strides); + + if (!copy) { + py::array self; + return py::array(py::dtype(info), info.shape, info.strides, info.ptr, self); + } else { + return py::array(py::dtype(info), info.shape, info.strides, info.ptr); + } + } +} + +py::tuple PyTensor::AsNumpyTuple(const InstanceData &instance_data) { + py::tuple numpy_inputs_tuple(instance_data.size()); + for (size_t i = 0; i < instance_data.size(); i++) { // inputs + numpy_inputs_tuple[i] = PyTensor::AsNumpy(instance_data[i]); + } + return numpy_inputs_tuple; +} + +InstanceData PyTensor::AsInstanceData(const py::tuple &tuple) { + InstanceData instance_data; + for (auto &item : tuple) { + TensorBasePtr tensor = nullptr; + if (py::isinstance(item)) { + tensor = std::make_shared(); + tensor->set_data_type(serving::kMSI_String); + auto val = item.cast(); + tensor->add_bytes_data(reinterpret_cast(val.data()), val.length()); + } else if (py::isinstance(item)) { + tensor = std::make_shared(); + tensor->set_data_type(serving::kMSI_Bytes); + auto val = std::string(item.cast()); + tensor->add_bytes_data(reinterpret_cast(val.data()), val.length()); + } else if (py::isinstance(item)) { + auto val = item.cast(); + tensor = std::make_shared(serving::kMSI_Bool, std::vector(), &val, sizeof(val)); + } else if (py::isinstance(item)) { + auto val = item.cast(); + tensor = std::make_shared(serving::kMSI_Int32, std::vector(), &val, sizeof(val)); + } else if (py::isinstance(item)) { + auto val = item.cast(); + tensor = std::make_shared(serving::kMSI_Float32, std::vector(), &val, sizeof(val)); + } else { + try { + tensor = PyTensor::MakeTensorNoCopy(py::cast(item)); + } catch (const std::runtime_error &error) { + MSI_LOG_EXCEPTION << "Get illegal result data with type " << py::str(item.get_type()).cast(); + } + } + instance_data.push_back(tensor); + } + return instance_data; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/python/tensor_py.h b/mindspore_serving/ccsrc/python/tensor_py.h new file mode 100644 index 0000000..eaf7e79 --- /dev/null +++ b/mindspore_serving/ccsrc/python/tensor_py.h @@ -0,0 +1,98 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_SERVING_PY_H +#define MINDSPORE_SERVING_SERVING_PY_H + +#include +#include +#include +#include +#include +#include "common/serving_common.h" +#include "common/instance.h" + +namespace py = pybind11; + +namespace mindspore::serving { + +class NumpyTensor : public TensorBase { + public: + explicit NumpyTensor(py::buffer_info &&buffer) : buffer_(std::move(buffer)) {} + ~NumpyTensor() { + py::gil_scoped_acquire acquire; + { buffer_ = py::buffer_info(); } + } + /// py::array object. + py::array py_array() const { + // Use dummy owner to avoid copy data. + py::str dummyOwner; + return py::array(py::dtype(buffer_), buffer_.shape, buffer_.strides, buffer_.ptr, dummyOwner); + } + + void set_data_type(DataType type) override { + MSI_LOG_EXCEPTION << "NumpyTensor is readyonly, cannot invoke set_data_type"; + } + DataType data_type() const override { return GetDataType(buffer_); } + + void set_shape(const std::vector &shape) override { + MSI_LOG_EXCEPTION << "NumpyTensor is readyonly, cannot invoke set_shape"; + } + std::vector shape() const override { return buffer_.shape; } + + const uint8_t *data() const override { return static_cast(buffer_.ptr); } + size_t data_size() const override { return buffer_.size * buffer_.itemsize; } + + bool resize_data(size_t data_len) override { + MSI_LOG_EXCEPTION << "NumpyTensor is readyonly, cannot invoke resize_data"; + return false; + } + uint8_t *mutable_data() override { + MSI_LOG_EXCEPTION << "NumpyTensor is readyonly, cannot invoke mutable_data"; + return nullptr; + } + + void clear_bytes_data() override { MSI_LOG_EXCEPTION << "NumpyTensor is readyonly, cannot invoke clear_bytes_data"; } + void add_bytes_data(const uint8_t *data, size_t bytes_len) override { + MSI_LOG_EXCEPTION << "NumpyTensor is readyonly, cannot invoke add_bytes_data"; + } + + size_t bytes_data_size() const override { return 0; } + void get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const override { + MSI_LOG_EXCEPTION << "NumpyTensor is readyonly, cannot invoke get_bytes_data"; + } + + static DataType GetDataType(const py::buffer_info &buf); + + private: + py::buffer_info buffer_; +}; + +class PyTensor { + public: + // For all type, but for BYTES type, there can only be one item in bytes_val. + // If the tensor data is destroyed when the numpy array is return to python env, the tensor data need to be copied + static py::array AsNumpy(TensorBasePtr tensor, bool copy = false); + static TensorBasePtr MakeTensor(const py::array &input); + static TensorBasePtr MakeTensorNoCopy(const py::array &input); + + static py::tuple AsNumpyTuple(const InstanceData &instance); + static InstanceData AsInstanceData(const py::tuple &tuple); +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_SERVING_PY_H diff --git a/mindspore_serving/ccsrc/python/worker/postprocess_py.cc b/mindspore_serving/ccsrc/python/worker/postprocess_py.cc new file mode 100644 index 0000000..5a1763b --- /dev/null +++ b/mindspore_serving/ccsrc/python/worker/postprocess_py.cc @@ -0,0 +1,82 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "python/worker/postprocess_py.h" +#include + +namespace mindspore::serving { + +size_t PyPostprocess::GetInputsCount(const std::string &postprocess_name) const { + size_t inputs_count; + size_t outputs_count; + (void)PyPostprocessStorage::Instance()->GetPyPostprocessInfo(postprocess_name, inputs_count, outputs_count); + return inputs_count; +} + +size_t PyPostprocess::GetOutputsCount(const std::string &postprocess_name) const { + size_t inputs_count; + size_t outputs_count; + (void)PyPostprocessStorage::Instance()->GetPyPostprocessInfo(postprocess_name, inputs_count, outputs_count); + return outputs_count; +} + +std::shared_ptr PyPostprocessStorage::Instance() { + static std::shared_ptr instance; + if (instance == nullptr) { + instance = std::make_shared(); + } + return instance; +} + +void PyPostprocessStorage::Register(const std::string &postprocess_name, size_t inputs_count, size_t outputs_count) { + postprocess_infos_[postprocess_name] = std::make_pair(inputs_count, outputs_count); + MSI_LOG_INFO << "Register python postprocess " << postprocess_name; + PostprocessStorage::Instance().Register(postprocess_name, py_postprocess_); +} + +bool PyPostprocessStorage::GetPyPostprocessInfo(const std::string &postprocess_name, size_t &inputs_count, + size_t &outputs_count) { + auto it = postprocess_infos_.find(postprocess_name); + if (it == postprocess_infos_.end()) { + return false; + } + inputs_count = it->second.first; + outputs_count = it->second.second; + return true; +} + +std::vector PyPostprocessStorage::GetPyCppPostprocessInfo(const std::string &postprocess_name) const { + std::vector results; + auto postprocess = PostprocessStorage::Instance().GetPostprocess(postprocess_name); + if (!postprocess) { + return results; + } + size_t inputs_count = postprocess->GetInputsCount(postprocess_name); + size_t outputs_count = postprocess->GetOutputsCount(postprocess_name); + if (inputs_count == 0 || outputs_count == 0) { + MSI_LOG_ERROR << "Postprocess " + postprocess_name + " input or output names cannot be null"; + return results; + } + results.push_back(inputs_count); + results.push_back(outputs_count); + return results; +} + +PyPostprocessStorage::PyPostprocessStorage() {} + +PyPostprocessStorage::~PyPostprocessStorage() = default; + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/python/worker/postprocess_py.h b/mindspore_serving/ccsrc/python/worker/postprocess_py.h new file mode 100644 index 0000000..c9f4f11 --- /dev/null +++ b/mindspore_serving/ccsrc/python/worker/postprocess_py.h @@ -0,0 +1,64 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_POSTPROCESS_PY_H +#define MINDSPORE_SERVING_POSTPROCESS_PY_H + +#include +#include +#include +#include +#include +#include "pybind11/pybind11.h" +#include "pybind11/numpy.h" +#include "pybind11/stl.h" +#include "worker/postprocess.h" +#include "python/tensor_py.h" + +namespace py = pybind11; + +namespace mindspore::serving { + +class PyPostprocess : public PostprocessBase { + public: + Status Postprocess(const std::string &postprocess_name, const InstanceData &input, InstanceData &output) override { + MSI_LOG_EXCEPTION << "PyPostprocess::Postprocess not expected to be invoked, preprocess name " << postprocess_name; + } + size_t GetInputsCount(const std::string &postprocess_name) const override; + size_t GetOutputsCount(const std::string &postprocess_name) const override; + bool IsPythonPostprocess() const override { return true; } +}; + +class MS_API PyPostprocessStorage { + public: + static std::shared_ptr Instance(); + + void Register(const std::string &preprocess_name, size_t inputs_count, size_t outputs_count); + + bool GetPyPostprocessInfo(const std::string &postprocess_name, size_t &inputs_count, size_t &outputs_count); + + std::vector GetPyCppPostprocessInfo(const std::string &postprocess_name) const; + PyPostprocessStorage(); + ~PyPostprocessStorage(); + + private: + std::unordered_map> postprocess_infos_; + std::shared_ptr py_postprocess_ = std::make_shared(); +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_POSTPROCESS_PY_H diff --git a/mindspore_serving/ccsrc/python/worker/preprocess_py.cc b/mindspore_serving/ccsrc/python/worker/preprocess_py.cc new file mode 100644 index 0000000..f3ca479 --- /dev/null +++ b/mindspore_serving/ccsrc/python/worker/preprocess_py.cc @@ -0,0 +1,86 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "python/worker/preprocess_py.h" +#include +#include +#include +#include +#include + +namespace mindspore::serving { + +size_t PyPreprocess::GetInputsCount(const std::string &preprocess_name) const { + size_t inputs_count; + size_t outputs_count; + (void)PyPreprocessStorage::Instance()->GetPyPreprocessInfo(preprocess_name, inputs_count, outputs_count); + return inputs_count; +} + +size_t PyPreprocess::GetOutputsCount(const std::string &preprocess_name) const { + size_t inputs_count; + size_t outputs_count; + (void)PyPreprocessStorage::Instance()->GetPyPreprocessInfo(preprocess_name, inputs_count, outputs_count); + return outputs_count; +} + +std::shared_ptr PyPreprocessStorage::Instance() { + static std::shared_ptr instance; + if (instance == nullptr) { + instance = std::make_shared(); + } + return instance; +} + +void PyPreprocessStorage::Register(const std::string &preprocess_name, size_t inputs_count, size_t outputs_count) { + preprocess_infos_[preprocess_name] = std::make_pair(inputs_count, outputs_count); + MSI_LOG_INFO << "Register python preprocess " << preprocess_name; + PreprocessStorage::Instance().Register(preprocess_name, py_preprocess_); +} + +bool PyPreprocessStorage::GetPyPreprocessInfo(const std::string &preprocess_name, size_t &inputs_count, + size_t &outputs_count) { + auto it = preprocess_infos_.find(preprocess_name); + if (it == preprocess_infos_.end()) { + return false; + } + inputs_count = it->second.first; + outputs_count = it->second.second; + return true; +} + +std::vector PyPreprocessStorage::GetPyCppPreprocessInfo(const std::string &preprocess_name) const { + std::vector results; + auto preprocess = PreprocessStorage::Instance().GetPreprocess(preprocess_name); + if (!preprocess) { + return results; + } + size_t inputs_count = preprocess->GetInputsCount(preprocess_name); + size_t outputs_count = preprocess->GetOutputsCount(preprocess_name); + if (inputs_count == 0 || outputs_count == 0) { + MSI_LOG_ERROR << "Preprocess " + preprocess_name + " inputs or outputs count cannot be 0"; + return results; + } + results.push_back(inputs_count); + results.push_back(outputs_count); + return results; +} + +PyPreprocessStorage::PyPreprocessStorage() {} + +PyPreprocessStorage::~PyPreprocessStorage() = default; + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/python/worker/preprocess_py.h b/mindspore_serving/ccsrc/python/worker/preprocess_py.h new file mode 100644 index 0000000..bc49846 --- /dev/null +++ b/mindspore_serving/ccsrc/python/worker/preprocess_py.h @@ -0,0 +1,66 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_PREPROCESS_PY_H +#define MINDSPORE_SERVING_PREPROCESS_PY_H + +#include +#include +#include +#include +#include +#include "pybind11/pybind11.h" +#include "pybind11/numpy.h" +#include "pybind11/stl.h" +#include "worker/preprocess.h" +#include "python/tensor_py.h" + +namespace py = pybind11; + +namespace mindspore::serving { + +class PyPreprocess : public PreprocessBase { + public: + Status Preprocess(const std::string &preprocess_name, const InstanceData &input, InstanceData &output) override { + MSI_LOG_EXCEPTION << "PyPreprocess::Preprocess not expected to be invoked, preprocess name " << preprocess_name; + } + size_t GetInputsCount(const std::string &preprocess_name) const override; + size_t GetOutputsCount(const std::string &preprocess_name) const override; + + bool IsPythonPreprocess() const override { return true; } +}; + +class MS_API PyPreprocessStorage { + public: + static std::shared_ptr Instance(); + + void Register(const std::string &preprocess_name, size_t inputs_count, size_t outputs_count); + + bool GetPyPreprocessInfo(const std::string &preprocess_name, size_t &inputs_count, size_t &outputs_count); + + std::vector GetPyCppPreprocessInfo(const std::string &preprocess_name) const; + + PyPreprocessStorage(); + ~PyPreprocessStorage(); + + private: + std::unordered_map> preprocess_infos_; + std::shared_ptr py_preprocess_ = std::make_shared(); +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_PREPROCESS_PY_H diff --git a/mindspore_serving/ccsrc/python/worker/worker_py.cc b/mindspore_serving/ccsrc/python/worker/worker_py.cc new file mode 100644 index 0000000..055e37f --- /dev/null +++ b/mindspore_serving/ccsrc/python/worker/worker_py.cc @@ -0,0 +1,129 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "python/worker/worker_py.h" +#include +#include +#include +#include "common/exit_handle.h" +#include "worker/notfiy_master/grpc_notify.h" +#include "worker/notfiy_master/local_notify.h" + +namespace mindspore::serving { + +void PyWorker::StartServable(const std::string &model_directory, const std::string &model_name, uint32_t version_number, + const std::string &master_ip, uint32_t master_port, const std::string &host_ip, + uint32_t host_port) { + auto notify_master = std::make_shared(master_ip, master_port, host_ip, host_port); + auto status = Worker::GetInstance().StartServable(model_directory, model_name, version_number, notify_master); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } + status = Worker::GetInstance().StartGrpcServer(host_ip, host_port); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } + status = Worker::GetInstance().StartVersionController(); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } +} + +void PyWorker::StartServableInMaster(const std::string &model_directory, const std::string &model_name, + uint32_t version_number) { + auto notify_master = std::make_shared(); + auto status = Worker::GetInstance().StartServable(model_directory, model_name, version_number, notify_master); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } + status = Worker::GetInstance().StartVersionController(); + if (status != SUCCESS) { + MSI_LOG_EXCEPTION << "Raise failed: " << status.StatusMessage(); + } +} + +TaskItem PyWorker::GetPyTask() { + TaskItem item; + Worker::GetInstance().GetPyTaskQueueGroup().PopPyTask(item); + return item; +} + +TaskItem PyWorker::TryGetPreprocessPyTask() { + TaskItem item; + Worker::GetInstance().GetPyTaskQueueGroup().TryPopPreprocessTask(item); + return item; +} + +TaskItem PyWorker::TryGetPostprocessPyTask() { + TaskItem item; + Worker::GetInstance().GetPyTaskQueueGroup().TryPopPostprocessTask(item); + return item; +} + +void PyWorker::PushPreprocessPyResult(const py::tuple &output_batch) { + MSI_TIME_STAMP_START(PushPreprocessPyResult) + std::vector outputs; + for (auto &output : output_batch) { + ResultInstance instance; + instance.data = PyTensor::AsInstanceData(py::cast(output)); + outputs.push_back(instance); + } + Worker::GetInstance().GetPyTaskQueuePreprocess()->PushTaskPyResult(outputs); + MSI_TIME_STAMP_END(PushPreprocessPyResult) +} + +void PyWorker::PushPreprocessPyFailed(int count) { + std::vector results; + Status error_msg(INVALID_INPUTS, "Preprocess Failed"); + for (int i = 0; i < count; i++) { + ResultInstance result_instance; + result_instance.error_msg = error_msg; + results.push_back(result_instance); + } + Worker::GetInstance().GetPyTaskQueuePreprocess()->PushTaskPyResult(results); +} + +void PyWorker::PushPostprocessPyResult(const py::tuple &output_batch) { + std::vector outputs; + for (auto &output : output_batch) { + ResultInstance instance; + instance.data = PyTensor::AsInstanceData(py::cast(output)); + outputs.push_back(instance); + } + Worker::GetInstance().GetPyTaskQueuePostprocess()->PushTaskPyResult(outputs); +} + +void PyWorker::PushPostprocessPyFailed(int count) { + std::vector results; + Status error_msg(INVALID_INPUTS, "Postprocess Failed"); + for (int i = 0; i < count; i++) { + ResultInstance result_instance; + result_instance.error_msg = error_msg; + results.push_back(result_instance); + } + Worker::GetInstance().GetPyTaskQueuePostprocess()->PushTaskPyResult(results); +} + +void PyWorker::WaitAndClear() { + ExitHandle::Instance().WorkerWait(); + Worker::GetInstance().Clear(); +} + +void PyWorker::Stop() { ExitHandle::Instance().Stop(); } + +int PyWorker::GetBatchSize() { return Worker::GetInstance().GetBatchSize(); } + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/python/worker/worker_py.h b/mindspore_serving/ccsrc/python/worker/worker_py.h new file mode 100644 index 0000000..c2cc22d --- /dev/null +++ b/mindspore_serving/ccsrc/python/worker/worker_py.h @@ -0,0 +1,52 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_WORKER_PY_H +#define MINDSPORE_SERVING_WORKER_PY_H + +#include +#include +#include "worker/worker.h" +#include "worker/task_queue.h" +#include "python/tensor_py.h" + +namespace mindspore::serving { + +class MS_API PyWorker { + public: + static void StartServable(const std::string &model_directory, const std::string &model_name, uint32_t version_number, + const std::string &master_ip, uint32_t master_port, const std::string &host_ip, + uint32_t host_port); + + static void StartServableInMaster(const std::string &model_directory, const std::string &model_name, + uint32_t version_number); + + static int GetBatchSize(); + static void WaitAndClear(); + static void Stop(); + static TaskItem GetPyTask(); + static TaskItem TryGetPreprocessPyTask(); + static TaskItem TryGetPostprocessPyTask(); + static void PushPreprocessPyResult(const py::tuple &output_batch); + static void PushPreprocessPyFailed(int count); + + static void PushPostprocessPyResult(const py::tuple &output_batch); + static void PushPostprocessPyFailed(int count); +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_WORKER_PY_H diff --git a/mindspore_serving/ccsrc/worker/context.cc b/mindspore_serving/ccsrc/worker/context.cc new file mode 100644 index 0000000..508219d --- /dev/null +++ b/mindspore_serving/ccsrc/worker/context.cc @@ -0,0 +1,60 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/context.h" + +namespace mindspore::serving { + +std::shared_ptr ServableContext::Instance() { + static std::shared_ptr instance; + if (instance == nullptr) { + instance = std::make_shared(); + } + return instance; +} + +void ServableContext::SetDeviceType(DeviceType device_type) { device_type_ = device_type; } + +DeviceType ServableContext::GetDeviceType() const { return device_type_; } + +void ServableContext::SetDeviceId(uint32_t device_id) { device_id_ = device_id; } + +uint32_t ServableContext::GetDeviceId() const { return device_id_; } + +Status ServableContext::SetDeviceTypeStr(const std::string &device_type) { + DeviceType type; + std::string device_type_lowcase = device_type; + for (auto &c : device_type_lowcase) { + if (c >= 'A' && c <= 'Z') { + c = c - 'A' + 'a'; + } + } + if (device_type_lowcase == "ascend" || device_type_lowcase == "davinci") { + type = kDeviceTypeAscend; + } else if (device_type_lowcase == "gpu") { + type = kDeviceTypeGpu; + } else if (device_type_lowcase == "cpu") { + type = kDeviceTypeCpu; + } else if (device_type_lowcase == "none") { + type = kDeviceTypeNotSpecified; + } else { + return INFER_STATUS_LOG_ERROR(FAILED) << "Unsupport device type " << device_type; + } + SetDeviceType(type); + return SUCCESS; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/worker/context.h b/mindspore_serving/ccsrc/worker/context.h new file mode 100644 index 0000000..788ef3e --- /dev/null +++ b/mindspore_serving/ccsrc/worker/context.h @@ -0,0 +1,46 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_CONTEXT_H +#define MINDSPORE_CONTEXT_H + +#include +#include +#include +#include "common/serving_common.h" +#include "worker/inference/inference.h" + +namespace mindspore::serving { + +class MS_API ServableContext { + public: + static std::shared_ptr Instance(); + + Status SetDeviceTypeStr(const std::string &device_type); + + void SetDeviceType(DeviceType device_type); + DeviceType GetDeviceType() const; + void SetDeviceId(uint32_t device_id); + uint32_t GetDeviceId() const; + + private: + DeviceType device_type_ = kDeviceTypeNotSpecified; + uint32_t device_id_ = 0; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_CONTEXT_H diff --git a/mindspore_serving/ccsrc/worker/grpc/worker_process.cc b/mindspore_serving/ccsrc/worker/grpc/worker_process.cc new file mode 100644 index 0000000..0d53d1f --- /dev/null +++ b/mindspore_serving/ccsrc/worker/grpc/worker_process.cc @@ -0,0 +1,68 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/grpc/worker_process.h" +#include "master/dispacther.h" +#include "worker/worker.h" + +namespace mindspore { +namespace serving { +grpc::Status MSWorkerImpl::Exit(grpc::ServerContext *context, const proto::ExitRequest *request, + proto::ExitReply *reply) { + MSI_LOG(INFO) << "Master Exit"; + Worker::GetInstance().StopServable(false); + return grpc::Status::OK; +} + +grpc::Status MSWorkerImpl::Predict(grpc::ServerContext *context, const proto::PredictRequest *request, + proto::PredictReply *reply) { + Status status(FAILED); + MSI_LOG(INFO) << "Begin call service Eval"; + try { + MSI_TIME_STAMP_START(Predict) + status = Worker::GetInstance().Run(*request, *reply); + MSI_TIME_STAMP_END(Predict) + } catch (const std::bad_alloc &ex) { + MSI_LOG(ERROR) << "Serving Error: malloc memory failed"; + std::cout << "Serving Error: malloc memory failed" << std::endl; + } catch (const std::runtime_error &ex) { + MSI_LOG(ERROR) << "Serving Error: runtime error occurred: " << ex.what(); + std::cout << "Serving Error: runtime error occurred: " << ex.what() << std::endl; + } catch (const std::exception &ex) { + MSI_LOG(ERROR) << "Serving Error: exception occurred: " << ex.what(); + std::cout << "Serving Error: exception occurred: " << ex.what() << std::endl; + } catch (...) { + MSI_LOG(ERROR) << "Serving Error: exception occurred"; + std::cout << "Serving Error: exception occurred"; + } + MSI_LOG(INFO) << "Finish call service Eval"; + + if (status == INVALID_INPUTS) { + auto proto_error_msg = reply->add_error_msg(); + proto_error_msg->set_error_code(status.StatusCode()); + proto_error_msg->set_error_msg(status.StatusMessage()); + return grpc::Status::OK; + } else if (status != SUCCESS) { + auto proto_error_msg = reply->add_error_msg(); + proto_error_msg->set_error_code(FAILED); + proto_error_msg->set_error_msg("Predict failed"); + return grpc::Status::OK; + } + return grpc::Status::OK; +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/worker/grpc/worker_process.h b/mindspore_serving/ccsrc/worker/grpc/worker_process.h new file mode 100644 index 0000000..6aa3e65 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/grpc/worker_process.h @@ -0,0 +1,41 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_WORKER_PROCESS_H +#define MINDSPORE_WORKER_PROCESS_H + +#include +#include +#include +#include "common/serving_common.h" +#include "proto/ms_worker.pb.h" +#include "proto/ms_worker.grpc.pb.h" + +namespace mindspore { +namespace serving { + +// Service Implement +class MSWorkerImpl final : public proto::MSWorker::Service { + public: + grpc::Status Predict(grpc::ServerContext *context, const proto::PredictRequest *request, + proto::PredictReply *reply) override; + grpc::Status Exit(grpc::ServerContext *context, const proto::ExitRequest *request, proto::ExitReply *reply) override; +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_WORKER_PROCESS_H diff --git a/mindspore_serving/ccsrc/worker/inference/inference.h b/mindspore_serving/ccsrc/worker/inference/inference.h new file mode 100644 index 0000000..f0451d4 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/inference/inference.h @@ -0,0 +1,209 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_INC_INFERENCE_H +#define MINDSPORE_SERVING_INC_INFERENCE_H + +#include +#include +#include +#include +#include +#include "common/tensor_base.h" +#include "common/tensor.h" +#include "common/log.h" +#include "common/status.h" +#include "include/api/types.h" + +namespace mindspore { +namespace serving { + +using api::ModelType; +using api::ModelType::kMindIR; +using api::ModelType::kOM; + +struct TensorInfo { + size_t size; // -1: unspecified + DataType data_type; + std::vector shape; +}; + +enum DeviceType { + kDeviceTypeNotSpecified, + kDeviceTypeAscendMS, + kDeviceTypeAscendCL, + kDeviceTypeAscend, + kDeviceTypeGpu, + kDeviceTypeCpu, +}; + +class MS_API InferSession { + public: + InferSession() = default; + virtual ~InferSession() = default; + virtual Status InitEnv(DeviceType device_type, uint32_t device_id, + const std::unordered_map &other_options) = 0; + virtual Status FinalizeEnv() = 0; + + virtual Status LoadModelFromFile(serving::DeviceType device_type, uint32_t device_id, const std::string &file_name, + ModelType model_type, uint32_t &model_id) = 0; + virtual Status UnloadModel(uint32_t model_id) = 0; + // override this method to avoid request/reply data copy + virtual Status ExecuteModel(uint32_t model_id, const RequestBase &request, ReplyBase &reply) = 0; + virtual Status ExecuteModel(uint32_t model_id, const std::vector &request, + std::vector &reply) { + VectorTensorPtrWrapRequest wrap_request(request); + VectorTensorPtrWrapReply wrap_reply(reply, []() { return std::make_shared(); }); + return ExecuteModel(model_id, wrap_request, wrap_reply); + } + + virtual std::vector GetInputInfos(uint32_t model_id) const = 0; + virtual std::vector GetOutputInfos(uint32_t model_id) const = 0; + virtual ssize_t GetBatchSize(uint32_t model_id) const = 0; + virtual TensorBasePtr MakeInferenceTensor(DataType data_type, const std::vector &shape) const { + return nullptr; + } + virtual bool CheckModelSupport(DeviceType device_type, ModelType model_type) const { return true; } +}; + +struct InferSessionRegInfo { + std::shared_ptr session; + ModelType model_type; + int priority; +}; + +class MS_API InferSessionStorage { + public: + void Register(DeviceType device_type, ModelType model_type, const std::shared_ptr &session, + int priority) { + auto &list = session_map_[device_type]; + InferSessionRegInfo info{session, model_type, priority}; + list.push_back(info); + } + + std::shared_ptr Get(DeviceType device_type, ModelType model_type, DeviceType *specified_device_type) { + MSI_EXCEPTION_IF_NULL(specified_device_type); + if (device_type == kDeviceTypeNotSpecified) { + for (auto &item_device : session_map_) { + std::shared_ptr ret_session = GetSession(item_device.second, item_device.first, model_type); + if (ret_session) { + *specified_device_type = item_device.first; + return ret_session; + } + } + return nullptr; + } else if (device_type == kDeviceTypeAscend) { + auto ascend_list = {kDeviceTypeAscendCL, kDeviceTypeAscendMS}; + for (auto ascend_type : ascend_list) { + auto it = session_map_.find(ascend_type); + if (it == session_map_.end()) { + continue; + } + auto session_ret = GetSession(it->second, ascend_type, model_type); + if (session_ret != nullptr) { + *specified_device_type = ascend_type; + return session_ret; + } + } + return nullptr; + } + auto it = session_map_.find(device_type); + if (it == session_map_.end()) { + return nullptr; + } + std::shared_ptr session_ret; + session_ret = GetSession(it->second, device_type, model_type); + *specified_device_type = device_type; + return session_ret; + } + + static InferSessionStorage &Instance() { + static InferSessionStorage instance; + return instance; + } + + private: + std::unordered_map> session_map_; + + std::shared_ptr GetSession(const std::vector &session_list, DeviceType device_type, + ModelType model_type) { + std::shared_ptr session_ret = nullptr; + int cur_priority = INT32_MIN; + for (auto &item : session_list) { + if (item.model_type != model_type) { + continue; + } + if (session_ret == nullptr || cur_priority < item.priority) { + if (!item.session->CheckModelSupport(device_type, model_type)) { + MSI_LOG_INFO << "CheckModelSupport for " << device_type << " " << model_type << " failed, skipped"; + continue; + } + cur_priority = item.priority; + session_ret = item.session; + } + } + return session_ret; + } +}; + +class MS_API InferSessionRegister { + public: + InferSessionRegister(DeviceType device_type, ModelType model_type, const std::shared_ptr &session, + int priority) { + InferSessionStorage::Instance().Register(device_type, model_type, session, priority); + } +}; + +#define REGISTER_INFER_SEESION_UNIQUE(device_type, model_type, cls_name, priority, index) \ + static mindspore::serving::InferSessionRegister g_register_session_##cls_name##_##index( \ + device_type, model_type, std::make_shared(), priority); + +#define REGISTER_INFER_SEESION_HELPER(device_type, model_type, cls_name, priority, index) \ + REGISTER_INFER_SEESION_UNIQUE(device_type, model_type, cls_name, priority, index) + +#define REGISTER_INFER_SEESION(device_type, model_type, cls_name, priority) \ + REGISTER_INFER_SEESION_HELPER(device_type, model_type, cls_name, priority, __COUNTER__); + +static inline LogStream &operator<<(LogStream &stream, DeviceType device_type) { + switch (device_type) { + case kDeviceTypeAscend: + stream << "kDeviceTypeAscend"; + break; + case kDeviceTypeAscendMS: + stream << "kDeviceTypeAscend910"; + break; + case kDeviceTypeAscendCL: + stream << "kDeviceTypeAscend310"; + break; + case kDeviceTypeGpu: + stream << "kDeviceTypeGpu"; + break; + case kDeviceTypeCpu: + stream << "kDeviceTypeCpu"; + break; + case kDeviceTypeNotSpecified: + stream << "kDeviceTypeNotSpecified"; + break; + default: + stream << "[device type " << static_cast(device_type) << "]"; + break; + } + return stream; +} + +} // namespace serving +} // namespace mindspore +#endif // MINDSPORE_SERVING_INC_INFERENCE_H diff --git a/mindspore_serving/ccsrc/worker/inference/mindspore_model_wrap.cc b/mindspore_serving/ccsrc/worker/inference/mindspore_model_wrap.cc new file mode 100644 index 0000000..960d60a --- /dev/null +++ b/mindspore_serving/ccsrc/worker/inference/mindspore_model_wrap.cc @@ -0,0 +1,342 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mindspore_model_wrap.h" +#include "api/types.h" + +namespace mindspore { +namespace serving { + +Status MindSporeModelWrap::InitEnv(serving::DeviceType device_type, uint32_t device_id, + const std::unordered_map &other_options) { + return SUCCESS; +} + +Status MindSporeModelWrap::FinalizeEnv() { return SUCCESS; } + +api::DataType TransInferDataType2ApiTypeId(DataType data_type) { + const std::map type2id_map{ + {serving::kMSI_Unknown, api::kMsUnknown}, {serving::kMSI_Bool, api::kMsBool}, + {serving::kMSI_Int8, api::kMsInt8}, {serving::kMSI_Uint8, api::kMsUint8}, + {serving::kMSI_Int16, api::kMsInt16}, {serving::kMSI_Uint16, api::kMsUint16}, + {serving::kMSI_Int32, api::kMsInt32}, {serving::kMSI_Uint32, api::kMsUint32}, + {serving::kMSI_Int64, api::kMsInt64}, {serving::kMSI_Uint64, api::kMsUint64}, + {serving::kMSI_Float16, api::kMsFloat16}, {serving::kMSI_Float32, api::kMsFloat32}, + {serving::kMSI_Float64, api::kMsFloat64}, + }; + auto it = type2id_map.find(data_type); + if (it == type2id_map.end()) { + MSI_LOG_WARNING << "Unsupported MSI data type " << data_type; + return api::kMsUnknown; + } else { + return it->second; + } +} + +DataType TransTypeId2InferDataType(api::DataType type_id) { + const std::map id2type_map{ + {api::kMsUnknown, kMSI_Unknown}, {api::kMsBool, kMSI_Bool}, {api::kMsFloat64, kMSI_Float64}, + {api::kMsInt8, kMSI_Int8}, {api::kMsUint8, kMSI_Uint8}, {api::kMsInt16, kMSI_Int16}, + {api::kMsUint16, kMSI_Uint16}, {api::kMsInt32, kMSI_Int32}, {api::kMsUint32, kMSI_Uint32}, + {api::kMsInt64, kMSI_Int64}, {api::kMsUint64, kMSI_Uint64}, {api::kMsFloat16, kMSI_Float16}, + {api::kMsFloat32, kMSI_Float32}, + }; + auto it = id2type_map.find(type_id); + if (it == id2type_map.end()) { + MSI_LOG_WARNING << "Unsupported data id " << type_id; + return kMSI_Unknown; + } else { + return it->second; + } +} + +Status MindSporeModelWrap::LoadModelFromFile(serving::DeviceType device_type, uint32_t device_id, + const std::string &file_name, ModelType model_type, uint32_t &model_id) { + std::string device_type_str; + if (device_type == kDeviceTypeAscendMS) { + device_type_str = api::kDeviceTypeAscendMS; + } else if (device_type == kDeviceTypeAscendCL) { + device_type_str = api::kDeviceTypeAscendCL; + } else { + MSI_LOG_EXCEPTION << "Only support Ascend310 or Ascend910 in MindSporeModelWrap"; + } + + std::shared_ptr model = nullptr; + try { + model = std::make_shared(device_type_str, device_id); + } catch (std::runtime_error &ex) { + MSI_LOG_ERROR << "Load model from file failed, device_type " << device_type_str << ", device_id " << device_id; + return FAILED; + } + api::ModelType api_model_type; + switch (model_type) { + case kMindIR: + api_model_type = api::kMindIR; + break; + case kOM: + api_model_type = api::kOM; + break; + default: + MSI_LOG_EXCEPTION << "Only support OM and MindIR, now model type is " << model_type; + } + api::Status status = model->LoadModel(file_name, api_model_type, {}); + if (!status.IsSuccess()) { + return Status(FAILED, status.StatusMessage()); + } + model_index_++; + model_id = model_index_; + ApiModelInfo api_model_info; + api_model_info.model = model; + api_model_info.device_type = device_type_str; + api_model_info.device_id = device_id; + auto st = GetModelInfos(api_model_info); + if (st != SUCCESS) { + return st; + } + model_map_[model_id] = api_model_info; + return SUCCESS; +} + +Status MindSporeModelWrap::GetModelInfos(ApiModelInfo &api_model_info) { + std::vector input_tensors; + auto model = api_model_info.model; + api::Status status = model->GetInputsInfo(&input_tensors); + if (!status.IsSuccess()) { + return Status(FAILED, status.StatusMessage()); + } + bool first_dim_same = true; + auto find_batch_size = [&first_dim_same, &api_model_info](const std::vector &shape) { + if (first_dim_same) { + if (shape.empty()) { + first_dim_same = false; + } else if (api_model_info.batch_size != 0) { + if (api_model_info.batch_size != shape[0]) { + first_dim_same = false; + } + } else { + api_model_info.batch_size = shape[0]; + } + } + }; + auto shape_element_num = [](const std::vector &shape) -> size_t { + size_t elements_nums = std::accumulate(shape.begin(), shape.end(), 1LL, std::multiplies()); + return elements_nums; + }; + auto get_tensor_info_from_tensor = [find_batch_size, shape_element_num](const api::Tensor &tensor) { + serving::TensorInfo tensor_info; + tensor_info.shape = tensor.Shape(); + tensor_info.data_type = TransTypeId2InferDataType(tensor.DataType()); + tensor_info.size = tensor.DataSize(); + if (tensor_info.size == 0) { + tensor_info.size = TensorBase::GetTypeSize(tensor_info.data_type) * shape_element_num(tensor_info.shape); + } + find_batch_size(tensor_info.shape); + return tensor_info; + }; + for (auto &item : input_tensors) { + api_model_info.input_names.push_back(item.Name()); + auto tensor_info = get_tensor_info_from_tensor(item); + if (tensor_info.data_type == kMSI_Unknown) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Unknown input api data type " << item.DataType(); + } + api_model_info.input_tensor_infos.push_back(tensor_info); + } + std::vector output_tensors; + status = model->GetOutputsInfo(&output_tensors); + if (!status.IsSuccess()) { + return Status(FAILED, status.StatusMessage()); + } + for (auto &item : output_tensors) { + api_model_info.output_names.push_back(item.Name()); + auto tensor_info = get_tensor_info_from_tensor(item); + if (tensor_info.data_type == kMSI_Unknown) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Unknown output api data type " << item.DataType(); + } + api_model_info.output_tensor_infos.push_back(tensor_info); + } + if (!first_dim_same) { + api_model_info.batch_size = 0; + } + return SUCCESS; +} + +Status MindSporeModelWrap::UnloadModel(uint32_t model_id) { + auto it = model_map_.find(model_id); + if (it == model_map_.end()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Invalid model id " << model_id; + } + auto model = it->second.model; + api::Status status = model->UnloadModel(); + model_map_.erase(it); + if (!status.IsSuccess()) { + return Status(FAILED, status.StatusMessage()); + } + return SUCCESS; +} + +Status MindSporeModelWrap::ExecuteModel(uint32_t model_id, const RequestBase &request, serving::ReplyBase &reply) { + FuncMakeInBuffer func_in = [&request](size_t index) { + auto input_tensor = request[index]; + return api::Buffer(input_tensor->data(), input_tensor->data_size()); + }; + + FuncMakeOutTensor func_out = [&reply](const api::Buffer &result_tensor, DataType data_type, + const std::vector &shape) { + auto tensor = reply.add(); + MSI_EXCEPTION_IF_NULL(tensor); + tensor->set_data(result_tensor.Data(), result_tensor.DataSize()); + tensor->set_data_type(data_type); + tensor->set_shape(shape); + }; + return ExecuteModelCommon(model_id, request.size(), func_in, func_out); +} + +Status MindSporeModelWrap::ExecuteModel(uint32_t model_id, const std::vector &request, + std::vector &reply) { + FuncMakeInBuffer func_in = [&request](size_t index) { + auto &input_tensor = request[index]; + auto api_buffer_wrap = std::dynamic_pointer_cast(input_tensor); + if (api_buffer_wrap) { + return api_buffer_wrap->GetBuffer(); + } else { + return api::Buffer(input_tensor->data(), input_tensor->data_size()); + } + }; + FuncMakeOutTensor func_out = [&reply](const api::Buffer &result_tensor, DataType data_type, + const std::vector &shape) { + auto tensor = std::make_shared(result_tensor); + tensor->set_data_type(data_type); + tensor->set_shape(shape); + reply.push_back(tensor); + }; + return ExecuteModelCommon(model_id, request.size(), func_in, func_out); +} + +Status MindSporeModelWrap::ExecuteModelCommon(uint32_t model_id, size_t request_size, const FuncMakeInBuffer &in_func, + const FuncMakeOutTensor &out_func) { + auto it = model_map_.find(model_id); + if (it == model_map_.end()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Invalid model id " << model_id; + } + auto &model_info = it->second; + auto model = model_info.model; + auto &input_names = model_info.input_names; + auto &output_names = model_info.output_names; + if (input_names.size() != request_size) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Inputs size not match, request inputs size " << request_size + << ", model inputs size " << input_names.size(); + } + std::map inputs; + for (size_t i = 0; i < input_names.size(); i++) { + inputs[input_names[i]] = in_func(i); + } + std::map outputs; + api::Status status = model->Predict(inputs, &outputs); + if (!status.IsSuccess()) { + MSI_LOG_ERROR << "Predict failed: " << status.StatusMessage(); + return FAILED; + } + if (outputs.size() != output_names.size()) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Outputs size not match, predict outputs size " << outputs.size() + << ", model outputs size " << output_names.size(); + } + auto &output_infos = model_info.output_tensor_infos; + for (size_t i = 0; i < output_names.size(); i++) { + auto &output_name = output_names[i]; + auto output_it = outputs.find(output_name); + if (output_it == outputs.end()) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Get output failed, cannot find output " << output_name << " in predict result"; + } + auto &result_tensor = output_it->second; + auto &output_info = output_infos[i]; + if (result_tensor.DataSize() != output_info.size) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Get output failed, predict output data size " << result_tensor.DataSize() + << " not match model info data size " << output_info.size << ", output_name " << output_name; + } + out_func(result_tensor, output_info.data_type, output_info.shape); + } + return SUCCESS; +} + +std::vector MindSporeModelWrap::GetInputInfos(uint32_t model_id) const { + auto it = model_map_.find(model_id); + if (it == model_map_.end()) { + MSI_LOG_ERROR << "Invalid model id " << model_id; + return {}; + } + auto &model_info = it->second; + return model_info.input_tensor_infos; +} + +std::vector MindSporeModelWrap::GetOutputInfos(uint32_t model_id) const { + auto it = model_map_.find(model_id); + if (it == model_map_.end()) { + MSI_LOG_ERROR << "Invalid model id " << model_id; + return {}; + } + auto &model_info = it->second; + return model_info.output_tensor_infos; +} + +ssize_t MindSporeModelWrap::GetBatchSize(uint32_t model_id) const { + auto it = model_map_.find(model_id); + if (it == model_map_.end()) { + MSI_LOG_ERROR << "Invalid model id " << model_id; + return {}; + } + auto &model_info = it->second; + return model_info.batch_size; +} + +TensorBasePtr MindSporeModelWrap::MakeInferenceTensor(DataType data_type, const std::vector &shape) const { + return std::make_shared(data_type, shape); +} + +bool MindSporeModelWrap::CheckModelSupport(DeviceType device_type, ModelType model_type) const { + std::string device_type_str; + switch (device_type) { + case kDeviceTypeAscendMS: + device_type_str = api::kDeviceTypeAscendMS; + break; + case kDeviceTypeAscendCL: + device_type_str = api::kDeviceTypeAscendCL; + break; + default: + return false; + } + return api::Model::CheckModelSupport(device_type_str, model_type); +} + +ApiBufferTensorWrap::ApiBufferTensorWrap() = default; + +ApiBufferTensorWrap::ApiBufferTensorWrap(DataType type, const std::vector &shape) + : type_(type), shape_(shape) { + size_t data_len = itemsize() * TensorBase::element_cnt(); + buffer_.ResizeData(data_len); +} + +ApiBufferTensorWrap::ApiBufferTensorWrap(const api::Buffer &buffer) : buffer_(buffer) {} + +ApiBufferTensorWrap::~ApiBufferTensorWrap() = default; + +REGISTER_INFER_SEESION(serving::kDeviceTypeAscendCL, kOM, MindSporeModelWrap, 1); +REGISTER_INFER_SEESION(serving::kDeviceTypeAscendCL, kMindIR, MindSporeModelWrap, 1); +REGISTER_INFER_SEESION(serving::kDeviceTypeAscendMS, kMindIR, MindSporeModelWrap, 1); + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/worker/inference/mindspore_model_wrap.h b/mindspore_serving/ccsrc/worker/inference/mindspore_model_wrap.h new file mode 100644 index 0000000..da5ba1a --- /dev/null +++ b/mindspore_serving/ccsrc/worker/inference/mindspore_model_wrap.h @@ -0,0 +1,122 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_WROERK_ACL_INFERENCE_H +#define MINDSPORE_SERVING_WROERK_ACL_INFERENCE_H + +#include +#include "common/serving_common.h" +#include "worker/inference/inference.h" +#include "api/model.h" + +namespace mindspore { +namespace serving { + +struct ApiModelInfo { + std::vector input_names; + std::vector input_tensor_infos; + std::vector output_names; + std::vector output_tensor_infos; + std::shared_ptr model; + uint32_t batch_size = 0; + std::string device_type; + uint32_t device_id = 0; +}; + +class MindSporeModelWrap : public InferSession { + public: + MindSporeModelWrap() = default; + + ~MindSporeModelWrap() = default; + + Status InitEnv(serving::DeviceType device_type, uint32_t device_id, + const std::unordered_map &other_options) override; + + Status FinalizeEnv() override; + + Status LoadModelFromFile(serving::DeviceType device_type, uint32_t device_id, const std::string &file_name, + serving::ModelType model_type, uint32_t &model_id) override; + + Status UnloadModel(uint32_t model_id) override; + + // override this method to avoid request/reply data copy + Status ExecuteModel(uint32_t model_id, const RequestBase &request, ReplyBase &reply) override; + Status ExecuteModel(uint32_t model_id, const std::vector &request, + std::vector &reply) override; + + std::vector GetInputInfos(uint32_t model_id) const override; + + std::vector GetOutputInfos(uint32_t model_id) const override; + + ssize_t GetBatchSize(uint32_t model_id) const override; + + TensorBasePtr MakeInferenceTensor(DataType data_type, const std::vector &shape) const override; + + bool CheckModelSupport(DeviceType device_type, ModelType model_type) const override; + + private: + std::unordered_map model_map_; + uint32_t model_index_ = 0; + + using FuncMakeInBuffer = std::function; + using FuncMakeOutTensor = + std::function &shape)>; + Status ExecuteModelCommon(uint32_t model_id, size_t request_size, const FuncMakeInBuffer &in_func, + const FuncMakeOutTensor &out_func); + Status GetModelInfos(ApiModelInfo &model_info); +}; + +class ApiBufferTensorWrap : public TensorBase { + public: + ApiBufferTensorWrap(); + ApiBufferTensorWrap(DataType type, const std::vector &shape); + explicit ApiBufferTensorWrap(const api::Buffer &buffer); + ~ApiBufferTensorWrap() override; + + void set_data_type(DataType type) override { type_ = type; } + DataType data_type() const override { return type_; } + + void set_shape(const std::vector &shape) override { shape_ = shape; } + std::vector shape() const override { return shape_; } + + const uint8_t *data() const override { return static_cast(buffer_.Data()); } + size_t data_size() const override { return buffer_.DataSize(); } + + bool resize_data(size_t data_len) override { return buffer_.ResizeData(data_len); } + uint8_t *mutable_data() override { return static_cast(buffer_.MutableData()); } + + // For kMSI_String and kMSI_Bytes + void clear_bytes_data() override { MSI_LOG_EXCEPTION << "Not support for api::Buffer Tensor"; } + void add_bytes_data(const uint8_t *data, size_t bytes_len) override { + MSI_LOG_EXCEPTION << "Not support for api::Buffer Tensor"; + } + size_t bytes_data_size() const override { MSI_LOG_EXCEPTION << "Not support for api::Buffer Tensor"; } + void get_bytes_data(size_t index, const uint8_t *&data, size_t &bytes_len) const override { + MSI_LOG_EXCEPTION << "Not support for api::Buffer Tensor"; + } + + api::Buffer GetBuffer() const { return buffer_; } + + private: + DataType type_ = kMSI_Unknown; + std::vector shape_; + api::Buffer buffer_; +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_WROERK_ACL_INFERENCE_H diff --git a/mindspore_serving/ccsrc/worker/model.cc b/mindspore_serving/ccsrc/worker/model.cc new file mode 100644 index 0000000..1496a35 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/model.cc @@ -0,0 +1,37 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/model.h" +#include +#include "mindspore_serving/ccsrc/common/tensor.h" + +namespace mindspore::serving { + +Status AscendModelServable::Predict(const std::vector &input, std::vector &output) { + return session_->ExecuteModel(model_id_, input, output); +} + +std::vector AscendModelServable::GetInputInfos() const { return session_->GetInputInfos(model_id_); } + +std::vector AscendModelServable::GetOutputInfos() const { return session_->GetOutputInfos(model_id_); } + +uint64_t AscendModelServable::GetBatchSize() const { return session_->GetBatchSize(model_id_); } + +TensorBasePtr AscendModelServable::MakeInferenceTensor(DataType data_type, const std::vector &shape) const { + return session_->MakeInferenceTensor(data_type, shape); +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/worker/model.h b/mindspore_serving/ccsrc/worker/model.h new file mode 100644 index 0000000..934c9f8 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/model.h @@ -0,0 +1,67 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_MODEL_H +#define MINDSPORE_SERVING_MODEL_H + +#include +#include +#include +#include + +#include "common/serving_common.h" +#include "common/instance.h" +#include "mindspore_serving/ccsrc/worker/inference/inference.h" +#include "common/servable.h" + +namespace mindspore::serving { + +class ServableBase { + public: + ServableBase() = default; + virtual ~ServableBase() = default; + + virtual Status Predict(const std::vector &input, std::vector &output) = 0; + + virtual std::vector GetInputInfos() const = 0; + virtual std::vector GetOutputInfos() const = 0; + virtual uint64_t GetBatchSize() const = 0; + virtual TensorBasePtr MakeInferenceTensor(DataType data_type, const std::vector &shape) const { + return nullptr; + } +}; + +class AscendModelServable : public ServableBase { + public: + AscendModelServable(const std::shared_ptr &session, uint32_t model_id) + : session_(session), model_id_(model_id) {} + ~AscendModelServable() = default; + + Status Predict(const std::vector &input, std::vector &output) override; + + std::vector GetInputInfos() const override; + std::vector GetOutputInfos() const override; + uint64_t GetBatchSize() const override; + TensorBasePtr MakeInferenceTensor(DataType data_type, const std::vector &shape) const override; + + private: + std::shared_ptr session_{nullptr}; + uint32_t model_id_ = 0; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_MODEL_H diff --git a/mindspore_serving/ccsrc/worker/notfiy_master/base_notify.h b/mindspore_serving/ccsrc/worker/notfiy_master/base_notify.h new file mode 100644 index 0000000..a7d28d4 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/notfiy_master/base_notify.h @@ -0,0 +1,38 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_BASE_NOTIFY_H +#define MINDSPORE_SERVING_BASE_NOTIFY_H +#include "common/serving_common.h" +#include "common/servable.h" + +namespace mindspore { +namespace serving { + +class MS_API BaseNotifyMaster { + public: + BaseNotifyMaster() = default; + virtual ~BaseNotifyMaster() = default; + virtual Status Register(const std::vector &worker_specs) = 0; + virtual Status Unregister() = 0; + virtual Status AddWorker(const WorkerSpec &worker_spec) = 0; + virtual Status RemoveWorker(const WorkerSpec &worker_spec) = 0; +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_BASE_NOTIFY_H diff --git a/mindspore_serving/ccsrc/worker/notfiy_master/grpc_notfiy.cc b/mindspore_serving/ccsrc/worker/notfiy_master/grpc_notfiy.cc new file mode 100644 index 0000000..2c46f3d --- /dev/null +++ b/mindspore_serving/ccsrc/worker/notfiy_master/grpc_notfiy.cc @@ -0,0 +1,150 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "worker/notfiy_master/grpc_notify.h" +#include +#include +#include +#include +#include +#include "common/exit_handle.h" + +namespace mindspore { +namespace serving { + +GrpcNotfiyMaster::GrpcNotfiyMaster(const std::string &master_ip, uint32_t master_port, const std::string &host_ip, + uint32_t host_port) + : master_ip_(master_ip), master_port_(master_port), host_ip_(host_ip), host_port_(host_port) { + master_address_ = master_ip_ + ":" + std::to_string(master_port); + worker_address_ = host_ip_ + ":" + std::to_string(host_port_); + auto channel = grpc::CreateChannel(master_address_, grpc::InsecureChannelCredentials()); + stub_ = proto::MSMaster::NewStub(channel); +} + +GrpcNotfiyMaster::~GrpcNotfiyMaster() = default; + +Status GrpcNotfiyMaster::Register(const std::vector &worker_specs) { + const int32_t REGISTER_TIME_OUT = 60; + const int32_t REGISTER_INTERVAL = 1; + auto loop = REGISTER_TIME_OUT; + while (loop-- && !ExitHandle::Instance().HasStopped()) { + MSI_LOG(INFO) << "Register to " << master_address_; + proto::RegisterRequest request; + request.set_address(worker_address_); + for (size_t i = 0; i < worker_specs.size(); i++) { + auto &spec = worker_specs[i]; + auto worker_spec = request.add_worker_spec(); + worker_spec->set_name(spec.servable_name); + worker_spec->set_version_number(spec.version_number); + for (auto &method : spec.methods) { + auto proto_method = worker_spec->add_methods(); + proto_method->set_name(method.name); + for (auto &name : method.input_names) { + proto_method->add_input_names(name); + } + } + } + + proto::RegisterReply reply; + grpc::ClientContext context; + std::chrono::system_clock::time_point deadline = + std::chrono::system_clock::now() + std::chrono::seconds(REGISTER_INTERVAL); + context.set_deadline(deadline); + grpc::Status status = stub_->Register(&context, request, &reply); + if (status.ok()) { + MSI_LOG(INFO) << "Register SUCCESS "; + return SUCCESS; + } + std::this_thread::sleep_for(std::chrono::milliseconds(REGISTER_INTERVAL * 1000)); + } + if (ExitHandle::Instance().HasStopped()) { + MSI_LOG_INFO << "Worker exit, stop registration"; + return FAILED; + } + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Register TimeOut"; +} + +Status GrpcNotfiyMaster::Unregister() { + if (is_stoped_.load()) { + return SUCCESS; + } + is_stoped_ = true; + proto::ExitRequest request; + request.set_address(worker_address_); + proto::ExitReply reply; + grpc::ClientContext context; + const int32_t TIME_OUT = 1; + std::chrono::system_clock::time_point deadline = std::chrono::system_clock::now() + std::chrono::seconds(TIME_OUT); + context.set_deadline(deadline); + grpc::Status status = stub_->Exit(&context, request, &reply); + if (status.ok()) { + MSI_LOG(INFO) << "Exit SUCCESS "; + return SUCCESS; + } + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Exit Failed"; +} + +Status GrpcNotfiyMaster::AddWorker(const WorkerSpec &worker_spec) { + proto::AddWorkerReply reply; + grpc::ClientContext context; + proto::AddWorkerRequest request; + request.set_address(worker_address_); + request.mutable_worker_spec()->set_name(worker_spec.servable_name); + request.mutable_worker_spec()->set_version_number(worker_spec.version_number); + for (auto &method : worker_spec.methods) { + auto proto_method = request.mutable_worker_spec()->add_methods(); + proto_method->set_name(method.name); + for (auto &name : method.input_names) { + proto_method->add_input_names(name); + } + } + const int32_t INTERVAL = 1; + std::chrono::system_clock::time_point deadline = std::chrono::system_clock::now() + std::chrono::seconds(INTERVAL); + context.set_deadline(deadline); + grpc::Status status = stub_->AddWorker(&context, request, &reply); + if (status.ok()) { + MSI_LOG(INFO) << "AddWorker SUCCESS "; + return SUCCESS; + } + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "AddWorker Failed"; +} + +Status GrpcNotfiyMaster::RemoveWorker(const WorkerSpec &worker_spec) { + proto::RemoveWorkerReply reply; + grpc::ClientContext context; + proto::RemoveWorkerRequest request; + request.set_address(worker_address_); + request.mutable_worker_spec()->set_name(worker_spec.servable_name); + request.mutable_worker_spec()->set_version_number(worker_spec.version_number); + for (auto &method : worker_spec.methods) { + auto proto_method = request.mutable_worker_spec()->add_methods(); + proto_method->set_name(method.name); + for (auto &name : method.input_names) { + proto_method->add_input_names(name); + } + } + const int32_t INTERVAL = 1; + std::chrono::system_clock::time_point deadline = std::chrono::system_clock::now() + std::chrono::seconds(INTERVAL); + context.set_deadline(deadline); + grpc::Status status = stub_->RemoveWorker(&context, request, &reply); + if (status.ok()) { + MSI_LOG(INFO) << "RemoveWorker SUCCESS "; + return SUCCESS; + } + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "RemoveWorker Failed"; +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/worker/notfiy_master/grpc_notify.h b/mindspore_serving/ccsrc/worker/notfiy_master/grpc_notify.h new file mode 100644 index 0000000..d6f6f53 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/notfiy_master/grpc_notify.h @@ -0,0 +1,50 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_GRPC_NOTIFY_H +#define MINDSPORE_SERVING_GRPC_NOTIFY_H +#include "worker/notfiy_master/base_notify.h" +#include "proto/ms_master.pb.h" +#include "proto/ms_master.grpc.pb.h" + +namespace mindspore { +namespace serving { + +class MS_API GrpcNotfiyMaster : public BaseNotifyMaster { + public: + GrpcNotfiyMaster(const std::string &master_ip, uint32_t master_port, const std::string &host_ip, uint32_t host_port); + ~GrpcNotfiyMaster() override; + Status Register(const std::vector &worker_specs) override; + Status Unregister() override; + Status AddWorker(const WorkerSpec &worker_spec) override; + Status RemoveWorker(const WorkerSpec &worker_spec) override; + + private: + std::string master_ip_; + uint32_t master_port_; + std::string host_ip_; + uint32_t host_port_; + std::string worker_address_; + std::string master_address_; + + std::unique_ptr stub_; + std::atomic is_stoped_{false}; +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_GRPC_NOTIFY_H diff --git a/mindspore_serving/ccsrc/worker/notfiy_master/local_notify.cc b/mindspore_serving/ccsrc/worker/notfiy_master/local_notify.cc new file mode 100644 index 0000000..6dbd360 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/notfiy_master/local_notify.cc @@ -0,0 +1,37 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/notfiy_master/local_notify.h" +#include "master/server.h" +namespace mindspore { +namespace serving { + +Status LocalNotifyMaster::Register(const std::vector &worker_specs) { + return Server::Instance().GetDispatcher()->RegisterLocalServable(worker_specs); +} + +Status LocalNotifyMaster::Unregister() { return Server::Instance().GetDispatcher()->UnregisterLocalServable(); } + +Status LocalNotifyMaster::AddWorker(const WorkerSpec &worker_spec) { + return Server::Instance().GetDispatcher()->AddLocalServable(worker_spec); +} + +Status LocalNotifyMaster::RemoveWorker(const WorkerSpec &worker_spec) { + return Server::Instance().GetDispatcher()->RemoveLocalServable(worker_spec); +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/worker/notfiy_master/local_notify.h b/mindspore_serving/ccsrc/worker/notfiy_master/local_notify.h new file mode 100644 index 0000000..04a6251 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/notfiy_master/local_notify.h @@ -0,0 +1,37 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_LOCAL_NOTIFY_H +#define MINDSPORE_SERVING_LOCAL_NOTIFY_H +#include "worker/notfiy_master/base_notify.h" + +namespace mindspore { +namespace serving { + +class MS_API LocalNotifyMaster : public BaseNotifyMaster { + public: + LocalNotifyMaster() = default; + ~LocalNotifyMaster() override = default; + Status Register(const std::vector &worker_specs) override; + Status Unregister() override; + Status AddWorker(const WorkerSpec &worker_spec) override; + Status RemoveWorker(const WorkerSpec &worker_spec) override; +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_LOCAL_NOTIFY_H diff --git a/mindspore_serving/ccsrc/worker/postprocess.cc b/mindspore_serving/ccsrc/worker/postprocess.cc new file mode 100644 index 0000000..e756431 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/postprocess.cc @@ -0,0 +1,47 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/postprocess.h" +#include + +namespace mindspore::serving { + +void PostprocessStorage::Register(const std::string &postprocess_name, std::shared_ptr postprocess) { + if (postprocess_map_.find(postprocess_name) != postprocess_map_.end()) { + MSI_LOG_WARNING << "postprocess " << postprocess_name << " has been registered"; + } + postprocess_map_[postprocess_name] = std::move(postprocess); +} + +std::shared_ptr PostprocessStorage::GetPostprocess(const std::string &postprocess_name) const { + auto it = postprocess_map_.find(postprocess_name); + if (it != postprocess_map_.end()) { + return it->second; + } + return nullptr; +} + +PostprocessStorage &PostprocessStorage::Instance() { + static PostprocessStorage storage; + return storage; +} + +RegPostprocess::RegPostprocess(const std::string &postprocess_name, std::shared_ptr postprocess) { + MSI_LOG_INFO << "Register C++ postprocess " << postprocess_name; + PostprocessStorage::Instance().Register(postprocess_name, std::move(postprocess)); +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/worker/postprocess.h b/mindspore_serving/ccsrc/worker/postprocess.h new file mode 100644 index 0000000..91031bf --- /dev/null +++ b/mindspore_serving/ccsrc/worker/postprocess.h @@ -0,0 +1,63 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_POSTPROCESS_H +#define MINDSPORE_SERVING_POSTPROCESS_H + +#include +#include +#include +#include + +#include "common/serving_common.h" +#include "common/instance.h" + +namespace mindspore::serving { + +class PostprocessBase : public std::enable_shared_from_this { + public: + PostprocessBase() = default; + virtual ~PostprocessBase() = default; + + virtual Status Postprocess(const std::string &postprocess_name, const InstanceData &input, InstanceData &output) = 0; + virtual size_t GetInputsCount(const std::string &postprocess_name) const = 0; + virtual size_t GetOutputsCount(const std::string &postprocess_name) const = 0; + virtual bool IsPythonPostprocess() const { return false; } +}; + +class MS_API PostprocessStorage { + public: + void Register(const std::string &postprocess_name, std::shared_ptr postprocess); + + std::shared_ptr GetPostprocess(const std::string &postprocess_name) const; + + static PostprocessStorage &Instance(); + + private: + std::unordered_map> postprocess_map_; +}; + +class RegPostprocess { + public: + RegPostprocess(const std::string &postprocess_name, std::shared_ptr postprocess); +}; + +#define REGISTER_POSTPROCESS(cls_name, postprocess_name) \ + static RegPostprocess g_register_postprocess_##cls_name(postprocess_name, std::make_shared()); + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_POSTPROCESS_H diff --git a/mindspore_serving/ccsrc/worker/predict_thread.cc b/mindspore_serving/ccsrc/worker/predict_thread.cc new file mode 100644 index 0000000..134dc79 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/predict_thread.cc @@ -0,0 +1,93 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/predict_thread.h" +#include +#include +#include "worker/task_queue.h" +#include "worker/preprocess.h" +#include "worker/postprocess.h" + +namespace mindspore::serving { + +serving::PredictThread::PredictThread() {} +PredictThread::~PredictThread() { Stop(); } + +Status PredictThread::PushPredictTask(const std::vector &inputs) { + if (!has_started) { + MSI_LOG_EXCEPTION << "Predict task thread has not been started"; + } + std::unique_lock lock{m_lock_}; + bool empty_before = predict_buffer_.empty(); + for (auto &item : inputs) { + predict_buffer_.push(item); + } + if (empty_before) { + cond_var_.notify_one(); + } + return SUCCESS; +} + +void PredictThread::ThreadFunc(PredictThread *queue) { queue->Predict(); } + +void PredictThread::Predict() { + while (!is_stoped_) { + std::vector intances; + { + std::unique_lock lock{m_lock_}; + if (predict_buffer_.empty()) { + cond_var_.wait(lock, [this] { return is_stoped_.load() || !predict_buffer_.empty(); }); + if (is_stoped_) { + return; + } + } + for (uint32_t i = 0; i < batch_size_ && !predict_buffer_.empty(); i++) { + intances.push_back(predict_buffer_.front()); + predict_buffer_.pop(); + } + } + MSI_TIME_STAMP_START(InvokePredict) + predict_fun_(intances); + MSI_TIME_STAMP_END(InvokePredict) + } +} + +void PredictThread::Stop() { + if (is_stoped_) { + return; + } + is_stoped_.store(true); + cond_var_.notify_all(); + if (predict_thread_.joinable()) { + try { + predict_thread_.join(); + } catch (const std::system_error &) { + } catch (...) { + } + } +} + +void PredictThread::Start(PredictFun predict_fun, uint32_t batch_size) { + if (has_started) { + return; + } + predict_fun_ = std::move(predict_fun); + batch_size_ = batch_size; + predict_thread_ = std::thread(ThreadFunc, this); + has_started = true; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/worker/predict_thread.h b/mindspore_serving/ccsrc/worker/predict_thread.h new file mode 100644 index 0000000..de9a8c3 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/predict_thread.h @@ -0,0 +1,56 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_PREDICT_THREAD_H +#define MINDSPORE_PREDICT_THREAD_H + +#include +#include +#include +#include +#include +#include "common/instance.h" + +namespace mindspore::serving { + +using PredictFun = std::function &inputs)>; +class MS_API PredictThread { + public: + PredictThread(); + ~PredictThread(); + + Status PushPredictTask(const std::vector &inputs); + void Start(PredictFun predict_fun, uint32_t batch_size); + void Stop(); + + private: + std::thread predict_thread_; + std::queue predict_buffer_; + PredictFun predict_fun_; + uint32_t batch_size_ = 0; + + std::mutex m_lock_; + std::condition_variable cond_var_; + std::atomic is_stoped_{false}; + std::atomic has_started = false; + + static void ThreadFunc(PredictThread *queue); + void Predict(); +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_PREDICT_THREAD_H diff --git a/mindspore_serving/ccsrc/worker/preprocess.cc b/mindspore_serving/ccsrc/worker/preprocess.cc new file mode 100644 index 0000000..130c969 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/preprocess.cc @@ -0,0 +1,47 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/preprocess.h" +#include + +namespace mindspore::serving { + +void PreprocessStorage::Register(const std::string &preprocess_name, std::shared_ptr preprocess) { + if (preprocess_map_.find(preprocess_name) != preprocess_map_.end()) { + MSI_LOG_WARNING << "preprocess " << preprocess_name << " has been registered"; + } + preprocess_map_[preprocess_name] = std::move(preprocess); +} + +PreprocessStorage &PreprocessStorage::Instance() { + static PreprocessStorage storage; + return storage; +} + +std::shared_ptr PreprocessStorage::GetPreprocess(const std::string &preprocess_name) const { + auto it = preprocess_map_.find(preprocess_name); + if (it != preprocess_map_.end()) { + return it->second; + } + return nullptr; +} + +RegPreprocess::RegPreprocess(const std::string &preprocess_name, std::shared_ptr preprocess) { + MSI_LOG_INFO << "Register C++ preprocess " << preprocess_name; + PreprocessStorage::Instance().Register(preprocess_name, std::move(preprocess)); +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/worker/preprocess.h b/mindspore_serving/ccsrc/worker/preprocess.h new file mode 100644 index 0000000..f2042fd --- /dev/null +++ b/mindspore_serving/ccsrc/worker/preprocess.h @@ -0,0 +1,63 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_PREPROCESS_H +#define MINDSPORE_SERVING_PREPROCESS_H + +#include +#include +#include +#include +#include +#include "common/serving_common.h" +#include "common/instance.h" + +namespace mindspore::serving { + +class PreprocessBase : public std::enable_shared_from_this { + public: + PreprocessBase() = default; + virtual ~PreprocessBase() = default; + + virtual Status Preprocess(const std::string &preprocess_name, const InstanceData &input, InstanceData &output) = 0; + virtual size_t GetInputsCount(const std::string &preprocess_name) const = 0; + virtual size_t GetOutputsCount(const std::string &preprocess_name) const = 0; + virtual bool IsPythonPreprocess() const { return false; } +}; + +class MS_API PreprocessStorage { + public: + void Register(const std::string &preprocess_name, std::shared_ptr preprocess); + + std::shared_ptr GetPreprocess(const std::string &preprocess_name) const; + + static PreprocessStorage &Instance(); + + private: + std::unordered_map> preprocess_map_; +}; + +class RegPreprocess { + public: + RegPreprocess(const std::string &preprocess_name, std::shared_ptr preprocess); +}; + +#define REGISTER_PREPROCESS(cls_name, preprocess_name) \ + static RegPreprocess g_register_preprocess_##cls_name(preprocess_name, std::make_shared()); + +} // namespace mindspore::serving + +#endif // MINDSPORE_SERVING_PREPROCESS_H diff --git a/mindspore_serving/ccsrc/worker/register/argmax.cc b/mindspore_serving/ccsrc/worker/register/argmax.cc new file mode 100644 index 0000000..c4656ed --- /dev/null +++ b/mindspore_serving/ccsrc/worker/register/argmax.cc @@ -0,0 +1,89 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/postprocess.h" +#include "mindspore_serving/ccsrc/common/tensor.h" + +namespace mindspore::serving { + +class ArgmaxPostprocess : public PostprocessBase { + public: + template + void ArgmaxImp(const uint8_t *input, int64_t *output, size_t data_size, size_t elemsize) { + auto count = data_size / elemsize; + auto data = reinterpret_cast(input); + *output = 0; + for (size_t i = 1; i < count; i++) { + if (data[i] > data[*output]) { + *output = i; + } + } + } + + Status Postprocess(const std::string &postprocess_name, const InstanceData &input, InstanceData &output) override { + auto input_x = input[0]; + auto x_data = input_x->data(); + auto out_tensor = std::make_shared(); + out_tensor->set_data_type(serving::kMSI_Int64); + out_tensor->resize_data(sizeof(int64_t)); + out_tensor->set_shape({}); + output.push_back(out_tensor); + auto y_data = reinterpret_cast(out_tensor->mutable_data()); + switch (input_x->data_type()) { + case kMSI_Float32: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Float64: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Int8: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Uint8: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Int16: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Uint16: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Int32: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Uint32: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Int64: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + case kMSI_Uint64: + ArgmaxImp(x_data, y_data, input_x->data_size(), input_x->itemsize()); + break; + default: + return INFER_STATUS(FAILED) << "Argmax not support data type " << input_x->data_type(); + } + return SUCCESS; + } + + size_t GetInputsCount(const std::string &postprocess_name) const override { return 1; } + + size_t GetOutputsCount(const std::string &postprocess_name) const override { return 1; } +}; + +REGISTER_POSTPROCESS(ArgmaxPostprocess, "argmax_cpp") + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/worker/task_queue.cc b/mindspore_serving/ccsrc/worker/task_queue.cc new file mode 100644 index 0000000..3d20702 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/task_queue.cc @@ -0,0 +1,340 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/task_queue.h" +#include +#include +#include "worker/preprocess.h" +#include "worker/postprocess.h" + +static const char *kTaskTypeStop = "stop"; +static const char *kTaskTypeEmpty = "empty"; +static const char *kTaskTypePreprocess = "preprocess"; +static const char *kTaskTypePostprocess = "postprocess"; + +namespace mindspore::serving { + +TaskQueue::TaskQueue() {} + +TaskQueue::TaskQueue(std::shared_ptr lock, std::shared_ptr cond_var) + : lock_(lock), cond_var_(cond_var) {} + +TaskQueue::~TaskQueue() { Stop(); } + +Status TaskQueue::SetWorkerCallback(uint64_t worker_id, TaskCallBack on_task_done) { + callback_map_[worker_id] = on_task_done; + return SUCCESS; +} + +void TaskQueue::PushTask(const std::string &task_name, uint64_t worker_id, const std::vector &inputs) { + if (inputs.empty()) { + MSI_LOG_EXCEPTION << "Inputs cannot be empty"; + } + { + std::unique_lock lock{*lock_}; + auto &task_list = task_map_[task_name]; + task_list.name = task_name; + for (auto &input : inputs) { + TaskContext context; + context.user_id = input.context.user_id; + context.instance_index = input.context.instance_index; + context.worker_id = worker_id; + + task_list.context_list.push_back(context); + task_list.instance_list.push_back(input); + } + task_priority_list_.push(task_name); + } + cond_var_->notify_one(); +} + +void TaskQueue::PushTaskResult(uint64_t worker_id, const Instance &input, const ResultInstance &output) { + auto it = callback_map_.find(worker_id); + if (it == callback_map_.end()) { + MSI_LOG_ERROR << "Worker serivce " << worker_id << " has not specificated callback"; + return; + } + if (it->second == nullptr) { + MSI_LOG_ERROR << "Worker serivce " << worker_id << " has not specify callback preprocess"; + return; + } + it->second({input}, {output}); +} + +void TaskQueue::PushTaskResult(uint64_t worker_id, const std::vector &inputs, + const std::vector &outputs) { + auto it = callback_map_.find(worker_id); + if (it == callback_map_.end()) { + MSI_LOG_ERROR << "Worker serivce " << worker_id << " has not specificated callback"; + return; + } + if (it->second == nullptr) { + MSI_LOG_ERROR << "Worker serivce " << worker_id << " has not specify callback preprocess"; + return; + } + it->second(inputs, outputs); +} + +Status TaskQueue::PushTaskPyResult(const std::vector &outputs) { + auto &context_list = task_item_processing_.context_list; + auto &instance_list = task_item_processing_.instance_list; + if (outputs.empty() || context_list.size() < outputs.size()) { + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "processing task not match result, processing size " + << context_list.size() << ", result size " << outputs.size(); + } + std::unordered_map, std::vector>> worker_result_map; + for (size_t i = 0; i < outputs.size(); i++) { + auto &result_item = worker_result_map[context_list[i].worker_id]; + result_item.first.push_back(instance_list[i]); + result_item.second.push_back(outputs[i]); + } + for (auto &item : worker_result_map) { + PushTaskResult(item.first, item.second.first, item.second.second); + } + context_list.erase(context_list.begin(), context_list.begin() + outputs.size()); + instance_list.erase(instance_list.begin(), instance_list.begin() + outputs.size()); + return SUCCESS; +} + +void TaskQueue::PopTask(TaskItem &task_item) { + if (is_stoped_) { + task_item.task_type = kTaskTypeStop; + return; + } + std::unique_lock lock{*lock_}; + while (true) { + if (task_priority_list_.empty()) { + cond_var_->wait(lock, [this] { return is_stoped_.load() || !task_priority_list_.empty(); }); + if (is_stoped_) { + task_item.task_type = kTaskTypeStop; + return; + } + } + auto task_item_info = task_priority_list_.front(); + task_priority_list_.pop(); + auto &cur_task = task_map_[task_item_info]; + if (cur_task.instance_list.empty()) { + continue; + } + task_item = cur_task; + cur_task.context_list.clear(); + cur_task.instance_list.clear(); + break; + } +} + +void TaskQueue::TryPopTask(TaskItem &task_item) { + if (is_stoped_) { + task_item.task_type = kTaskTypeStop; + return; + } + std::unique_lock lock{*lock_}; + while (true) { + if (task_priority_list_.empty()) { + task_item.task_type = kTaskTypeEmpty; + return; + } + auto task_item_info = task_priority_list_.front(); + task_priority_list_.pop(); + auto &cur_task = task_map_[task_item_info]; + if (cur_task.instance_list.empty()) { + continue; + } + task_item = cur_task; + cur_task.context_list.clear(); + cur_task.instance_list.clear(); + break; + } +} + +void TaskQueue::TryPopPyTask(TaskItem &task_item) { + TryPopTask(task_item); + if (IsValidTask(task_item)) { + task_item_processing_ = task_item; + } +} + +void TaskQueue::Stop() { + if (is_stoped_) { + return; + } + is_stoped_.store(true); + cond_var_->notify_all(); +} + +bool TaskQueue::IsValidTask(const TaskItem &task_item) { + return task_item.task_type != kTaskTypeStop && task_item.task_type != kTaskTypeEmpty; +} + +bool TaskQueue::Empty() const { return task_priority_list_.empty(); } + +PyTaskQueueGroup::PyTaskQueueGroup() = default; + +PyTaskQueueGroup::~PyTaskQueueGroup() = default; + +std::shared_ptr PyTaskQueueGroup::GetPreprocessTaskQueue() { return preprocess_task_que_; } +std::shared_ptr PyTaskQueueGroup::GetPostprocessTaskQueue() { return postprocess_task_que_; } +void PyTaskQueueGroup::PopPyTask(TaskItem &task_item) { + while (true) { + { + std::unique_lock lock{*lock_}; + if (preprocess_task_que_->Empty() && postprocess_task_que_->Empty()) { + cond_var_->wait(lock, [this] { + return is_stoped_.load() || !(preprocess_task_que_->Empty() && postprocess_task_que_->Empty()); + }); + if (is_stoped_) { + task_item.task_type = kTaskTypeStop; + return; + } + } + } + preprocess_task_que_->TryPopPyTask(task_item); + if (TaskQueue::IsValidTask(task_item)) { + task_item.task_type = kTaskTypePreprocess; + break; + } + postprocess_task_que_->TryPopPyTask(task_item); + if (TaskQueue::IsValidTask(task_item)) { + task_item.task_type = kTaskTypePostprocess; + break; + } + } +} + +void PyTaskQueueGroup::TryPopPreprocessTask(TaskItem &task_item) { + preprocess_task_que_->TryPopPyTask(task_item); + if (TaskQueue::IsValidTask(task_item)) { + task_item.task_type = kTaskTypePreprocess; + } +} + +void PyTaskQueueGroup::TryPopPostprocessTask(TaskItem &task_item) { + postprocess_task_que_->TryPopPyTask(task_item); + if (TaskQueue::IsValidTask(task_item)) { + task_item.task_type = kTaskTypePostprocess; + } +} + +void PyTaskQueueGroup::Stop() { + is_stoped_ = true; + preprocess_task_que_->Stop(); + postprocess_task_que_->Stop(); +} + +TaskQueueThreadPool::TaskQueueThreadPool() = default; + +TaskQueueThreadPool::~TaskQueueThreadPool() = default; + +void TaskQueueThreadPool::ThreadFunc(TaskQueueThreadPool *thread_pool) { + while (true) { + TaskItem task_item; + thread_pool->task_queue_->PopTask(task_item); + if (task_item.task_type == kTaskTypeStop) { + return; + } + auto status = thread_pool->HandleTask(task_item); + if (status != SUCCESS) { + MSI_LOG_ERROR << "System error happens, thread exit"; + return; + } + } +} + +void TaskQueueThreadPool::Start(uint32_t size) { + if (has_started) { + return; + } + has_started = true; + for (uint32_t i = 0; i < size; ++i) { + pool_.emplace_back(ThreadFunc, this); + } +} + +void TaskQueueThreadPool::Stop() { + task_queue_->Stop(); + for (std::thread &thd : pool_) { + if (thd.joinable()) { + try { + thd.join(); + } catch (const std::system_error &) { + } catch (...) { + } + } + } + pool_.clear(); +} + +Status PreprocessThreadPool::HandleTask(TaskItem &task_item) { + Status status; + auto preprocess = PreprocessStorage::Instance().GetPreprocess(task_item.name); + if (!preprocess) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "System error, get preprocess " << task_item.name << " failed"; + return status; + } + for (size_t i = 0; i < task_item.instance_list.size(); i++) { + auto &instance = task_item.instance_list[i]; + auto &context = task_item.context_list[i]; + ResultInstance result; + try { + status = preprocess->Preprocess(task_item.name, instance.data, result.data); + } catch (const std::bad_alloc &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: malloc memory failed"; + } catch (const std::runtime_error &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: runtime error occurred: " << ex.what(); + } catch (const std::exception &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: exception occurred: " << ex.what(); + } catch (...) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: exception occurred"; + } + if (status != SUCCESS) { + result.error_msg = status; + } + task_queue_->PushTaskResult(context.worker_id, instance, result); + } + return SUCCESS; +} + +Status PostprocessThreadPool::HandleTask(TaskItem &task_item) { + Status status; + auto postprocess = PostprocessStorage::Instance().GetPostprocess(task_item.name); + if (!postprocess) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "System error, get postprocess " << task_item.name << " failed"; + return status; + } + for (size_t i = 0; i < task_item.instance_list.size(); i++) { + auto &instance = task_item.instance_list[i]; + auto &context = task_item.context_list[i]; + ResultInstance result; + try { + status = postprocess->Postprocess(task_item.name, instance.data, result.data); + } catch (const std::bad_alloc &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: malloc memory failed"; + } catch (const std::runtime_error &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: runtime error occurred: " << ex.what(); + } catch (const std::exception &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: exception occurred: " << ex.what(); + } catch (...) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: exception occurred"; + } + if (status != SUCCESS) { + result.error_msg = status; + } + task_queue_->PushTaskResult(context.worker_id, instance, result); + } + return SUCCESS; +} + +} // namespace mindspore::serving diff --git a/mindspore_serving/ccsrc/worker/task_queue.h b/mindspore_serving/ccsrc/worker/task_queue.h new file mode 100644 index 0000000..00462a1 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/task_queue.h @@ -0,0 +1,138 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_TASK_QUEUE_H +#define MINDSPORE_TASK_QUEUE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/instance.h" + +namespace mindspore::serving { + +// key +struct TaskContext { + uint64_t user_id = 0; + uint32_t instance_index = 0; + uint64_t worker_id = 0; + bool operator==(const TaskContext &other) const { + return user_id == other.user_id && instance_index == other.instance_index && worker_id == other.worker_id; + } + bool operator!=(const TaskContext &other) const { return !operator==(other); } +}; + +struct TaskItem { + std::string task_type; // preprocess, postprocess, stop + std::string name; // preprocess name, postprocess name + std::vector context_list; + std::vector instance_list; +}; + +using TaskCallBack = + std::function &inputs, const std::vector &output)>; +// task queue for preprocess and postprocess +class MS_API TaskQueue { + public: + TaskQueue(); + TaskQueue(std::shared_ptr lock, std::shared_ptr cond_var); + ~TaskQueue(); + + Status SetWorkerCallback(uint64_t worker_id, TaskCallBack on_task_done); + + void PushTask(const std::string &task_name, uint64_t worker_id, const std::vector &inputs); + void PopTask(TaskItem &task_item); + void TryPopTask(TaskItem &task_item); + void PushTaskResult(uint64_t worker_id, const Instance &input, const ResultInstance &output); + void PushTaskResult(uint64_t worker_id, const std::vector &inputs, + const std::vector &outputs); + + void TryPopPyTask(TaskItem &task_item); + Status PushTaskPyResult(const std::vector &outputs); + + void Stop(); + bool Empty() const; + + static bool IsValidTask(const TaskItem &task_item); + + private: + std::unordered_map task_map_; + std::queue task_priority_list_; + TaskItem task_item_processing_; + std::unordered_map callback_map_; + + std::shared_ptr lock_ = std::make_shared(); + std::shared_ptr cond_var_ = std::make_shared(); + std::atomic is_stoped_{false}; +}; + +class MS_API PyTaskQueueGroup { + public: + PyTaskQueueGroup(); + ~PyTaskQueueGroup(); + + std::shared_ptr GetPreprocessTaskQueue(); + std::shared_ptr GetPostprocessTaskQueue(); + void PopPyTask(TaskItem &task_item); + void TryPopPreprocessTask(TaskItem &task_item); + void TryPopPostprocessTask(TaskItem &task_item); + void Stop(); + + private: + std::shared_ptr lock_ = std::make_shared(); + std::shared_ptr cond_var_ = std::make_shared(); + std::atomic is_stoped_{false}; + std::shared_ptr preprocess_task_que_ = std::make_shared(lock_, cond_var_); + std::shared_ptr postprocess_task_que_ = std::make_shared(lock_, cond_var_); +}; + +class TaskQueueThreadPool { + public: + TaskQueueThreadPool(); + virtual ~TaskQueueThreadPool(); + + void Start(uint32_t size = 4); + void Stop(); + + std::shared_ptr GetTaskQueue() { return task_queue_; } + + protected: + std::atomic has_started = false; + std::vector pool_; + std::shared_ptr task_queue_ = std::make_shared(); + + virtual Status HandleTask(TaskItem &task_item) = 0; + static void ThreadFunc(TaskQueueThreadPool *thread_pool); +}; + +class PreprocessThreadPool : public TaskQueueThreadPool { + protected: + Status HandleTask(TaskItem &task_item) override; +}; + +class PostprocessThreadPool : public TaskQueueThreadPool { + protected: + Status HandleTask(TaskItem &task_item) override; +}; + +} // namespace mindspore::serving + +#endif // MINDSPORE_TASK_QUEUE_H diff --git a/mindspore_serving/ccsrc/worker/version_control/version_controller.cc b/mindspore_serving/ccsrc/worker/version_control/version_controller.cc new file mode 100644 index 0000000..8c1f8a2 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/version_control/version_controller.cc @@ -0,0 +1,67 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/version_control/version_controller.h" +#include +#include +#include +#include +#include +#include "worker/worker.h" + +namespace mindspore { +namespace serving { + +void VersionController::ThreadFunc() { + while (!is_stoped_.load()) { + std::unique_lock lock{m_lock_}; + cond_var_.wait_for(lock, std::chrono::seconds(poll_model_wait_seconds_), [this] { return is_stoped_.load(); }); + if (is_stoped_) { + break; + } + Worker::GetInstance().Update(); + } +} + +VersionController::VersionController() = default; + +VersionController::~VersionController() = default; + +void VersionController::StartPollModelPeriodic() { + if (has_started) { + return; + } + poll_model_thread_ = std::thread(&VersionController::ThreadFunc, this); + // poll_model_thread_.detach(); + has_started = true; +} + +void VersionController::StopPollModelPeriodic() { + if (is_stoped_) { + return; + } + is_stoped_.store(true); + cond_var_.notify_all(); + if (poll_model_thread_.joinable()) { + try { + poll_model_thread_.join(); + } catch (const std::system_error &) { + } catch (...) { + } + } +} +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/worker/version_control/version_controller.h b/mindspore_serving/ccsrc/worker/version_control/version_controller.h new file mode 100644 index 0000000..e41ae60 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/version_control/version_controller.h @@ -0,0 +1,50 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MINDSPORE_SERVING_VERSOIN_CONTROLLER_H_ +#define MINDSPORE_SERVING_VERSOIN_CONTROLLER_H_ + +#include +#include +#include +#include +#include +#include +#include "common/serving_common.h" + +namespace mindspore { +namespace serving { +class VersionController { + public: + VersionController(); + ~VersionController(); + void StartPollModelPeriodic(); + void StopPollModelPeriodic(); + + private: + std::thread poll_model_thread_; + + std::mutex m_lock_; + std::condition_variable cond_var_; + std::atomic is_stoped_ = false; + std::atomic has_started = false; + uint32_t poll_model_wait_seconds_ = 5; + void ThreadFunc(); +}; + +} // namespace serving +} // namespace mindspore + +#endif // !MINDSPORE_SERVING_VERSOIN_CONTROLLER_H_ diff --git a/mindspore_serving/ccsrc/worker/worker.cc b/mindspore_serving/ccsrc/worker/worker.cc new file mode 100644 index 0000000..8ef2d39 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/worker.cc @@ -0,0 +1,520 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/worker.h" +#include +#include +#include +#include +#include "pybind11/pybind11.h" +#include "common/proto_tensor.h" +#include "common/file_system_operation.h" +#include "common/exit_handle.h" +#include "worker/context.h" +#include "master/server.h" +#include "worker/grpc/worker_process.h" +#include "worker/task_queue.h" + +namespace py = pybind11; + +namespace mindspore { +namespace serving { + +static const char *kVersionStrategyLastest = "lastest"; +static const char *kVersionStrategySpecific = "specific"; + +Worker &Worker::GetInstance() { + static Worker instance; + ExitHandle::Instance().InitSignalHandle(); + return instance; +} + +Status Worker::StartGrpcServer(const std::string &ip, uint32_t grpc_port) { + return grpc_server_.Start(std::make_shared(), ip, grpc_port, gRpcMaxMBMsgSize, "Worker gRPC"); +} + +Status Worker::RegisterWorker() { + std::vector specs; + std::vector signatures; + for (auto &work : work_list_) { + specs.push_back(work.servable_spec); + signatures.push_back(work.servable_signature); + } + std::vector worker_specs; + for (size_t i = 0; i < specs.size(); i++) { + auto &spec = specs[i]; + auto &servable_signature = signatures[i]; + WorkerSpec worker_spec; + worker_spec.servable_name = spec.servable_name; + worker_spec.version_number = spec.version_number; + for (auto &method : servable_signature.methods) { + WorkerMethodInfo worker_method_info; + worker_method_info.name = method.method_name; + for (auto &name : method.inputs) { + worker_method_info.input_names.push_back(name); + } + worker_spec.methods.push_back(worker_method_info); + } + worker_specs.push_back(worker_spec); + } + auto status = notify_master_->Register(worker_specs); + return status; +} + +Status Worker::StartVersionController() { + version_controller_.StartPollModelPeriodic(); + return SUCCESS; +} + +Status Worker::AddWorker(ServableWorkerContext &work) { + WorkerSpec worker_spec; + worker_spec.servable_name = work.servable_spec.servable_name; + worker_spec.version_number = work.servable_spec.version_number; + for (auto &method : work.servable_signature.methods) { + WorkerMethodInfo worker_method_info; + worker_method_info.name = method.method_name; + for (auto &name : method.inputs) { + worker_method_info.input_names.push_back(name); + } + worker_spec.methods.push_back(worker_method_info); + } + return notify_master_->AddWorker(worker_spec); +} + +Status Worker::RemoveWorker(ServableWorkerContext &work) { + WorkerSpec worker_spec; + worker_spec.servable_name = work.servable_spec.servable_name; + worker_spec.version_number = work.servable_spec.version_number; + for (auto &method : work.servable_signature.methods) { + WorkerMethodInfo worker_method_info; + worker_method_info.name = method.method_name; + for (auto &name : method.inputs) { + worker_method_info.input_names.push_back(name); + } + worker_spec.methods.push_back(worker_method_info); + } + return notify_master_->RemoveWorker(worker_spec); +} + +Status Worker::Run(const proto::PredictRequest &request, proto::PredictReply &reply) { + std::vector inputs; + RequestSpec request_spec; + MSI_TIME_STAMP_START(CreateInstanceFromRequest) + auto status = GrpcTensorHelper::CreateInstanceFromRequest(request, request_spec, inputs); + MSI_TIME_STAMP_END(CreateInstanceFromRequest) + if (status != SUCCESS) { + MSI_LOG(ERROR) << "transfer request to instances failed"; + return status; + } + if (inputs.empty()) { + return INFER_STATUS_LOG_ERROR(INVALID_INPUTS) << "Input instances count is 0"; + } + std::vector outputs; + MSI_TIME_STAMP_START(RUN_METHOD) + status = Run(request_spec, inputs, outputs); + MSI_TIME_STAMP_END(RUN_METHOD) + if (status != SUCCESS) { + MSI_LOG(ERROR) << "Run servable " << request_spec.Repr() << " failed"; + return status; + } + MSI_TIME_STAMP_START(CreateReplyFromInstances) + status = GrpcTensorHelper::CreateReplyFromInstances(request, outputs, reply); + MSI_TIME_STAMP_END(CreateReplyFromInstances) + if (status != SUCCESS) { + MSI_LOG(ERROR) << "transfer result to reply failed"; + return status; + } + MSI_LOG(INFO) << "run Predict finished"; + return SUCCESS; +} + +Status Worker::Run(const RequestSpec &request_spec, const std::vector &inputs, + std::vector &outputs) { + auto result_pair = RunAsync(request_spec, inputs); + if (result_pair.first != SUCCESS) { + return result_pair.first; + } + auto result = result_pair.second; + while (result->HasNext()) { + serving::Instance instance; + result->GetNext(instance); + outputs.push_back(instance); + } + return SUCCESS; +} + +std::pair> Worker::RunAsync(const RequestSpec &request_spec, + const std::vector &inputs) { + std::shared_ptr result = std::make_shared(inputs.size()); + const auto &worker = GetServableWorker(request_spec); + if (worker.worker_service == nullptr) { + return {INFER_STATUS_LOG_ERROR(FAILED) << "Cannot find servable match " << request_spec.Repr(), nullptr}; + } + WorkerCallBack on_process_done = [result](const Instance &output, const Status &error_msg) { + auto output_index = output.context.instance_index; + if (output_index < result->result_.size()) { + result->result_[output_index].error_msg = error_msg; + if (error_msg == SUCCESS) { + result->result_[output_index] = output; + } + } + }; + result->future_list_ = worker.worker_service->Work(request_spec, inputs, on_process_done); + return {SUCCESS, result}; +} + +Status Worker::InitEnv(ModelType model_type, const std::unordered_map &other_options) { + Status status; + if (session_) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Session has been inited"; + } + auto context = ServableContext::Instance(); + DeviceType device_type = kDeviceTypeNotSpecified; + session_ = InferSessionStorage::Instance().Get(context->GetDeviceType(), model_type, &device_type); + if (session_ == nullptr) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Cannot find session registed for device type " << context->GetDeviceType(); + } + if (device_type != kDeviceTypeNotSpecified) { + context->SetDeviceType(device_type); + } + status = session_->InitEnv(context->GetDeviceType(), context->GetDeviceId(), other_options); + if (status != SUCCESS) { + session_ = nullptr; + return INFER_STATUS_LOG_ERROR(FAILED) + << "Init env failed, device type " << context->GetDeviceType() << ", device id " << context->GetDeviceId(); + } + return SUCCESS; +} + +Status Worker::FinalizeEnv() { + if (session_ != nullptr) { + return session_->FinalizeEnv(); + } + return SUCCESS; +} +Status Worker::LoadModel(LoadServableSpec &servable_spec, uint64_t version_number, ServableWorkerContext &work) { + servable_spec.version_number = version_number; + ServableSignature signature; + if (!ServableStorage::Instance()->GetServableDef(servable_spec.servable_name, signature)) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Servable " << servable_spec.servable_name << " has not been registerd"; + } + const auto &servable_meta = signature.servable_meta; + std::string model_file_name = servable_spec.servable_directory + "/" + servable_spec.servable_name + "/" + + std::to_string(version_number) + "/" + servable_meta.servable_file; + uint32_t model_id; + auto context = ServableContext::Instance(); + Status status = session_->LoadModelFromFile(context->GetDeviceType(), context->GetDeviceId(), model_file_name, + servable_meta.model_format, model_id); + if (status != SUCCESS) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Load model failed, model directory " << servable_spec.servable_directory << ", model file " + << servable_meta.servable_file << ", version number " << version_number; + } + auto service = std::make_shared(GetPyTaskQueuePreprocess(), GetPyTaskQueuePostprocess(), + GetCppTaskQueuePreprocess(), GetCppTaskQueuePostprocess()); + status = service->Init(signature, std::make_shared(session_, model_id)); + if (status != SUCCESS) { + return status; + } + work.servable_spec = servable_spec; + work.servable_signature = signature; + work.worker_service = service; + work.model_id = model_id; + work.model_file_name = model_file_name; + return SUCCESS; +} + +void Worker::Update() { + if (version_strategy_ == kVersionStrategySpecific) { + return; + } + + std::vector versions; + GetVersions(base_spec_, versions); + for (auto &version : versions) { + bool isfind = std::any_of(work_list_.begin(), work_list_.end(), [&](const ServableWorkerContext &work) { + return work.servable_spec.version_number == version; + }); + if (isfind) { + continue; + } + ServableWorkerContext work; + LoadModel(base_spec_, version, work); + auto status = AddWorker(work); + if (status != SUCCESS) { + MSI_LOG_ERROR << "AddWorker failed"; + } + work_list_.push_back(work); + MSI_LOG_INFO << "Load Model version " << version << " success"; + } + for (auto iter = work_list_.begin(); iter != work_list_.end();) { + bool isfind = std::any_of(versions.begin(), versions.end(), + [&](const uint64_t &version) { return iter->servable_spec.version_number == version; }); + if (isfind) { + ++iter; + continue; + } + (void)RemoveWorker(*iter); + session_->UnloadModel(iter->model_id); + MSI_LOG_INFO << "UnLoad Model version " << iter->servable_spec.version_number << " success"; + work_list_.erase(iter); + } +} + +Status Worker::StartServable(const std::string &servable_directory, const std::string &servable_name, + uint32_t version_number, std::shared_ptr notify_master) { + if (servable_started_) { + MSI_LOG_EXCEPTION << "Servable has started"; + } + notify_master_ = std::move(notify_master); + base_spec_.servable_directory = servable_directory; + base_spec_.servable_name = servable_name; + base_spec_.version_number = version_number; + + std::string version_strategy; + if (version_number == 0) { + version_strategy = kVersionStrategyLastest; + } else { + version_strategy = kVersionStrategySpecific; + } + Status status; + ServableSignature signature; + if (!ServableStorage::Instance()->GetServableDef(servable_name, signature)) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Servable " << servable_name << " has not been registerd"; + } + if (session_ == nullptr) { + status = InitEnv(signature.servable_meta.model_format, {}); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Init env failed"; + return status; + } + } + + std::vector real_versions; + status = LoadServableConfig(base_spec_, real_versions, version_strategy); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Load servable config file failed, model directory " << base_spec_.servable_directory + << ", model name " << base_spec_.servable_name; + return status; + } + for (auto real_version_number : real_versions) { + ServableWorkerContext work; + status = LoadModel(base_spec_, real_version_number, work); + if (status != SUCCESS) { + return status; + } + work_list_.push_back(work); + } + status = RegisterWorker(); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Register worker failed"; + return status; + } + servable_started_ = true; + return SUCCESS; +} + +void Worker::StopServable(bool notify_master) { + exit_notify_master_ = notify_master; + ExitHandle::Instance().Stop(); +} + +void Worker::Clear() { + if (clear_flag_.test_and_set()) { + return; + } + MSI_LOG_INFO << "Start clear worker session"; + servable_stoppedd_ = true; + version_controller_.StopPollModelPeriodic(); + if (exit_notify_master_ && servable_started_) { + notify_master_->Unregister(); + } + if (session_ != nullptr) { + for (auto &it : work_list_) { + session_->UnloadModel(it.model_id); + } + } + work_list_.clear(); + FinalizeEnv(); + + session_ = nullptr; + py_task_queue_group_.Stop(); + cpp_preprocess_.Stop(); + cpp_postprocess_.Stop(); + grpc_server_.Stop(); + MSI_LOG_INFO << "End clear worker session"; +} + +bool Worker::HasCleared() { return servable_stoppedd_; } + +Worker::~Worker() { Clear(); } + +void Worker::GetVersions(const LoadServableSpec &servable_spec, std::vector &real_versions) { + // define version_strategy:"specific","lastest","multi" + if (version_strategy_ == kVersionStrategySpecific) { + real_versions.push_back(servable_spec.version_number); + return; + } + auto trans_to_integer = [](const std::string &str) -> uint32_t { + uint32_t parsed_value = 0; + for (auto c : str) { + if (c < '0' || c > '9') { + return 0; + } + parsed_value = parsed_value * 10 + c - '0'; + } + if (std::to_string(parsed_value) != str) { + return 0; + } + return parsed_value; + }; + uint64_t newest_version = 0; + std::string model_path = servable_spec.servable_directory + "/" + servable_spec.servable_name; + auto sub_dir = GetAllSubDirsNotFullPath(model_path); + static std::set ignore_dir; + for (const auto &dir : sub_dir) { + if (dir == "__pycache__") continue; + auto version_parse = trans_to_integer(dir); + if (version_parse == 0) { + if (ignore_dir.count(servable_spec.servable_directory + dir) == 0) { + ignore_dir.emplace(servable_spec.servable_directory + dir); + MSI_LOG_INFO << "Ignore directory " << dir << ", model_directory " << servable_spec.servable_directory + << ", model_name " << servable_spec.servable_name; + } + continue; + } + real_versions.push_back(version_parse); + if (version_parse > newest_version) { + newest_version = version_parse; + } + } + if (version_strategy_ == kVersionStrategyLastest) { + real_versions.clear(); + if (newest_version != 0) { + real_versions.push_back(newest_version); + } + } +} +Status Worker::LoadServableConfig(const LoadServableSpec &servable_spec, std::vector &real_versions, + const std::string &version_strategy) { + auto model_directory = servable_spec.servable_directory; + auto model_name = servable_spec.servable_name; + + if (!DirOrFileExist(model_directory + "/" + model_name)) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Model not found, model_directory " << model_directory << ", model_name " << model_name; + } + std::string model_path = model_directory + "/" + model_name; + auto version_directory = [model_path](int64_t version_number) { + return model_path + "/" + std::to_string(version_number); + }; + version_strategy_ = version_strategy; + // version_strategy:"specific","lastest","multi" + GetVersions(servable_spec, real_versions); + if (real_versions.size() == 0) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "Not found invalid model version , model_directory " << model_directory << ", model_name " << model_name; + } + for (auto real_version_number : real_versions) { + if (!DirOrFileExist(version_directory(real_version_number))) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Open failed for version " << real_version_number << ", model_directory " + << model_directory << ", model_name " << model_name; + } + } + return SUCCESS; +} + +ServableWorkerContext Worker::GetServableWorker(const RequestSpec &request_spec) { + ServableWorkerContext context; + if (request_spec.version_number != 0) { + auto item = find_if(work_list_.begin(), work_list_.end(), [&](const ServableWorkerContext &v) { + return v.servable_spec.servable_name == request_spec.servable_name && + v.servable_spec.version_number == request_spec.version_number; + }); + if (item != work_list_.end()) { + context = *item; + } + } else { + uint64_t max_version = 0; + for (auto &item : work_list_) { + if (item.servable_spec.servable_name == request_spec.servable_name && + item.servable_spec.version_number > max_version) { + context = item; + max_version = item.servable_spec.version_number; + } + } + } + return context; +} + +Worker::Worker() { + cpp_preprocess_.Start(2); + cpp_postprocess_.Start(2); +} + +ssize_t Worker::GetBatchSize() const { + ssize_t batch_size_ret = -1; + for (auto service : work_list_) { + auto batch_size = session_->GetBatchSize(service.model_id); + if (batch_size != -1) { + batch_size_ret = batch_size; + break; + } + } + return batch_size_ret; +} + +AsyncResult::AsyncResult(size_t size) : result_(size), next_index_(0) {} + +bool AsyncResult::HasNext() { return next_index_ < future_list_.size(); } + +Status AsyncResult::GetNext(Instance &instance_result) { + if (next_index_ >= future_list_.size()) { + MSI_LOG_ERROR << "GetNext failed, index greater than instance count " << future_list_.size(); + return FAILED; + } + auto index = next_index_; + next_index_++; + auto &future = future_list_[index]; + if (!future.valid()) { + instance_result.error_msg = result_[index].error_msg; + return FAILED; + } + const int kWaitMaxHundredMs = 100; + int i; + for (i = 0; i < kWaitMaxHundredMs; i++) { // + if (Worker::GetInstance().HasCleared()) { + instance_result.error_msg = Status(FAILED, "Servable stopped"); + return FAILED; + } + if (future.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready) { + break; + } + } + if (i >= kWaitMaxHundredMs) { + MSI_LOG_ERROR << "GetNext failed, wait time out, index " << index << ", total count " << future_list_.size(); + instance_result.error_msg = Status(FAILED, "Time out"); + return FAILED; + } + + future.get(); + instance_result = result_[index]; + return SUCCESS; +} + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/worker/worker.h b/mindspore_serving/ccsrc/worker/worker.h new file mode 100644 index 0000000..356584c --- /dev/null +++ b/mindspore_serving/ccsrc/worker/worker.h @@ -0,0 +1,119 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_WORKER_WORKER_H +#define MINDSPORE_SERVING_WORKER_WORKER_H + +#include +#include +#include +#include +#include +#include "worker/worker_execute.h" +#include "common/serving_common.h" +#include "proto/ms_service.pb.h" +#include "worker/notfiy_master/base_notify.h" +#include "common/grpc_server.h" +#include "worker/task_queue.h" +#include "worker/version_control/version_controller.h" + +namespace mindspore { +namespace serving { + +class AsyncResult { + public: + explicit AsyncResult(size_t size); + + bool HasNext(); + Status GetNext(Instance &instance_result); + + private: + std::vector> future_list_; + std::vector result_; + size_t next_index_; + + friend class Worker; +}; + +struct ServableWorkerContext { + LoadServableSpec servable_spec; + ServableSignature servable_signature; + std::shared_ptr worker_service = nullptr; + uint32_t model_id = 0; + std::string model_file_name; +}; + +class MS_API Worker { + public: + static Worker &GetInstance(); + Worker(); + ~Worker(); + + Status Run(const proto::PredictRequest &request, proto::PredictReply &reply); + Status Run(const RequestSpec &request_spec, const std::vector &inputs, std::vector &outputs); + std::pair> RunAsync(const RequestSpec &request_spec, + const std::vector &inputs); + + Status InitEnv(ModelType model_type, const std::unordered_map &other_options); + Status FinalizeEnv(); + + Status StartServable(const std::string &servable_directory, const std::string &servable_name, uint32_t version_number, + std::shared_ptr notify_master); + void StopServable(bool notify_master = true); + void Clear(); + bool HasCleared(); + Status RegisterWorker(); + Status StartGrpcServer(const std::string &ip, uint32_t grpc_port); + Status LoadModel(LoadServableSpec &servable_spec, uint64_t version, ServableWorkerContext &work); + void Update(); + Status StartVersionController(); + Status AddWorker(ServableWorkerContext &work); + Status RemoveWorker(ServableWorkerContext &work); + + PyTaskQueueGroup &GetPyTaskQueueGroup() { return py_task_queue_group_; } + std::shared_ptr GetPyTaskQueuePreprocess() { return py_task_queue_group_.GetPreprocessTaskQueue(); } + std::shared_ptr GetPyTaskQueuePostprocess() { return py_task_queue_group_.GetPostprocessTaskQueue(); } + std::shared_ptr GetCppTaskQueuePreprocess() { return cpp_preprocess_.GetTaskQueue(); } + std::shared_ptr GetCppTaskQueuePostprocess() { return cpp_postprocess_.GetTaskQueue(); } + ssize_t GetBatchSize() const; + + private: + ServableWorkerContext GetServableWorker(const RequestSpec &request_spec); + std::vector work_list_; + std::shared_ptr session_ = nullptr; + GrpcServer grpc_server_; + std::string version_strategy_; + PyTaskQueueGroup py_task_queue_group_; + PreprocessThreadPool cpp_preprocess_; + PostprocessThreadPool cpp_postprocess_; + + VersionController version_controller_; + LoadServableSpec base_spec_; + std::atomic_bool exit_notify_master_ = true; + std::atomic_bool servable_started_ = false; + std::atomic_bool servable_stoppedd_ = false; + std::atomic_flag clear_flag_ = ATOMIC_FLAG_INIT; + std::shared_ptr notify_master_ = nullptr; + + Status LoadServableConfig(const LoadServableSpec &servable_spec, std::vector &real_version_number, + const std::string &version_strategy); + void GetVersions(const LoadServableSpec &servable_spec, std::vector &real_versions); +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_WORKER_WORKER_H diff --git a/mindspore_serving/ccsrc/worker/worker_execute.cc b/mindspore_serving/ccsrc/worker/worker_execute.cc new file mode 100644 index 0000000..5f46a68 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/worker_execute.cc @@ -0,0 +1,546 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "worker/worker_execute.h" +#include +#include +#include +#include +#include "worker/preprocess.h" +#include "worker/postprocess.h" +#include "mindspore_serving/ccsrc/common/tensor.h" +#include "common/buffer_tensor.h" + +namespace mindspore { +namespace serving { + +#define NO_THREAD_POOL + +WorkerExecute::WorkerExecute(std::shared_ptr py_preprocess_task_queue, + std::shared_ptr py_postprocess_task_queue, + std::shared_ptr cpp_preprocess_task_queue, + std::shared_ptr cpp_postprocess_task_queue) + : py_preprocess_task_queue_(py_preprocess_task_queue), + py_postprocess_task_queue_(py_postprocess_task_queue), + cpp_preprocess_task_queue_(cpp_preprocess_task_queue), + cpp_postprocess_task_queue_(cpp_postprocess_task_queue) { + static std::atomic_uint64_t g_worker_id; + worker_id_ = ++g_worker_id; +} + +WorkerExecute::~WorkerExecute() = default; + +Status WorkerExecute::CheckSevableSignature() { + Status status; + const auto &input_infos = input_infos_; + if (input_infos.size() != servable_declare_.servable_meta.inputs_count) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "The inputs count " << servable_declare_.servable_meta.inputs_count << " declared not eqaul to the count " + << input_infos.size() << " defined in servable"; + } + const auto &output_infos = output_infos_; + if (output_infos.size() != servable_declare_.servable_meta.outputs_count) { + return INFER_STATUS_LOG_ERROR(FAILED) + << "The outputs count " << servable_declare_.servable_meta.outputs_count + << " declared not eqaul to the count " << output_infos.size() << " defined in servable"; + } + MSI_LOG_INFO << "Model input infos:"; + for (auto &item : input_infos) { + MSI_LOG_INFO << item.shape << ", " << item.data_type << ", " << item.size; + } + MSI_LOG_INFO << "Model output infos:"; + for (auto &item : output_infos) { + MSI_LOG_INFO << item.shape << ", " << item.data_type << ", " << item.size; + } + if (servable_declare_.servable_meta.with_batch_dim) { + if (model_batch_size_ == 0) { + return INFER_STATUS(FAILED) << "Servable batch size cannot be " << model_batch_size_; + } + for (const auto &item : input_infos) { + if (item.shape.empty() || static_cast(item.shape[0]) != model_batch_size_) { + return INFER_STATUS(FAILED) << "Servable batch size " << model_batch_size_ << " not match model input shape " + << item.shape; + } + } + for (const auto &item : output_infos) { + if (item.shape.empty() || static_cast(item.shape[0]) != model_batch_size_) { + return INFER_STATUS(FAILED) << "Servable batch size " << model_batch_size_ << " not match model output shape " + << item.shape; + } + } + } + return SUCCESS; +} + +Status WorkerExecute::Init(const ServableSignature &servable_declare, const std::shared_ptr &servable) { + Status status; + if (init_flag_) { + return INFER_STATUS_LOG_ERROR(FAILED) << "Worker service has been initialized"; + } + if (servable == nullptr) { + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "System error: Servable cannot be nullptr"; + } + servable_declare_ = servable_declare; + servable_ = servable; + input_infos_ = servable_->GetInputInfos(); + output_infos_ = servable_->GetOutputInfos(); + if (servable_declare_.servable_meta.with_batch_dim) { + model_batch_size_ = servable_->GetBatchSize(); + } else { + model_batch_size_ = 1; + } + + status = servable_declare_.Check(); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Check servable declare failed"; + return status; + } + status = CheckSevableSignature(); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Check servable definition failed"; + return status; + } + InitInputTensors(); + // init python preprocess and postprocess + InitPrePostprocess(); + // init predict fun + auto predict_fun = [this](const std::vector &inputs) { this->PredictHandle(inputs); }; + + predict_thread_.Start(predict_fun, model_batch_size_); + + init_flag_ = true; + return SUCCESS; +} + +void WorkerExecute::InitPrePostprocess() { + for (auto &method : servable_declare_.methods) { + if (!method.preprocess_name.empty()) { + auto preprocess = PreprocessStorage::Instance().GetPreprocess(method.preprocess_name); + if (preprocess && preprocess->IsPythonPreprocess()) { + python_preprocess_names_.emplace(method.preprocess_name); + } + } + if (!method.postprocess_name.empty()) { + auto postprocess = PostprocessStorage::Instance().GetPostprocess(method.postprocess_name); + if (postprocess && postprocess->IsPythonPostprocess()) { + python_postprocess_names_.emplace(method.postprocess_name); + } + } + } + // init cpp preprocess and postprocess + auto preprocess_callback = [this](const std::vector &inputs, const std::vector &outputs) { + if (inputs.empty() || inputs.size() != outputs.size()) { + MSI_LOG_ERROR << "Invalid inputs size " << inputs.size() << ", result size " << outputs.size(); + return; + } + std::vector outputs_real; + for (size_t i = 0; i < inputs.size(); i++) { + auto &input = inputs[i]; + auto &output = outputs[i]; + if (output.error_msg != SUCCESS) { + ReplyError(input, output.error_msg); + continue; + } + auto output_result = CreateResultInstance(input, output, kPredictPhaseTag_Preproces); + outputs_real.push_back(output_result); + } + OnRecievePredictInputs(outputs_real); + }; + auto postprocess_callback = [this](const std::vector &inputs, const std::vector &outputs) { + if (inputs.empty() || inputs.size() != outputs.size()) { + MSI_LOG_ERROR << "Invalid inputs size " << inputs.size() << ", result size " << outputs.size(); + return; + } + std::vector outputs_real; + for (size_t i = 0; i < inputs.size(); i++) { + auto &input = inputs[i]; + auto &output = outputs[i]; + if (output.error_msg != SUCCESS) { + ReplyError(input, output.error_msg); + continue; + } + auto output_result = CreateResultInstance(input, output, kPredictPhaseTag_Postprocess); + outputs_real.push_back(output_result); + } + ReplyRequest(outputs_real); + }; + py_preprocess_task_queue_->SetWorkerCallback(GetWorkerId(), preprocess_callback); + cpp_preprocess_task_queue_->SetWorkerCallback(GetWorkerId(), preprocess_callback); + py_postprocess_task_queue_->SetWorkerCallback(GetWorkerId(), postprocess_callback); + cpp_postprocess_task_queue_->SetWorkerCallback(GetWorkerId(), postprocess_callback); +} + +void WorkerExecute::InitInputTensors() { + inference_inputs_.clear(); + for (const auto &input_info : input_infos_) { + TensorBasePtr tensor = servable_->MakeInferenceTensor(input_info.data_type, input_info.shape); + if (tensor == nullptr) { + tensor = std::make_shared(); + tensor->set_data_type(input_info.data_type); + tensor->set_shape(input_info.shape); + tensor->resize_data(input_info.size); + } + inference_inputs_.push_back(tensor); + } +} + +std::vector> WorkerExecute::Work(const RequestSpec &request_spec, + const std::vector &instances_data, + WorkerCallBack on_process_done) { + if (!init_flag_) { + MSI_LOG_EXCEPTION << "Worker service has not been initialized"; + } + std::vector> future_list(instances_data.size()); + std::vector new_inputs(instances_data.size()); + auto user_id = WorkerExecute::GetNextUserId(); + auto user_context = std::make_shared(); + user_context->worker_call_back = on_process_done; + user_context->request_spec = request_spec; + + MethodSignature &method_def = user_context->method_def; + Status status; + if (!servable_declare_.GetMethodDeclare(request_spec.method_name, method_def)) { + MSI_LOG_EXCEPTION << "Not support method " << request_spec.method_name; + } + + for (size_t i = 0; i < new_inputs.size(); i++) { + new_inputs[i].input_data = instances_data[i]; + auto &context = new_inputs[i].context; + context.user_id = user_id; + context.instance_index = i; + context.user_context = user_context; + context.promise = std::make_shared>(); + future_list[i] = context.promise->get_future(); + } + if (!method_def.preprocess_name.empty()) { + OnRecievePreprocessInputs(new_inputs); + } else { + OnRecievePredictInputs(new_inputs); + } + return future_list; +} + +void WorkerExecute::OnRecievePreprocessInputs(const std::vector &inputs) { + if (inputs.empty()) { + MSI_LOG_EXCEPTION << "Inputs cannot be empty"; + } + const MethodSignature &method_def = inputs[0].context.user_context->method_def; + auto real_inputs = CreateInputInstance(inputs, kPredictPhaseTag_Preproces); + if (python_preprocess_names_.count(method_def.preprocess_name) > 0) { + py_preprocess_task_queue_->PushTask(method_def.preprocess_name, GetWorkerId(), real_inputs); + } else { + cpp_preprocess_task_queue_->PushTask(method_def.preprocess_name, GetWorkerId(), real_inputs); + } +} + +void WorkerExecute::OnRecievePostprocessInputs(const Instance &input) { + const MethodSignature &method_def = input.context.user_context->method_def; + auto real_input = CreateInputInstance(input, kPredictPhaseTag_Postprocess); + if (python_postprocess_names_.count(method_def.postprocess_name) > 0) { + py_postprocess_task_queue_->PushTask(method_def.postprocess_name, GetWorkerId(), {real_input}); + } else { + cpp_postprocess_task_queue_->PushTask(method_def.postprocess_name, GetWorkerId(), {real_input}); + } +} + +void WorkerExecute::OnRecievePredictInputs(const std::vector &inputs) { + // create input for predict, and check + auto real_inputs = CreateInputInstance(inputs, kPredictPhaseTag_Predict); + std::vector valid_inputs; + for (auto &item : real_inputs) { + auto status = CheckPredictInput(item); + if (status != SUCCESS) { + ReplyError(item, status); + continue; + } + valid_inputs.push_back(item); + } + predict_thread_.PushPredictTask(valid_inputs); +} + +bool WorkerExecute::ReplyRequest(const std::vector &outputs) { + for (auto &item : outputs) { + ReplyRequest(item); + } + return true; +} + +bool WorkerExecute::ReplyRequest(const Instance &outputs) { + Status status; + Instance trans_outputs = CreateInputInstance(outputs, kPredictPhaseTag_Output); + outputs.context.user_context->worker_call_back(trans_outputs, Status(SUCCESS)); + outputs.context.promise->set_value(); + return true; +} + +bool WorkerExecute::ReplyError(const std::vector &context, const Status &error_msg) { + for (auto &item : context) { + ReplyError(item, error_msg); + } + return true; +} + +bool WorkerExecute::ReplyError(const Instance &instance, const Status &error_msg) { + Instance error_instance; + error_instance.context = instance.context; + instance.context.user_context->worker_call_back(error_instance, error_msg); + instance.context.promise->set_value(); + return true; +} + +void WorkerExecute::PredictHandle(const std::vector &inputs) { + Status status; + try { + std::vector outputs; + status = Predict(inputs, outputs); + if (status != SUCCESS) { + this->ReplyError(inputs, status); + return; + } + for (auto &output : outputs) { + MethodSignature &method_def = output.context.user_context->method_def; + if (!method_def.postprocess_name.empty()) { + OnRecievePostprocessInputs(output); + } else { + ReplyRequest(output); + } + } + return; + } catch (const std::bad_alloc &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: malloc memory failed"; + } catch (const std::runtime_error &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: runtime error occurred: " << ex.what(); + } catch (const std::exception &ex) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: exception occurred: " << ex.what(); + } catch (...) { + status = INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << "Serving Error: exception occurred"; + } + ReplyError(inputs, status); +} + +Status WorkerExecute::PrePredict(const std::vector &inputs) { + auto input_batch_size = static_cast(inputs.size()); + uint32_t model_batch_size = model_batch_size_; + if (input_batch_size == 0 || input_batch_size > model_batch_size) { + MSI_LOG_ERROR << "Input batch size " << input_batch_size << " invalid, model batch size " << model_batch_size; + return SYSTEM_ERROR; + } + + for (size_t i = 0; i < inference_inputs_.size(); i++) { + auto &tensor = inference_inputs_[i]; + auto data_size = tensor->data_size(); + auto item_size = static_cast(data_size / model_batch_size); + auto dst_buffer = reinterpret_cast(tensor->mutable_data()); + + for (uint32_t k = 0; k < input_batch_size; k++) { + if (i >= inputs[k].data.size()) { + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) << " Batch index " << k << " does not have input " << i; + } + if (item_size != inputs[k].data[i]->data_size()) { + return INFER_STATUS_LOG_ERROR(SYSTEM_ERROR) + << " Batch index " << k << " input data size " << inputs[k].data[i]->data_size() + << "does not match size " << item_size << " defined in model"; + } + memcpy_s(dst_buffer + k * item_size, data_size - k * item_size, inputs[k].data[i]->data(), item_size); + } + for (uint32_t k = input_batch_size; k < model_batch_size; k++) { + memcpy_s(dst_buffer + k * item_size, data_size - k * item_size, inputs[0].data[i]->data(), item_size); + } + } + return SUCCESS; +} + +Status WorkerExecute::PostPredict(const std::vector &inputs, const std::vector &predict_result, + std::vector &outputs) { + auto input_batch_size = static_cast(inputs.size()); + uint32_t model_batch_size = model_batch_size_; + if (input_batch_size == 0 || input_batch_size > model_batch_size) { + MSI_LOG_ERROR << "Input batch size " << input_batch_size << " invalid, model batch size " << model_batch_size; + return SYSTEM_ERROR; + } + std::vector results_data(input_batch_size); + for (auto &item : predict_result) { + size_t item_size = item->data_size() / model_batch_size; + if (item_size == 0) { + MSI_LOG_EXCEPTION << "Ouput result data size cannot be 0"; + } + auto shape = item->shape(); + if (servable_declare_.servable_meta.with_batch_dim) { + if (shape.empty() || shape[0] != model_batch_size) { + MSI_LOG_EXCEPTION << "Output shape " << shape << " not match batch size " << model_batch_size; + } + shape.erase(shape.begin()); + } + auto src_buffer = reinterpret_cast(item->data()); + for (uint32_t k = 0; k < input_batch_size; k++) { + auto tensor = std::make_shared(); + tensor->set_shape(shape); + tensor->set_data_type(item->data_type()); + tensor->set_data(src_buffer + item_size * k, item_size); + results_data[k].data.push_back(tensor); + } + } + outputs = CreateResultInstance(inputs, results_data, kPredictPhaseTag_Predict); + return SUCCESS; +} + +Status WorkerExecute::Predict(const std::vector &inputs, std::vector &outputs) { + Status status; + std::vector predict_outputs; + status = PrePredict(inputs); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Call Pre Predict failed, model info " << servable_declare_.servable_meta.Repr(); + return status; + } + status = servable_->Predict(inference_inputs_, predict_outputs); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Predict failed, model info " << servable_declare_.servable_meta.Repr(); + return status; + } + status = PostPredict(inputs, predict_outputs, outputs); + if (status != SUCCESS) { + MSI_LOG_ERROR << "Call Post Predict failed, model info " << servable_declare_.servable_meta.Repr(); + return status; + } + return SUCCESS; +} + +Status WorkerExecute::CheckPredictInput(const Instance &instance) { + const auto &inputs_info = input_infos_; + if (instance.data.size() < inputs_info.size()) { + return INFER_STATUS(INVALID_INPUTS) << "Given model inputs size " << instance.data.size() + << " less than model inputs size " << inputs_info.size(); + } + for (size_t i = 0; i < instance.data.size(); i++) { + auto input_data = instance.data[i]; + if (static_cast(inputs_info[i].size / model_batch_size_) != input_data->data_size()) { + return INFER_STATUS(INVALID_INPUTS) + << "Given model input " << i << " size " << input_data->data_size() << " not match the size " + << inputs_info[i].size / model_batch_size_ << " defined in model"; + } + if (inputs_info[i].data_type != input_data->data_type()) { + return INFER_STATUS(INVALID_INPUTS) + << "Given model input " << i << " data type " << input_data->data_type() << " not match the data type " + << inputs_info[i].data_type << " defined in model"; + } + } + return SUCCESS; +} + +std::vector WorkerExecute::CreateInputInstance(const std::vector &instances, + PredictPhaseTag phase) { + std::vector ret(instances.size()); + for (size_t i = 0; i < instances.size(); i++) { + ret[i] = CreateInputInstance(instances[i], phase); + } + return ret; +} + +Instance WorkerExecute::CreateInputInstance(const Instance &instance, PredictPhaseTag phase) { + Instance result = instance; + result.data.clear(); + + MethodSignature &method_def = instance.context.user_context->method_def; + std::vector> *inputs = nullptr; + switch (phase) { + case kPredictPhaseTag_Preproces: + inputs = &method_def.preprocess_inputs; + break; + case kPredictPhaseTag_Predict: + inputs = &method_def.servable_inputs; + break; + case kPredictPhaseTag_Postprocess: + inputs = &method_def.postprocess_inputs; + break; + case kPredictPhaseTag_Output: + inputs = &method_def.returns; + break; + default: { + MSI_LOG_EXCEPTION << "Invalid phase tag " << phase; + } + } + for (auto &item : *inputs) { + const InstanceData *data = nullptr; + switch (item.first) { + case kPredictPhaseTag_Input: + data = &instance.input_data; + break; + case kPredictPhaseTag_Preproces: + data = &instance.preprocess_data; + break; + case kPredictPhaseTag_Predict: + data = &instance.predict_data; + break; + case kPredictPhaseTag_Postprocess: + data = &instance.postprocess_data; + break; + default: { + MSI_LOG_EXCEPTION << "Invalid input phase tag " << item.first; + } + } + if (data->size() <= item.second) { + MSI_LOG_EXCEPTION << "Invalid output index " << item.second << ", output count " << data->size() + << ", input phase tag " << item.first << ", phase " << phase << ", method " + << method_def.method_name; + } + result.data.push_back(data->at(item.second)); + } + return result; +} + +std::vector WorkerExecute::CreateResultInstance(const std::vector &inputs, + const std::vector &results, + PredictPhaseTag phase) { + std::vector ret(inputs.size()); + for (size_t i = 0; i < inputs.size(); i++) { + ret[i] = CreateResultInstance(inputs[i], results[i], phase); + } + return ret; +} + +Instance WorkerExecute::CreateResultInstance(const Instance &input, const ResultInstance &result, + PredictPhaseTag phase) { + Instance instance = input; + instance.data.clear(); + switch (phase) { + case kPredictPhaseTag_Input: + instance.input_data = result.data; + break; + case kPredictPhaseTag_Preproces: + instance.preprocess_data = result.data; + break; + case kPredictPhaseTag_Predict: + instance.predict_data = result.data; + break; + case kPredictPhaseTag_Postprocess: + instance.postprocess_data = result.data; + break; + default: { + MSI_LOG_EXCEPTION << "Invalid phase tag " << phase; + } + } + return instance; +} + +uint64_t WorkerExecute::GetNextUserId() { + static std::atomic user_id; + return ++user_id; +} + +uint32_t WorkerExecute::GetWorkerId() const { return worker_id_; } + +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/ccsrc/worker/worker_execute.h b/mindspore_serving/ccsrc/worker/worker_execute.h new file mode 100644 index 0000000..e945912 --- /dev/null +++ b/mindspore_serving/ccsrc/worker/worker_execute.h @@ -0,0 +1,104 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_WORKER_EXECUTE_H +#define MINDSPORE_SERVING_WORKER_EXECUTE_H + +#include +#include +#include +#include +#include +#include + +#include "common/thread_pool.h" +#include "common/serving_common.h" +#include "common/instance.h" +#include "common/servable.h" +#include "worker/model.h" +#include "worker/predict_thread.h" +#include "worker/task_queue.h" + +namespace mindspore { +namespace serving { + +using WorkerCallBack = std::function; + +class WorkerExecute { + public: + WorkerExecute(std::shared_ptr py_preprocess_task_queue, + std::shared_ptr py_postprocess_task_queue, + std::shared_ptr cpp_preprocess_task_queue, + std::shared_ptr cpp_postprocess_task_queue); + ~WorkerExecute(); + + Status Init(const ServableSignature &servable_declare, const std::shared_ptr &servable); + std::vector> Work(const RequestSpec &request_spec, const std::vector &inputs, + WorkerCallBack on_process_done); + + static uint64_t GetNextUserId(); + uint32_t GetWorkerId() const; + + private: + std::set python_preprocess_names_; + std::set python_postprocess_names_; + PredictThread predict_thread_; + + ServableSignature servable_declare_; + std::shared_ptr servable_; + std::vector input_infos_; + std::vector output_infos_; + uint32_t model_batch_size_ = 0; + uint64_t worker_id_ = 0; + bool init_flag_ = false; + + std::shared_ptr py_preprocess_task_queue_; + std::shared_ptr py_postprocess_task_queue_; + std::shared_ptr cpp_preprocess_task_queue_; + std::shared_ptr cpp_postprocess_task_queue_; + std::vector inference_inputs_; + + Status CheckSevableSignature(); + + bool ReplyError(const std::vector &context, const Status &error_msg); + bool ReplyError(const Instance &context, const Status &error_msg); + bool ReplyRequest(const std::vector &outputs); + bool ReplyRequest(const Instance &outputs); + + void OnRecievePreprocessInputs(const std::vector &inputs); // callback + void OnRecievePredictInputs(const std::vector &inputs); // callback + void OnRecievePostprocessInputs(const Instance &inputs); // callback + + void PredictHandle(const std::vector &inputs); + Status PrePredict(const std::vector &inputs); + Status PostPredict(const std::vector &inputs, const std::vector &predict_result, + std::vector &outputs); + Status Predict(const std::vector &inputs, std::vector &outputs); + Status CheckPredictInput(const Instance &instance); + + Instance CreateInputInstance(const Instance &instance, PredictPhaseTag phase); + std::vector CreateInputInstance(const std::vector &instance, PredictPhaseTag phase); + Instance CreateResultInstance(const Instance &input, const ResultInstance &result, PredictPhaseTag phase); + std::vector CreateResultInstance(const std::vector &inputs, + const std::vector &results, PredictPhaseTag phase); + void InitPrePostprocess(); + void InitInputTensors(); +}; + +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_WORKER_EXECUTE_H diff --git a/mindspore_serving/client/__init__.py b/mindspore_serving/client/__init__.py new file mode 100644 index 0000000..51ea4eb --- /dev/null +++ b/mindspore_serving/client/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +from .python.client import Client diff --git a/mindspore_serving/client/cpp/client.cc b/mindspore_serving/client/cpp/client.cc new file mode 100644 index 0000000..aaaac74 --- /dev/null +++ b/mindspore_serving/client/cpp/client.cc @@ -0,0 +1,448 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "client/cpp/client.h" +#include +#include +#include +#include +#include +#include +#include "proto/ms_service.pb.h" +#include "proto/ms_service.grpc.pb.h" + +namespace mindspore { +namespace serving { +namespace client { + +Status &Status::operator<<(DataType val) { + std::unordered_map data_type_map = { + {DT_UINT8, "uint8"}, {DT_UINT16, "uint16"}, {DT_UINT32, "uint32"}, {DT_UINT64, "uint64"}, + {DT_INT8, "int8"}, {DT_INT16, "int16"}, {DT_INT32, "int32"}, {DT_INT64, "int64"}, + {DT_BOOL, "bool"}, {DT_FLOAT16, "float16"}, {DT_FLOAT32, "float32"}, {DT_FLOAT64, "float64"}, + {DT_STRING, "string"}, {DT_BYTES, "bytes"}, {DT_UNKNOWN, "unknown"}, + }; + auto it = data_type_map.find(val); + if (it == data_type_map.end()) { + status_msg_ += "unknown"; + } else { + status_msg_ += it->second; + } + return *this; +} + +Status &operator<<(Status &status, proto::DataType val) { + std::unordered_map data_type_map = { + {proto::MS_UINT8, "uint8"}, {proto::MS_UINT16, "uint16"}, {proto::MS_UINT32, "uint32"}, + {proto::MS_UINT64, "uint64"}, {proto::MS_INT8, "int8"}, {proto::MS_INT16, "int16"}, + {proto::MS_INT32, "int32"}, {proto::MS_INT64, "int64"}, {proto::MS_BOOL, "bool"}, + {proto::MS_FLOAT16, "float16"}, {proto::MS_FLOAT32, "float32"}, {proto::MS_FLOAT64, "float64"}, + {proto::MS_STRING, "string"}, {proto::MS_BYTES, "bytes"}, {proto::MS_UNKNOWN, "unknown"}, + }; + auto it = data_type_map.find(val); + if (it == data_type_map.end()) { + status << "unknown"; + } else { + status << it->second; + } + return status; +} + +Status &operator<<(Status &status, grpc::StatusCode val) { + std::unordered_map data_type_map = { + {grpc::OK, "OK"}, + {grpc::CANCELLED, "CANCELLED"}, + {grpc::UNKNOWN, "UNKNOWN"}, + {grpc::INVALID_ARGUMENT, "INVALID_ARGUMENT"}, + {grpc::DEADLINE_EXCEEDED, "DEADLINE_EXCEEDED"}, + {grpc::NOT_FOUND, "NOT_FOUND"}, + {grpc::ALREADY_EXISTS, "ALREADY_EXISTS"}, + {grpc::PERMISSION_DENIED, "PERMISSION_DENIED"}, + {grpc::UNAUTHENTICATED, "UNAUTHENTICATED"}, + {grpc::RESOURCE_EXHAUSTED, "RESOURCE_EXHAUSTED"}, + {grpc::FAILED_PRECONDITION, "FAILED_PRECONDITION"}, + {grpc::ABORTED, "ABORTED"}, + {grpc::OUT_OF_RANGE, "OUT_OF_RANGE"}, + {grpc::UNIMPLEMENTED, "UNIMPLEMENTED"}, + {grpc::INTERNAL, "INTERNAL"}, + {grpc::UNAVAILABLE, "UNAVAILABLE"}, + {grpc::DATA_LOSS, "DATA_LOSS"}, + }; + auto it = data_type_map.find(val); + if (it == data_type_map.end()) { + status << "unknown"; + } else { + status << it->second; + } + return status; +} + +Status MutableTensor::SetBytesData(const std::vector &val) { + if (mutable_proto_tensor_ == nullptr) { + return Status(SYSTEM_ERROR) << "proto tensor cannot be nullptr"; + } + auto proto_shape = mutable_proto_tensor_->mutable_shape(); + proto_shape->add_dims(1); + mutable_proto_tensor_->set_dtype(proto::MS_BYTES); + if (val.empty()) { + return Status(INVALID_INPUTS) << "Input index bytes val len is empty"; + } + mutable_proto_tensor_->add_bytes_val(val.data(), val.size()); + return SUCCESS; +} + +Status MutableTensor::SetStrData(const std::string &val) { + if (mutable_proto_tensor_ == nullptr) { + return Status(SYSTEM_ERROR) << "proto tensor cannot be nullptr"; + } + auto proto_shape = mutable_proto_tensor_->mutable_shape(); + proto_shape->add_dims(val.size()); + mutable_proto_tensor_->set_dtype(proto::MS_STRING); + if (val.empty()) { + return Status(INVALID_INPUTS) << "string index string val len is empty"; + } + mutable_proto_tensor_->add_bytes_val(val); + return SUCCESS; +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(uint8_t), shape, DT_UINT8); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(uint16_t), shape, DT_UINT16); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(uint32_t), shape, DT_UINT32); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(uint64_t), shape, DT_UINT64); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(int8_t), shape, DT_INT8); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(int16_t), shape, DT_INT16); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(int32_t), shape, DT_INT32); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(int64_t), shape, DT_INT64); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + std::vector val_uint8; + std::transform(val.begin(), val.end(), std::back_inserter(val_uint8), + [](bool item) { return static_cast(item); }); + return SetData(val_uint8.data(), val_uint8.size() * sizeof(bool), shape, DT_BOOL); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(float), shape, DT_FLOAT32); +} + +Status MutableTensor::SetData(const std::vector &val, const std::vector &shape) { + return SetData(val.data(), val.size() * sizeof(double), shape, DT_FLOAT64); +} + +Status MutableTensor::SetData(const void *data, size_t data_len, const std::vector &shape, + DataType data_type) { + if (mutable_proto_tensor_ == nullptr) { + return Status(SYSTEM_ERROR) << "proto tensor cannot be nullptr"; + } + if (data == nullptr || data_len == 0) { + return Status(INVALID_INPUTS) << "data cannot be nullptr, or data len cannot be 0"; + } + mutable_proto_tensor_->set_data(data, data_len); + auto proto_shape = mutable_proto_tensor_->mutable_shape(); + + std::unordered_map> data_type_map = { + {DT_UINT8, {proto::MS_UINT8, sizeof(uint8_t)}}, + {DT_UINT16, {proto::MS_UINT16, sizeof(uint16_t)}}, + {DT_UINT32, {proto::MS_UINT32, sizeof(uint32_t)}}, + {DT_UINT64, {proto::MS_UINT64, sizeof(uint64_t)}}, + {DT_INT8, {proto::MS_INT8, sizeof(int8_t)}}, + {DT_INT16, {proto::MS_INT16, sizeof(int16_t)}}, + {DT_INT32, {proto::MS_INT32, sizeof(int32_t)}}, + {DT_INT64, {proto::MS_INT64, sizeof(int64_t)}}, + {DT_BOOL, {proto::MS_BOOL, sizeof(bool)}}, + {DT_FLOAT16, {proto::MS_FLOAT16, 2}}, + {DT_FLOAT32, {proto::MS_FLOAT32, 4}}, + {DT_FLOAT64, {proto::MS_FLOAT64, 8}}, + }; + auto it = data_type_map.find(data_type); + if (it == data_type_map.end()) { + return Status(INVALID_INPUTS) << "Input unsupported find data type " << data_type; + } + mutable_proto_tensor_->set_dtype(it->second.first); + + auto shape_str = [](const std::vector &val) noexcept { + std::stringstream sstream; + sstream << "["; + for (size_t i = 0; i < val.size(); i++) { + sstream << val[i]; + if (i + 1 < val.size()) { + sstream << ", "; + } + } + sstream << "]"; + return sstream.str(); + }; + int64_t element_cnt = 1; + for (auto &item : shape) { + proto_shape->add_dims(item); + if (item <= 0 || item >= INT64_MAX || INT64_MAX / element_cnt < item) { + return Status(INVALID_INPUTS) << "Input input shape invalid " << shape_str(shape); + } + } + auto item_size = it->second.second; + if (static_cast(data_len) / element_cnt < item_size || + element_cnt * item_size != static_cast(data_len)) { + return Status(INVALID_INPUTS) << "Input input shape " << shape_str(shape) << " does not match data len " + << data_len; + } + return SUCCESS; +} + +Status Tensor::GetBytesData(std::vector &val) const { + if (proto_tensor_ == nullptr) { + return Status(SYSTEM_ERROR) << "proto tensor cannot be nullptr"; + } + if (proto_tensor_->dtype() != proto::MS_BYTES) { + return Status(INVALID_INPUTS) << "Output data type is not match, its' real data type is " << proto_tensor_->dtype(); + } + auto &bytes_data = proto_tensor_->bytes_val(); + if (bytes_data.size() != 1) { + return Status(INVALID_INPUTS) << "Bytes value type size can only be 1"; + } + val.resize(bytes_data[0].size()); + memcpy(val.data(), val.data(), bytes_data[0].size()); + return SUCCESS; +} + +Status Tensor::GetStrData(std::string &val) const { + if (proto_tensor_ == nullptr) { + return Status(SYSTEM_ERROR) << "proto tensor cannot be nullptr"; + } + if (proto_tensor_->dtype() != proto::MS_STRING) { + return Status(INVALID_INPUTS) << "Output data type is not match, its' real data type is " << proto_tensor_->dtype(); + } + auto &bytes_data = proto_tensor_->bytes_val(); + if (bytes_data.size() != 1) { + return Status(INVALID_INPUTS) << "Bytes value type size can only be 1"; + } + val.resize(bytes_data[0].size()); + memcpy(val.data(), val.data(), bytes_data[0].size()); + return SUCCESS; +} + +template +Status GetInputImp(const proto::Tensor *proto_tensor, std::vector

&val) { + if (proto_tensor == nullptr) { + return Status(SYSTEM_ERROR) << "proto tensor cannot be nullptr"; + } + if (proto_tensor->dtype() != proto_dtype) { + return Status(INVALID_INPUTS) << "Output data type is not match, its' real data type is " << proto_tensor->dtype(); + } + auto data = proto_tensor->data().data(); + auto data_len = proto_tensor->data().length(); + val.resize(data_len / sizeof(DT)); + memcpy(val.data(), data, data_len); + return SUCCESS; +} + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { + std::vector val_uint8; + Status status = GetInputImp(proto_tensor_, val_uint8); + if (!status.IsSuccess()) { + return status; + } + std::transform(val_uint8.begin(), val_uint8.end(), std::back_inserter(val), [](uint8_t item) { return item != 0; }); + return SUCCESS; +} + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetData(std::vector &val) const { return GetInputImp(proto_tensor_, val); } + +Status Tensor::GetFp16Data(std::vector &val) const { + return GetInputImp(proto_tensor_, val); +} + +DataType Tensor::GetDataType() const { + if (proto_tensor_ == nullptr) { + std::cout << "proto tensor cannot be nullptr" << std::endl; + return DT_UNKNOWN; + } + std::unordered_map data_type_map = { + {proto::MS_UNKNOWN, DT_UNKNOWN}, {proto::MS_UINT8, DT_UINT8}, {proto::MS_UINT16, DT_UINT16}, + {proto::MS_UINT32, DT_UINT32}, {proto::MS_UINT64, DT_UINT64}, {proto::MS_INT8, DT_INT8}, + {proto::MS_INT16, DT_INT16}, {proto::MS_INT32, DT_INT32}, {proto::MS_INT64, DT_INT64}, + {proto::MS_BOOL, DT_BOOL}, {proto::MS_FLOAT16, DT_FLOAT16}, {proto::MS_FLOAT32, DT_FLOAT32}, + {proto::MS_FLOAT64, DT_FLOAT64}, {proto::MS_STRING, DT_STRING}, {proto::MS_BYTES, DT_BYTES}, + }; + auto it_dt = data_type_map.find(proto_tensor_->dtype()); + if (it_dt == data_type_map.end()) { + std::cout << "Unsupported data type " << proto_tensor_->dtype() << std::endl; + return DT_UNKNOWN; + } + return it_dt->second; +} + +std::vector Tensor::GetShape() const { + if (proto_tensor_ == nullptr) { + std::cout << "proto tensor cannot be nullptr" << std::endl; + return std::vector(); + } + std::vector shape; + auto &dims = proto_tensor_->shape().dims(); + std::copy(dims.begin(), dims.end(), std::back_inserter(shape)); + return shape; +} + +Tensor Instance::Get(const std::string &item_name) const { + if (proto_instance_ == nullptr) { + std::cout << "proto instance cannot be nullptr" << std::endl; + return Tensor(nullptr, nullptr); + } + auto &items = proto_instance_->items(); + auto it = items.find(item_name); + if (it == items.end()) { + std::cout << "Cannot find item name " << item_name << std::endl; + return Tensor(nullptr, nullptr); + } + return Tensor(message_owner_, &it->second); +} + +bool Instance::HasErrorMsg(int64_t &error_code, std::string &error_msg) const { + if (error_msg_ == nullptr) { + return false; + } + error_code = error_msg_->error_code(); + error_msg = error_msg_->error_msg(); + return true; +} + +MutableTensor MutableInstance::Add(const std::string &item_name) { + if (mutable_proto_instance_ == nullptr) { + std::cout << "proto instance cannot be nullptr" << std::endl; + return MutableTensor(nullptr, nullptr); + } + auto items = mutable_proto_instance_->mutable_items(); + auto &proto_tensor = (*items)[item_name]; + return MutableTensor(message_owner_, &proto_tensor); +} + +InstancesRequest::InstancesRequest() { request_ = std::make_shared(); } + +MutableInstance InstancesRequest::AddInstance() { + auto proto_instance = request_->add_instances(); + return MutableInstance(request_, proto_instance); +} + +InstancesReply::InstancesReply() { reply_ = std::make_shared(); } + +std::vector InstancesReply::GetResult() const { + std::vector instances; + auto &proto_instances = reply_->instances(); + auto &proto_error_msgs = reply_->error_msg(); + for (int i = 0; i < proto_instances.size(); i++) { + auto &proto_instance = proto_instances[i]; + const proto::ErrorMsg *error_msg = nullptr; + if (proto_error_msgs.size() == 1) { + error_msg = &proto_error_msgs[0]; + } else if (proto_error_msgs.size() == proto_instances.size() && proto_error_msgs[i].error_code() != 0) { + error_msg = &proto_error_msgs[i]; + } + instances.push_back(Instance(reply_, &proto_instance, error_msg)); + } + return instances; +} + +class ClientImpl { + public: + ClientImpl(const std::string &server_ip, uint64_t server_port) { + std::string target_str = server_ip + ":" + std::to_string(server_port); + auto channel = grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()); + stub_ = proto::MSService::NewStub(channel); + } + Status Predict(const proto::PredictRequest &request, proto::PredictReply &reply) { + grpc::ClientContext context; + + // The actual RPC. + grpc::Status status = stub_->Predict(&context, request, &reply); + if (status.ok()) { + return SUCCESS; + } else { + std::cout << status.error_code() << ": " << status.error_message() << std::endl; + return Status(FAILED, status.error_message()); + } + } + + private: + std::unique_ptr stub_; +}; + +Client::Client(const std::string &server_ip, uint64_t server_port, const std::string &servable_name, + const std::string &method_name, uint64_t version_number) + : server_ip_(server_ip), + server_port_(server_port), + servable_name_(servable_name), + method_name_(method_name), + version_number_(version_number), + impl_(std::make_shared(server_ip, server_port)) {} + +Status Client::SendRequest(const InstancesRequest &request, InstancesReply &reply) { + proto::PredictRequest *proto_request = request.request_.get(); + proto::PredictReply *proto_reply = reply.reply_.get(); + auto servable_spec = proto_request->mutable_servable_spec(); + servable_spec->set_name(servable_name_); + servable_spec->set_method_name(method_name_); + servable_spec->set_version_number(version_number_); + + Status result = impl_->Predict(*proto_request, *proto_reply); + // std::string str; + // google::protobuf::TextFormat::PrintToString(*proto_reply, &str); + // std::cout << str << std::endl; + return result; +} + +} // namespace client +} // namespace serving +} // namespace mindspore diff --git a/mindspore_serving/client/cpp/client.h b/mindspore_serving/client/cpp/client.h new file mode 100644 index 0000000..982b1f5 --- /dev/null +++ b/mindspore_serving/client/cpp/client.h @@ -0,0 +1,248 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MINDSPORE_SERVING_CLIENT_H +#define MINDSPORE_SERVING_CLIENT_H + +#include +#include +#include +#include + +namespace google { +namespace protobuf { +class Message; +} +} // namespace google + +namespace mindspore { +namespace serving { +#define MS_API __attribute__((visibility("default"))) + +namespace proto { +class Tensor; +class Instance; +class PredictRequest; +class PredictReply; +class ErrorMsg; +} // namespace proto + +namespace client { + +using ProtoMsgOwner = std::shared_ptr; + +enum DataType { + DT_UNKNOWN, + DT_UINT8, + DT_UINT16, + DT_UINT32, + DT_UINT64, + DT_INT8, + DT_INT16, + DT_INT32, + DT_INT64, + DT_BOOL, + DT_FLOAT16, + DT_FLOAT32, + DT_FLOAT64, + DT_STRING, + DT_BYTES, +}; + +enum StatusCode { SUCCESS = 0, FAILED, INVALID_INPUTS, SYSTEM_ERROR, UNAVAILABLE }; + +class MS_API Status { + public: + Status() : status_code_(FAILED) {} + Status(enum StatusCode status_code, const std::string &status_msg = "") + : status_code_(status_code), status_msg_(status_msg) {} + bool IsSuccess() const { return status_code_ == SUCCESS; } + enum StatusCode StatusCode() const { return status_code_; } + std::string StatusMessage() { return status_msg_; } + bool operator==(const Status &other) const { return status_code_ == other.status_code_; } + bool operator==(enum StatusCode other_code) const { return status_code_ == other_code; } + bool operator!=(const Status &other) const { return status_code_ != other.status_code_; } + bool operator!=(enum StatusCode other_code) const { return status_code_ != other_code; } + operator bool() const = delete; + + template + Status &operator<<(T val); + Status &operator<<(DataType val); + template + Status &operator<<(const std::vector &val); + + private: + enum StatusCode status_code_; + std::string status_msg_; +}; + +class MS_API Tensor { + public: + Tensor(const ProtoMsgOwner &owner, const proto::Tensor *proto_tensor) + : message_owner_(owner), proto_tensor_(proto_tensor) {} + virtual ~Tensor() = default; + // Bytes type: for images etc. + Status GetBytesData(std::vector &val) const; + Status GetStrData(std::string &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetData(std::vector &val) const; + Status GetFp16Data(std::vector &val) const; + DataType GetDataType() const; + std::vector GetShape() const; + + bool IsValid() const { return proto_tensor_ != nullptr; } + + protected: + ProtoMsgOwner message_owner_; + + private: + const proto::Tensor *proto_tensor_; +}; + +class MS_API MutableTensor : public Tensor { + public: + MutableTensor(const ProtoMsgOwner &owner, proto::Tensor *proto_tensor) + : Tensor(owner, proto_tensor), mutable_proto_tensor_(proto_tensor) {} + ~MutableTensor() = default; + + // Bytes type: for images etc. + Status SetBytesData(const std::vector &val); + Status SetStrData(const std::string &val); + + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + Status SetData(const std::vector &val, const std::vector &shape); + + Status SetData(const void *data, size_t data_bytes_len, const std::vector &shape, DataType data_type); + + private: + proto::Tensor *mutable_proto_tensor_; +}; + +class MS_API Instance { + public: + Instance(const ProtoMsgOwner &owner, const proto::Instance *proto_instance, const proto::ErrorMsg *error_msg) + : message_owner_(owner), proto_instance_(proto_instance), error_msg_(error_msg) {} + virtual ~Instance() = default; + + Tensor Get(const std::string &item_name) const; + + bool IsValid() const { return proto_instance_ != nullptr; } + bool HasErrorMsg(int64_t &error_code, std::string &error_msg) const; + + protected: + ProtoMsgOwner message_owner_; + + private: + const proto::Instance *proto_instance_; + const proto::ErrorMsg *error_msg_; +}; + +class MS_API MutableInstance : public Instance { + public: + MutableInstance(const ProtoMsgOwner &owner, proto::Instance *proto_instance) + : Instance(owner, proto_instance, nullptr), mutable_proto_instance_(proto_instance) {} + ~MutableInstance() = default; + + MutableTensor Add(const std::string &item_name); + + private: + proto::Instance *mutable_proto_instance_; +}; + +class MS_API InstancesRequest { + public: + InstancesRequest(); + ~InstancesRequest() = default; + MutableInstance AddInstance(); + + private: + std::shared_ptr request_ = nullptr; + friend class Client; +}; + +class MS_API InstancesReply { + public: + InstancesReply(); + ~InstancesReply() = default; + std::vector GetResult() const; + + private: + std::shared_ptr reply_ = nullptr; + friend class Client; +}; + +class ClientImpl; +class MS_API Client { + public: + Client(const std::string &server_ip, uint64_t server_port, const std::string &servable_name, + const std::string &method_name, uint64_t version_number = 0); + ~Client() = default; + + Status SendRequest(const InstancesRequest &request, InstancesReply &reply); + + private: + std::string server_ip_; + uint64_t server_port_; + std::string servable_name_; + std::string method_name_; + uint64_t version_number_ = 0; + std::shared_ptr impl_; +}; + +template +Status &Status::operator<<(T val) { + std::stringstream stringstream; + stringstream << val; + status_msg_ += stringstream.str(); + return *this; +} + +template +Status &Status::operator<<(const std::vector &val) { + operator<<("["); + for (size_t i = 0; i < val.size(); i++) { + operator<<(val[i]); + if (i != val.size() - 1) { + operator<<(", "); + } + } + operator<<("["); + return *this; +} + +} // namespace client +} // namespace serving +} // namespace mindspore + +#endif // MINDSPORE_SERVING_CLIENT_H diff --git a/mindspore_serving/client/python/__init__.py b/mindspore_serving/client/python/__init__.py new file mode 100644 index 0000000..e307743 --- /dev/null +++ b/mindspore_serving/client/python/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ diff --git a/mindspore_serving/client/python/client.py b/mindspore_serving/client/python/client.py new file mode 100644 index 0000000..d94dd14 --- /dev/null +++ b/mindspore_serving/client/python/client.py @@ -0,0 +1,201 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +import grpc +import numpy as np +import mindspore_serving.proto.ms_service_pb2 as ms_service_pb2 +import mindspore_serving.proto.ms_service_pb2_grpc as ms_service_pb2_grpc + + +def _create_tensor(data, tensor=None): + if tensor is None: + tensor = ms_service_pb2.Tensor() + + tensor.shape.dims.extend(data.shape) + dtype_map = { + np.bool: ms_service_pb2.MS_BOOL, + np.int8: ms_service_pb2.MS_INT8, + np.uint8: ms_service_pb2.MS_UINT8, + np.int16: ms_service_pb2.MS_INT16, + np.uint16: ms_service_pb2.MS_UINT16, + np.int32: ms_service_pb2.MS_INT32, + np.uint32: ms_service_pb2.MS_UINT32, + + np.int64: ms_service_pb2.MS_INT64, + np.uint64: ms_service_pb2.MS_UINT64, + np.float16: ms_service_pb2.MS_FLOAT16, + np.float32: ms_service_pb2.MS_FLOAT32, + np.float64: ms_service_pb2.MS_FLOAT64, + } + for k, v in dtype_map.items(): + if k == data.dtype: + tensor.dtype = v + break + if tensor.dtype == ms_service_pb2.MS_UNKNOWN: + raise RuntimeError("Unknown data type", data.dtype) + tensor.data = data.tobytes() + return tensor + + +def _create_scalar_tensor(vals, tensor=None): + if not isinstance(vals, (tuple, list)): + vals = (vals,) + return _create_tensor(np.array(vals), tensor) + + +def _create_bytes_tensor(bytes_vals, tensor=None): + if tensor is None: + tensor = ms_service_pb2.Tensor() + + if not isinstance(bytes_vals, (tuple, list)): + bytes_vals = (bytes_vals,) + tensor.shape.dims.extend([len(bytes_vals)]) + tensor.dtype = ms_service_pb2.MS_BYTES + for item in bytes_vals: + tensor.bytes_val.append(item) + return tensor + + +def _create_str_tensor(str_vals, tensor=None): + if tensor is None: + tensor = ms_service_pb2.Tensor() + + if not isinstance(str_vals, (tuple, list)): + str_vals = (str_vals,) + tensor.shape.dims.extend([len(str_vals)]) + tensor.dtype = ms_service_pb2.MS_STRING + for item in str_vals: + tensor.bytes_val.append(bytes(item, encoding="utf8")) + return tensor + + +def _create_numpy_from_tensor(tensor): + dtype_map = { + ms_service_pb2.MS_BOOL: np.bool, + ms_service_pb2.MS_INT8: np.int8, + ms_service_pb2.MS_UINT8: np.uint8, + ms_service_pb2.MS_INT16: ms_service_pb2.MS_INT16, + ms_service_pb2.MS_UINT16: np.uint16, + ms_service_pb2.MS_INT32: np.int32, + ms_service_pb2.MS_UINT32: np.uint32, + + ms_service_pb2.MS_INT64: np.int64, + ms_service_pb2.MS_UINT64: np.uint64, + ms_service_pb2.MS_FLOAT16: np.float16, + ms_service_pb2.MS_FLOAT32: np.float32, + ms_service_pb2.MS_FLOAT64: np.float64, + } + if tensor.dtype == ms_service_pb2.MS_STRING or tensor.dtype == ms_service_pb2.MS_BYTES: + result = [] + for item in tensor.bytes_val: + if tensor.dtype == ms_service_pb2.MS_STRING: + result.append(bytes.decode(item)) + else: + result.append(item) + if len(result) == 1: + return result[0] + return result + + result = np.frombuffer(tensor.data, dtype_map[tensor.dtype]).reshape(tensor.shape.dims) + if not tensor.shape.dims or (len(tensor.shape.dims) == 1 and tensor.shape.dims[0] == 1): + result = result.reshape((1,))[0] + return result + + +class Client: + def __init__(self, ip, port, servable_name, method_name, version_number=None): + ''' + Create Client connect to rving + :param ip: serving ip + :param port: serving port + :param servable_name: the name of servable supplied by serving + :param method_name: method supplied by servable + :param version_number: the version number of servable, default None. + None meaning the maximum version number in all running versions. + ''' + self.ip = ip + self.port = port + self.servable_name = servable_name + self.method_name = method_name + self.version_number = version_number + channel_str = str(ip) + ":" + str(port) + channel = grpc.insecure_channel(channel_str) + self.stub = ms_service_pb2_grpc.MSServiceStub(channel) + + def _create_request(self): + request = ms_service_pb2.PredictRequest() + request.servable_spec.name = self.servable_name + request.servable_spec.method_name = self.method_name + if self.version_number is not None: + request.servable_spec.version_number = self.version_number + return request + + def _create_instance(self, **kwargs): + instance = ms_service_pb2.Instance() + for k, w in kwargs.items(): + tensor = instance.items[k] + if isinstance(w, (np.ndarray, np.number)): + _create_tensor(w, tensor) + elif isinstance(w, str): + _create_str_tensor(w, tensor) + elif isinstance(w, (bool, int, float)): + _create_scalar_tensor(w, tensor) + elif isinstance(w, bytes): + _create_bytes_tensor(w, tensor) + else: + raise RuntimeError("Not support value type " + str(type(w))) + return instance + + def _paser_result(self, result): + error_msg_len = len(result.error_msg) + if error_msg_len == 1: + return {"error": bytes.decode(result.error_msg[0].error_msg)} + ret_val = [] + instance_len = len(result.instances) + if error_msg_len not in (0, instance_len): + raise RuntimeError( + "error msg result size " + error_msg_len + " not be 0,1 or length of instances " + str( + instance_len)) + for i in range(instance_len): + instance = result.instances[i] + if error_msg_len == 0 or result.error_msg[i].error_code == 0: + instance_map = {} + for k, w in instance.items.items(): + instance_map[k] = _create_numpy_from_tensor(w) + ret_val.append(instance_map) + else: + ret_val.append({"error": bytes.decode(result.error_msg[i].error_msg)}) + return ret_val + + def infer(self, instances): + if not isinstance(instances, (tuple, list)): + instances = (instances,) + request = self._create_request() + for item in instances: + if isinstance(item, dict): + request.instances.append(self._create_instance(**item)) + else: + raise RuntimeError("instance should be a map") + + try: + result = self.stub.Predict(request) + return self._paser_result(result) + + except grpc.RpcError as e: + print(e.details()) + status_code = e.code() + print(status_code.name) + print(status_code.value) + return {"error": status_code.value} diff --git a/mindspore_serving/example/add/add/servable_config.py b/mindspore_serving/example/add/add/servable_config.py new file mode 100644 index 0000000..f43d687 --- /dev/null +++ b/mindspore_serving/example/add/add/servable_config.py @@ -0,0 +1,51 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""add model servable config""" + +from mindspore_serving.worker import register +import numpy as np + + +# define preprocess pipeline, the function arg is multi instances, every instance is tuple of inputs +# this example has one input and one output +def add_trans_datatype(instances): + """preprocess python implement""" + for instance in instances: + x1 = instance[0] + x2 = instance[1] + yield x1.astype(np.float32), x2.astype(np.float32) + + +# when with_batch_dim set to False, only support 2x2 add +# when with_batch_dim set to True(default), support Nx2 add, while N is view as batch +# float32 inputs/outputs +register.declare_servable(servable_file="tensor_add.mindir", model_format="MindIR", with_batch_dim=False) + + +# register add_common method in add +@register.register_method(output_names=["y"]) +def add_common(x1, x2): # only support float32 inputs + """method add_common data flow definition, only call model servable""" + y = register.call_servable(x1, x2) + return y + + +# register add_cast method in add +@register.register_method(output_names=["y"]) +def add_cast(x1, x2): + """method add_cast data flow definition, only call preprocess and model servable""" + x1, x2 = register.call_preprocess(add_trans_datatype, x1, x2) # cast input to float32 + y = register.call_servable(x1, x2) + return y diff --git a/mindspore_serving/example/add/client.py b/mindspore_serving/example/add/client.py new file mode 100644 index 0000000..3d70f7e --- /dev/null +++ b/mindspore_serving/example/add/client.py @@ -0,0 +1,57 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""client example of add""" +from mindspore_serving.client import Client +import numpy as np + + +def run_add_common(): + """invoke servable add method add_common""" + client = Client("localhost", 5500, "add", "add_common") + instances = [] + + # instance 1 + x1 = np.asarray([[1, 1], [1, 1]]).astype(np.float32) + x2 = np.asarray([[1, 1], [1, 1]]).astype(np.float32) + instances.append({"x1": x1, "x2": x2}) + + # instance 2 + x1 = np.asarray([[2, 2], [2, 2]]).astype(np.float32) + x2 = np.asarray([[2, 2], [2, 2]]).astype(np.float32) + instances.append({"x1": x1, "x2": x2}) + + # instance 3 + x1 = np.asarray([[3, 3], [3, 3]]).astype(np.float32) + x2 = np.asarray([[3, 3], [3, 3]]).astype(np.float32) + instances.append({"x1": x1, "x2": x2}) + + result = client.infer(instances) + print(result) + + +def run_add_cast(): + """invoke servable add method add_cast""" + client = Client("localhost", 5500, "add", "add_cast") + instances = [] + x1 = np.ones((2, 2), np.int32) + x2 = np.ones((2, 2), np.int32) + instances.append({"x1": x1, "x2": x2}) + result = client.infer(instances) + print(result) + + +if __name__ == '__main__': + run_add_common() + run_add_cast() diff --git a/mindspore_serving/example/add/export_model/add_model.py b/mindspore_serving/example/add/export_model/add_model.py new file mode 100644 index 0000000..644d0e9 --- /dev/null +++ b/mindspore_serving/example/add/export_model/add_model.py @@ -0,0 +1,61 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""add model generator""" + +import numpy as np + +import mindspore.context as context +import mindspore.nn as nn +from mindspore.ops import operations as P +from mindspore import Tensor +from mindspore.train.serialization import export +import os +from shutil import copyfile + +context.set_context(mode=context.GRAPH_MODE, device_target="Ascend") + + +class Net(nn.Cell): + def __init__(self): + super(Net, self).__init__() + self.add = P.TensorAdd() + + def construct(self, x_, y_): + return self.add(x_, y_) + + +def export_net(): + x = np.ones([2, 2]).astype(np.float32) + y = np.ones([2, 2]).astype(np.float32) + add = Net() + output = add(Tensor(x), Tensor(y)) + export(add, Tensor(x), Tensor(y), file_name='tensor_add.mindir', file_format='MINDIR') + try: + os.mkdir("../add/1") + except: + pass + try: + copyfile('tensor_add.mindir', '../add/1/tensor_add.mindir') + print("copy tensor_add.mindir to ../add/1/ success") + except: + print("copy tensor_add.mindir to ../add/1/ failed") + + print(x) + print(y) + print(output.asnumpy()) + + +if __name__ == "__main__": + export_net() diff --git a/mindspore_serving/example/add/master_with_worker.py b/mindspore_serving/example/add/master_with_worker.py new file mode 100644 index 0000000..376c631 --- /dev/null +++ b/mindspore_serving/example/add/master_with_worker.py @@ -0,0 +1,30 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +import os +from mindspore_serving import master +from mindspore_serving import worker + + +def start(): + servable_dir = os.path.abspath(".") + worker.start_servable_in_master(servable_dir, "add", device_id=0) + + master.start_grpc_server("0.0.0.0", 5500) + master.start_restful_server("0.0.0.0", 1500) + + +if __name__ == "__main__": + start() diff --git a/mindspore_serving/example/lenet/client.py b/mindspore_serving/example/lenet/client.py new file mode 100644 index 0000000..6aa00c4 --- /dev/null +++ b/mindspore_serving/example/lenet/client.py @@ -0,0 +1,37 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +import os +from mindspore_serving.client import Client + + +def run_predict(): + client = Client("localhost", 5500, "lenet", "predict") + instances = [] + for path, _, file_list in os.walk("./test_image/"): + for file_name in file_list: + image_file = os.path.join(path, file_name) + print(image_file) + with open(image_file, "rb") as fp: + instances.append({"image": fp.read()}) + result = client.infer(instances) + if "error" in result: + print("error happen:", result["error"]) + return + print(result) + + +if __name__ == '__main__': + run_predict() diff --git a/mindspore_serving/example/lenet/cpp/lenet_client.cc b/mindspore_serving/example/lenet/cpp/lenet_client.cc new file mode 100644 index 0000000..7edd3f2 --- /dev/null +++ b/mindspore_serving/example/lenet/cpp/lenet_client.cc @@ -0,0 +1,175 @@ +#include "client.h" +#include +#include +#include +#include +#include + +using namespace mindspore::serving::client; + +struct Options { + std::string ip = "localhost"; + int64_t port = 0; + std::string img_dir; +}; + +Options g_options; + +std::vector GetAllFiles(const std::string &dir_path) { + DIR *dir = nullptr; + struct dirent *ptr = nullptr; + std::vector files; + + if ((dir = opendir(dir_path.c_str())) == nullptr) { + return std::vector(); + } + + while ((ptr = readdir(dir)) != nullptr) { + std::string name = ptr->d_name; + if (name == "." || name == "..") { + continue; + } + if (ptr->d_type == DT_REG) { + files.push_back(name); + } + } + closedir(dir); + std::sort(files.begin(), files.end()); + return files; +} + +template +std::string VectorAsString(const std::vector
&list) { + std::stringstream stringstream; + stringstream << "["; + for (size_t i = 0; i < list.size(); i++) { + stringstream << list[i]; + if (i != list.size() - 1) { + stringstream << ", "; + } + } + stringstream << "]"; + return stringstream.str(); +} + +void PrintResultTensor(const Tensor &result_tensor) { + if (!result_tensor.IsValid()) { + std::cout << "Get result failed" << std::endl; + return; + } + auto data_type = result_tensor.GetDataType(); + switch (data_type) { + case mindspore::serving::client::DT_INT32: { + std::vector result_label; + result_tensor.GetData(result_label); + std::cout << "result: " << VectorAsString(result_label) << std::endl; + break; + } + case mindspore::serving::client::DT_INT64: { + std::vector result_label; + result_tensor.GetData(result_label); + std::cout << "result: " << VectorAsString(result_label) << std::endl; + break; + } + case mindspore::serving::client::DT_STRING: { + std::string result_label; + result_tensor.GetStrData(result_label); + std::cout << "result: " << result_label << std::endl; + break; + } + default: + std::cout << "Unreginized data type " << data_type << std::endl; + break; + } +} + +void GetImageBuffer(std::vector> &img_bytes) { + std::string dir_path = g_options.img_dir; + auto files = GetAllFiles(dir_path); + for (auto &file : files) { + std::ifstream fp(dir_path + file, std::ios::binary); + if (!fp.is_open()) { + continue; + } + fp.seekg(0, std::ios_base::end); + auto file_len = fp.tellg(); + fp.seekg(0, std::ios_base::beg); + img_bytes.emplace_back(std::vector(file_len)); + auto &img_buffer = img_bytes.back(); + fp.read(reinterpret_cast(img_buffer.data()), img_buffer.size()); + std::cout << file << ", " << file_len << std::endl; + } +} + +void RunInstances() { + std::vector> img_bytes; + GetImageBuffer(img_bytes); + + Client client(g_options.ip, g_options.port, "lenet", "predict"); + InstancesRequest request; + InstancesReply reply; + for (auto &img : img_bytes) { + auto instance = request.AddInstance(); + auto instance_input = instance.Add("image"); + instance_input.SetBytesData(img); + } + auto status = client.SendRequest(request, reply); + if (!status.IsSuccess()) { + std::cout << "Instances Mode: Send request failed, failed detail: " << status.StatusMessage() << std::endl; + return; + } + const auto &result = reply.GetResult(); + for (auto &instance : result) { + if (!instance.IsValid()) { + std::cout << "Inputs Mode: Get result failed" << std::endl; + return; + } + int64_t error_code; + std::string error_msg; + if (instance.HasErrorMsg(error_code, error_msg)) { + std::cout << "Error: " << error_msg << std::endl; + continue; + } + auto result_tensor = instance.Get("result"); + PrintResultTensor(result_tensor); + } +} + +int main(int argc, char **argv) { + auto usage = []() { + std::cout << "Usage: " << std::endl; + std::cout << "--ip={ip}, optional, default localhost" << std::endl; + std::cout << "--port={port}, optional, default 5500" << std::endl; + std::cout << "--img_dir={img_dir}, required" << std::endl; + }; + if (argc > 1) { + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + if (arg.find("--ip=") == 0) { + g_options.ip = arg.substr(std::string("--ip=").length()); + } else if (arg.find("--port=") == 0) { + g_options.port = std::stoi(arg.substr(std::string("--port=").length())); + } else if (arg.find("--img_dir=") == 0) { + g_options.img_dir = arg.substr(std::string("--img_dir=").length()); + auto start = g_options.img_dir.find_first_not_of('\"'); + auto end = g_options.img_dir.find_last_of('\"'); + if (start != 0 || end != std::string::npos) { + g_options.img_dir = + g_options.img_dir.substr(start, end != std::string::npos ? end - start : std::string::npos); + } + } else { + usage(); + return -1; + } + } + } + if (g_options.img_dir.empty()) { + std::cout << "Expect image dir" << std::endl; + usage(); + return -1; + } + std::cout << "Image dir: " << g_options.img_dir << ", ip: " << g_options.ip << ", port: " << g_options.port + << std::endl; + RunInstances(); + return 0; +} diff --git a/mindspore_serving/example/lenet/lenet/servable_config.py b/mindspore_serving/example/lenet/lenet/servable_config.py new file mode 100644 index 0000000..bf6f440 --- /dev/null +++ b/mindspore_serving/example/lenet/lenet/servable_config.py @@ -0,0 +1,64 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +import numpy as np +from mindspore_serving.worker import register +import mindspore.dataset as ds +import mindspore.dataset.transforms.py_transforms as P +import mindspore.dataset.vision.py_transforms as PV + + +# define preprocess pipeline, the function arg is multi instances, every instance is tuple of inputs +# this example has one input and one output +def lenet_preprocess(instances): + def generator_func(): + for instance in instances: + image = instance[0] + yield (image,) + + mnist_ds = ds.GeneratorDataset(generator_func, ["image"], shuffle=False) + resize_height, resize_width = 32, 32 + nml_mean = [0.1307] + nml_std = [0.3081] + mnist_ds = mnist_ds.map(operations=P.Compose([ + PV.Decode(), + PV.Grayscale(1), + PV.Resize(size=(resize_height, resize_width)), + PV.ToTensor(), + PV.Normalize(nml_mean, nml_std) + ]), input_columns="image", num_parallel_workers=1) + for data in mnist_ds.create_dict_iterator(): + image_result = data["image"] + yield (image_result,) + + +# define postprocess pipeline, the function arg is multi instances, every instance is tuple of inputs +# this example has one input and one output +def lenet_postprocess(instances): + for instance in instances: + input = instance[0] + yield (np.argmax(input),) + + +register.declare_servable(servable_file="checkpoint_lenet_2-9_1875.om", model_format="OM") + + +# register predict method in lenet +@register.register_method(output_names=["result"]) +def predict(image): + x = register.call_preprocess(lenet_preprocess, image) + x = register.call_servable(x) + x = register.call_postprocess(lenet_postprocess, x) + return x diff --git a/mindspore_serving/example/lenet/master.py b/mindspore_serving/example/lenet/master.py new file mode 100644 index 0000000..d9c2eba --- /dev/null +++ b/mindspore_serving/example/lenet/master.py @@ -0,0 +1,26 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +from mindspore_serving import master + + +def start(): + master.start_grpc_server("0.0.0.0", 5500) + master.start_master_server("127.0.0.1", 6500) + master.start_restful_server("0.0.0.0", 1500) + + +if __name__ == "__main__": + start() diff --git a/mindspore_serving/example/lenet/master_with_worker.py b/mindspore_serving/example/lenet/master_with_worker.py new file mode 100644 index 0000000..397802d --- /dev/null +++ b/mindspore_serving/example/lenet/master_with_worker.py @@ -0,0 +1,30 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +import os +from mindspore_serving import master +from mindspore_serving import worker + + +def start(): + servable_dir = os.path.abspath(".") + worker.start_servable_in_master(servable_dir, "lenet", device_id=0) + + master.start_grpc_server("0.0.0.0", 5500) + master.start_restful_server("0.0.0.0", 1500) + + +if __name__ == "__main__": + start() diff --git a/mindspore_serving/example/lenet/worker.py b/mindspore_serving/example/lenet/worker.py new file mode 100644 index 0000000..f83c008 --- /dev/null +++ b/mindspore_serving/example/lenet/worker.py @@ -0,0 +1,28 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +from mindspore_serving import worker +import os + + +def start(): + servable_dir = os.path.abspath(".") + worker.start_servable(servable_dir, "lenet", device_id=0, + master_ip="127.0.0.1", master_port=6500, + host_ip="127.0.0.1", host_port=6600) + + +if __name__ == "__main__": + start() diff --git a/mindspore_serving/example/python_client/ms_client.py b/mindspore_serving/example/python_client/ms_client.py new file mode 100644 index 0000000..096f638 --- /dev/null +++ b/mindspore_serving/example/python_client/ms_client.py @@ -0,0 +1,136 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +import sys +import grpc +import numpy as np +import ms_service_pb2 +import ms_service_pb2_grpc + + +def create_tensor(data): + tensor = ms_service_pb2.Tensor() + tensor.shape.dims.extend(data.shape) + dtype_map = { + np.bool: ms_service_pb2.MS_BOOL, + np.int8: ms_service_pb2.MS_INT8, + np.uint8: ms_service_pb2.MS_UINT8, + np.int16: ms_service_pb2.MS_INT16, + np.uint16: ms_service_pb2.MS_UINT16, + np.int32: ms_service_pb2.MS_INT32, + np.uint32: ms_service_pb2.MS_UINT32, + + np.int64: ms_service_pb2.MS_INT64, + np.uint64: ms_service_pb2.MS_UINT64, + np.float16: ms_service_pb2.MS_FLOAT16, + np.float32: ms_service_pb2.MS_FLOAT32, + np.float64: ms_service_pb2.MS_FLOAT64, + np.str: ms_service_pb2.MS_STRING, + np.bytes: ms_service_pb2.MS_BYTES, + } + tensor.dtype = dtype_map[data.dtype] + tensor.data = data.tobytes() + return tensor + + +def run_inputs(): + if len(sys.argv) > 2: + sys.exit("input error") + channel_str = "" + if len(sys.argv) == 2: + split_args = sys.argv[1].split('=') + if len(split_args) > 1: + channel_str = split_args[1] + else: + channel_str = 'localhost:5500' + else: + channel_str = 'localhost:5500' + + channel = grpc.insecure_channel(channel_str) + stub = ms_service_pb2_grpc.MSServiceStub(channel) + request = ms_service_pb2.PredictRequest() + request.servable_spec.name = "add_test" + request.servable_spec.method_name = "method_test" + + request.inputs["x0"] = create_tensor(np.ones([2, 2]).astype(np.float32)) + request.inputs["x1"] = create_tensor(np.ones([2, 2]).astype(np.float32)) + request.inputs["x2"] = create_tensor(np.ones([2, 2]).astype(np.float32)) + + try: + result = stub.Predict(request) + print("ms client received: ") + if not result.error_msg: + result_np = np.frombuffer(result.result[0].data, dtype=np.float32).reshape( + result.result[0].tensor_shape.dims) + print(result_np) + else: + print(result.error_msg) + except grpc.RpcError as e: + print(e.details()) + status_code = e.code() + print(status_code.name) + print(status_code.value) + + +def run_instances(): + if len(sys.argv) > 2: + sys.exit("input error") + if len(sys.argv) == 2: + split_args = sys.argv[1].split('=') + if len(split_args) > 1: + channel_str = split_args[1] + else: + channel_str = 'localhost:5500' + else: + channel_str = 'localhost:5500' + + channel = grpc.insecure_channel(channel_str) + stub = ms_service_pb2_grpc.MSServiceStub(channel) + request = ms_service_pb2.PredictRequest() + request.servable_spec.name = "add_test" + request.servable_spec.method_name = "method_test" + + for i in range(5): + instance = request.instances.add() + instance.items["x0"] = create_tensor(np.ones([2, ]).astype(np.float32)) + instance.items["x1"] = create_tensor(np.ones([2, ]).astype(np.float32)) + instance.items["x2"] = create_tensor(np.ones([2, ]).astype(np.float32)) + try: + result = stub.Predict(request) + print("ms client received: ") + if not result.error_msg: + result_np = np.frombuffer(result.result[0].data, dtype=np.float32).reshape( + result.result[0].tensor_shape.dims) + print(result_np) + elif len(result.error_msg) == 1: + print(result.error_msg[0].error_msg) + else: + for i in range(len(result.error_msg)): + print("result of ", i) + if result.error_msg[i].error_code != 0: + print(result.error_msg[i].error_msg) + else: + print(result.instances[i]) + + except grpc.RpcError as e: + print(e.details()) + status_code = e.code() + print(status_code.name) + print(status_code.value) + + +if __name__ == '__main__': + run_inputs() + run_instances() diff --git a/mindspore_serving/master/__init__.py b/mindspore_serving/master/__init__.py new file mode 100644 index 0000000..81d8a75 --- /dev/null +++ b/mindspore_serving/master/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +from ._master import start_grpc_server, start_restful_server, start_master_server diff --git a/mindspore_serving/master/_master.py b/mindspore_serving/master/_master.py new file mode 100644 index 0000000..21802ac --- /dev/null +++ b/mindspore_serving/master/_master.py @@ -0,0 +1,72 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""method of server supplied for master""" + +import threading +from functools import wraps +from mindspore_serving._mindspore_serving import Master_ + +_wait_and_clear_thread = None + + +# waiting for Ctrl+C, and clear +def _start_wait_and_clear(): + def thread_func(): + print("Serving master: wait for Ctrl+C to exit ------------------------------------") + Master_.wait_and_clear() + + global _wait_and_clear_thread + if not _wait_and_clear_thread: + _wait_and_clear_thread = threading.Thread(target=thread_func) + _wait_and_clear_thread.start() + + +def stop(): + Master_.stop() + + +def stop_on_except(func): + @wraps(func) + def handle_except(*args, **kwargs): + try: + func(*args, **kwargs) + except: + stop() + raise + + return handle_except + + +@stop_on_except +def start_grpc_server(ip="0.0.0.0", grpc_port=5500, max_msg_mb_size=100): + """start grpc server for the communication between client and serving. + the ip should be accessible to the client.""" + Master_.start_grpc_server(ip, grpc_port, max_msg_mb_size) + _start_wait_and_clear() + + +@stop_on_except +def start_master_server(ip="0.0.0.0", grpc_port=6100): + """start grpc server for the communication between workers and the master. + the ip is expected to be accessed only by workers.""" + Master_.start_grpc_master_server(ip, grpc_port) + + +@stop_on_except +def start_restful_server(ip="0.0.0.0", restful_port=5900, max_msg_mb_size=100): + """start restful server for the communication between client and serving. + the ip should be accessible to the client.""" + Master_.start_restful_server(ip, restful_port, max_msg_mb_size) + _start_wait_and_clear() diff --git a/mindspore_serving/proto/ms_master.proto b/mindspore_serving/proto/ms_master.proto new file mode 100644 index 0000000..65a2c45 --- /dev/null +++ b/mindspore_serving/proto/ms_master.proto @@ -0,0 +1,74 @@ +/** + * Copyright 2019 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ms_manager.proto +syntax = "proto3"; + +package mindspore.serving.proto; +import "mindspore_serving/proto/ms_service.proto"; + +service MSMaster { + rpc Register(RegisterRequest) returns (RegisterReply) {} + rpc Exit(ExitRequest) returns (ExitReply) {} + rpc AddWorker(AddWorkerRequest) returns (AddWorkerReply) {} + rpc RemoveWorker(RemoveWorkerRequest) returns (RemoveWorkerReply) {} +} + +message WorkerSpec { + string name = 1; + int64 version_number = 2; + + message MethodInfo{ + string name = 1; + repeated string input_names = 2; + } + repeated MethodInfo methods = 5; +} + +message RegisterRequest { + repeated WorkerSpec worker_spec = 1; + string address = 2; +} + +message RegisterReply { + ErrorMsg error_msg = 1; +} + +message ExitRequest { + string address = 1; +} + +message ExitReply { + ErrorMsg error_msg = 1; +} + +message AddWorkerRequest { + WorkerSpec worker_spec = 1; + string address = 2; +} + +message AddWorkerReply { + ErrorMsg error_msg = 1; +} + +message RemoveWorkerRequest { + WorkerSpec worker_spec = 1; + string address = 2; +} + +message RemoveWorkerReply { + ErrorMsg error_msg = 1; +} diff --git a/mindspore_serving/proto/ms_service.proto b/mindspore_serving/proto/ms_service.proto new file mode 100644 index 0000000..1dbb9cd --- /dev/null +++ b/mindspore_serving/proto/ms_service.proto @@ -0,0 +1,93 @@ +/** + * Copyright 2019 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ms_service.proto +syntax = "proto3"; + +package mindspore.serving.proto; + +service MSService { + rpc Predict(PredictRequest) returns (PredictReply) {} + +} + +message PredictRequest { + ServableSpec servable_spec = 1; + repeated Instance instances = 3; +} + +message ErrorMsg{ + int64 error_code = 1; // 0 is valid, otherwise invalid + bytes error_msg = 2; +} + +message PredictReply { + ServableSpec servable_spec = 1; + repeated Instance instances = 3; + // size 0: OK, 1: for all batch, >1: for every batch + repeated ErrorMsg error_msg = 4; +} + +message Instance{ + map items = 1; +} + +enum DataType { + MS_UNKNOWN = 0; + MS_BOOL = 1; + MS_INT8 = 2; + MS_UINT8 = 3; + MS_INT16 = 4; + MS_UINT16 = 5; + MS_INT32 = 6; + MS_UINT32 = 7; + MS_INT64 = 8; + MS_UINT64 = 9; + MS_FLOAT16 = 10; + MS_FLOAT32 = 11; + MS_FLOAT64 = 12; + MS_STRING = 13; // for string model input + MS_BYTES = 14; // for images +} + +message TensorShape { + repeated int64 dims = 1; +}; + +message Tensor { + // tensor shape info + TensorShape shape = 1; + + // tensor content data type + DataType dtype = 2; + + // tensor data + bytes data = 3; + + // for string type and images, the dtype is MS_BYTES. + repeated bytes bytes_val = 4; +} + +message ServableSpec { + // servable name + string name = 1; + + // optional. If unspecified, the latest version servable will be used. + int64 version_number = 3; + + // Specifies the method name in the servable. + string method_name = 2; +} diff --git a/mindspore_serving/proto/ms_worker.proto b/mindspore_serving/proto/ms_worker.proto new file mode 100644 index 0000000..c9ed051 --- /dev/null +++ b/mindspore_serving/proto/ms_worker.proto @@ -0,0 +1,27 @@ +/** + * Copyright 2019 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// ms_manager.proto +syntax = "proto3"; + +package mindspore.serving.proto; +import "mindspore_serving/proto/ms_service.proto"; +import "mindspore_serving/proto/ms_master.proto"; + +service MSWorker { + rpc Predict(PredictRequest) returns (PredictReply) {} + rpc Exit(ExitRequest) returns (ExitReply) {} +} diff --git a/mindspore_serving/worker/__init__.py b/mindspore_serving/worker/__init__.py new file mode 100644 index 0000000..01c804e --- /dev/null +++ b/mindspore_serving/worker/__init__.py @@ -0,0 +1,17 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +from . import register +from ._worker import start_servable, start_servable_in_master, stop diff --git a/mindspore_serving/worker/_worker.py b/mindspore_serving/worker/_worker.py new file mode 100644 index 0000000..05e9595 --- /dev/null +++ b/mindspore_serving/worker/_worker.py @@ -0,0 +1,182 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Inferface for start up servable""" + +import threading +from functools import wraps +from mindspore_serving._mindspore_serving import Worker_ +from . import context +from .task import start_py_task +from . import check_type + +_wait_and_clear_thread = None + + +# waiting for Ctrl+C, and clear +def _start_wait_and_clear(): + def thread_func(): + print("Serving worker: wait for Ctrl+C to exit ------------------------------------") + Worker_.wait_and_clear() + + global _wait_and_clear_thread + if not _wait_and_clear_thread: + _wait_and_clear_thread = threading.Thread(target=thread_func) + _wait_and_clear_thread.start() + + +def stop(): + Worker_.stop() + + +def stop_on_except(func): + @wraps(func) + def handle_except(*args, **kwargs): + try: + func(*args, **kwargs) + except: + stop() + raise + + return handle_except + + +def _load_servable_config(servable_directory, servable_name): + import sys + sys.path.append(servable_directory) + __import__(servable_name + ".servable_config") + + +# define version_strategy : "lastest" "specific" "multi" +# device_type: 'Ascend910', 'Ascend310' +@stop_on_except +def start_servable(servable_directory, servable_name, version_number=0, + device_type=None, device_id=0, + master_ip="0.0.0.0", master_port=6100, host_ip="0.0.0.0", host_port=6200): + r""" + Start up the servable named 'servable_name' defined in 'servable_directory', and the servable linked to the master + through gRPC (master_ip, master_port). + + Serving has two running modes. One is running in a single process, providing the Serving service of a single model. + The other includes a master and multiple workers. The master is responsible for providing the Serving access + interface for client, the worker is responsible for providing the service of the specific model, and the master + and worker communicate through gPRC defined as (master_ip, master_port) and (host_ip, host_port). + + Args: + servable_directory (str): The directory where the servable located in, there expected to has a directory named + `servable_name`. For example: + servables_dir/ # servable_directory + └─ bert/ # servable_name + ├───1/ # version 1 + │ └─── bert.mindir + ├───2/ # version 2 + │ └─── bert.mindir + └──servable_config.py + + servable_name (str): The servable name. + version_number (int): Model version number to be loaded. odel version ould be a positive integer. + 0 means to load the latest version. + In other cases, such as 1, it means to load the specified version. Default: 0. + device_type (str): Current only support "Ascend" and None. + "Ascend" which means device type can Ascend910 or Ascend310, etc. + None: The device type is determined by the mindspore environment. + Default: None. + device_id (int): The id of the device the model loads into and runs in. + master_ip (str): The master ip the worker linked to. + master_port (int): The master port the worker linked to. + host_ip (str): The worker ip the master linked to. + host_port (int): The worker port the master linked to. + """ + check_type.check_str(servable_directory) + check_type.check_str(servable_name) + check_type.check_int(version_number) + + if device_type: + check_type.check_str(device_type) + check_type.check_int(device_id) + + check_type.check_str(master_ip) + check_type.check_int(master_port) + + check_type.check_str(host_ip) + check_type.check_int(host_port) + + _load_servable_config(servable_directory, servable_name) + + if device_type is not None: + context.set_context(device_type=device_type) + else: + context.set_context(device_type='None') # depend on register implement + + context.set_context(device_id=device_id) + + Worker_.start_servable(servable_directory, servable_name, version_number, master_ip, master_port, + host_ip, host_port) + start_py_task(Worker_.get_batch_size()) + _start_wait_and_clear() + + +# define version_strategy : "lastest" "specific" "multi" +@stop_on_except +def start_servable_in_master(servable_directory, servable_name, version_number=0, device_type=None, + device_id=0): + r""" + Start up the servable named 'servable_name' defined in 'svable_directory', and the servable will running in + the process of the master. + + Serving has two running modes. One is running in a single process, providing the Serving service of a single model. + The other includes a master and multiple workers. The master is responsible for providing the Serving access + interface for client, the worker is responsible for providing the service of the specific model, and the master and worker + communicate through gPRC. + + Args: + servable_directory (str): The directory where the servable located in, there expected to has a directory named + `servable_name`. For example: + servables_dir/ # servable_directory + └─ bert/ # servable_name + ├───1/ # version 1 + │ └─── bert.mindir + ├───2/ # version 2 + │ └─── bert.mindir + └──servable_config.py + + servable_name (str): The servable name. + version_number (int): Model version number to be loaded. odel version ould be a positive integer. + 0 means to load the latest version. + In other cases, such as 1, it means to load the specified version. Default: 0. + device_type (str): Current only support "Ascend" and None. + "Ascend" which means device type can be Ascend910 or Ascend310, etc. + None: The device type is determined by the mindspore environment. + Default: None. + device_id (int): The id of the device the model loads into and runs in. + """ + check_type.check_str(servable_directory) + check_type.check_str(servable_name) + check_type.check_int(version_number) + + if device_type: + check_type.check_int(device_type) + check_type.check_int(device_id) + + _load_servable_config(servable_directory, servable_name) + + if device_type is not None: + context.set_context(device_type=device_type) + else: + context.set_context(device_type='None') # depend on register implement + + context.set_context(device_id=device_id) + Worker_.start_servable_in_master(servable_directory, servable_name, version_number) + start_py_task(Worker_.get_batch_size()) + _start_wait_and_clear() diff --git a/mindspore_serving/worker/check_type.py b/mindspore_serving/worker/check_type.py new file mode 100644 index 0000000..8f05e03 --- /dev/null +++ b/mindspore_serving/worker/check_type.py @@ -0,0 +1,51 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""T check for worker""" + + +def check_and_as_str_tuple_list(strs): + """Check whether the input parameters are reasonable multiple str inputs, + which can be single str, tuple or list of str. + finally, return tuple of str""" + if isinstance(strs, str): + strs = (strs,) + + if not isinstance(strs, (tuple, list)): + raise RuntimeError("Check failed, expecting str or tuple/list of str, actually", type(strs)) + + if isinstance(strs, (tuple, list)): + for item in strs: + if not isinstance(item, str): + raise RuntimeError("Check failed, expecting tuple/st to be str, actually", type(item)) + + return tuple(strs) + + +def check_str(str_val): + """Check whether the input parameters are reasonable str input""" + if not isinstance(str_val, str): + raise RuntimeError("Check str failed, expecting str, actually", type(str_val)) + + +def check_bool(bool_val): + """Check whether the input parameters are reasonable bool input""" + if not isinstance(bool_val, bool): + raise RuntimeError("Check bool failed, expecting bool, actually", type(bool_val)) + + +def check_int(int_val): + """Check whether the input parameters are reasonable int input""" + if not isinstance(int_val, int): + raise RuntimeError("Check failed, expecting int, actually", {type(int_val)}) diff --git a/mindspore_serving/worker/common.py b/mindspore_serving/worker/common.py new file mode 100644 index 0000000..488c766 --- /dev/null +++ b/mindspore_serving/worker/common.py @@ -0,0 +1,35 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Common implement for worker""" +import inspect +import os + + +def get_servable_dir(): + """Get the directory where servable is located. The name of the directory is the name of servable""" + stack = inspect.stack() + for item in stack: + if item.filename.endswith("servable_config.py"): + abs_path = os.path.realpath(item.filename) + last_dir = os.path.split(abs_path)[0] + last_dir = os.path.split(last_dir)[1] + if not last_dir: + continue + return last_dir + raise RuntimeError("Failed to obtain the directory of servable_config.py") + + +def get_func_name(func): + return func.__name__ diff --git a/mindspore_serving/worker/context.py b/mindspore_serving/worker/context.py new file mode 100644 index 0000000..f08b693 --- /dev/null +++ b/mindspore_serving/worker/context.py @@ -0,0 +1,60 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Context setting interface""" +from mindspore_serving._mindspore_serving import Context_ + + +class Context: + def __init__(self): + self.context_ = Context_.get_instance() + + def set_device_type(self, device_type): + self.context_.set_device_type_str(device_type) + + def set_device_id(self, device_id): + self.context_.set_device_id(device_id) + + +_k_context = None + + +def _context(): + """ + Get the global _context, if context is not created, create a new one. + + Returns: + _Context, the global context in PyNative mode. + """ + global _k_context + if _k_context is None: + _k_context = Context() + return _k_context + + +def set_context(**kwargs): + """The context setting interf. The acceptable parameters including: + device_type: 'Ascend','Davinci', 'None'. Case ignored. + 'Davinci' and 'Ascend' are the same. + 'None' means depend on mindspore register + device_id: reasonable vice id + """ + context = _context() + for (k, w) in kwargs.items(): + if k == "device_type": + context.set_device_type(w) + elif k == "device_id": + context.set_device_id(w) + else: + raise RuntimeError("Not support context key " + k) diff --git a/mindspore_serving/worker/register/__init__.py b/mindspore_serving/worker/register/__init__.py new file mode 100644 index 0000000..93954f1 --- /dev/null +++ b/mindspore_serving/worker/register/__init__.py @@ -0,0 +1,2 @@ +from .servable import declare_servable +from .method import register_method, call_preprocess, call_servable, call_postprocess diff --git a/mindspore_serving/worker/register/method.py b/mindspore_serving/worker/register/method.py new file mode 100644 index 0000000..42e660b --- /dev/null +++ b/mindspore_serving/worker/register/method.py @@ -0,0 +1,266 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Method registration interface""" + +import inspect +import ast +from easydict import EasyDict + +from mindspore_serving._mindspore_serving import PreprocessStorage_, PostprocessStorage_ +from mindspore_serving._mindspore_serving import ServableStorage_, MethodSignature_, PredictPhaseTag_ +from mindspore_serving.worker.common import get_func_name, get_servable_dir +from mindspore_serving.worker import check_type +from .preprocess import register_preprocess, check_preprocess +from .postprocess import register_postprocess, check_postprocess + +method_def_context_ = MethodSignature_() +method_def_ast_meta_ = EasyDict() + +method_tag_input = PredictPhaseTag_.kPredictPhaseTag_Input +method_tag_preprocess = PredictPhaseTag_.kPredictPhaseTag_Preproces +method_tag_predict = PredictPhaseTag_.kPredictPhaseTag_Predict +method_tag_postprocess = PredictPhaseTag_.kPredictPhaseTag_Postprocess + + +class _ServableStorage: + def __init__(self): + self.methods = {} + self.servable_metas = {} + self.storage = ServableStorage_.get_instance() + + def declare_servable(self, servable_meta): + self.storage.declare_servable(servable_meta) + self.servable_metas[servable_meta.servable_name] = servable_meta + + def declare_servable_input_output(self, servable_name, inputs_count, outputs_count): + self.storage.register_servable_input_output_info(servable_name, inputs_count, outputs_count) + servable_meta = self.servable_metas[servable_name] + servable_meta.inputs_count = inputs_count + servable_meta.outputs_count = outputs_count + + def register_method(self, method_signature): + self.storage.register_method(method_signature) + self.methods[method_signature.method_name] = method_signature + + def get_method(self, method_name): + method = self.methods.get(method_name, None) + if method is None: + raise RuntimeError(f"Method {method_name} not found") + return method + + def get_servable_meta(self, servable_name): + servable = self.servable_metas.get(servable_name, None) + if servable is None: + raise RuntimeError(f"Servable {servable_name} not found") + return servable + + +_servable_storage = _ServableStorage() + + +class _TensorDef: + def __init__(self, tag, tensor_index): + self.tag = tag + self.tensor_index = tensor_index + + def as_pair(self): + return (self.tag, self.tensor_index) + + +def _create_tensor_def_outputs(tag, outputs_cnt): + result = [_TensorDef(tag, i) for i in range(outputs_cnt)] + if len(result) == 1: + return result[0] + return tuple(result) + + +def call_preprocess(preprocess_fun, *args): + """For method registration, define the inputs data of preprocess + preprocess_fun can be : + Python function of preprocess. + The name of the preprocess implemented by C++ registered by REGISTER_PREPROCESS. + The input parameters number of implemented python and C++ function should equal to length of 'args' + """ + if _call_preprocess_name not in method_def_ast_meta_: + raise RuntimeError(f"Invalid call of ${_call_preprocess_name}") + inputs_count, outputs_count = method_def_ast_meta_[_call_preprocess_name] + + preprocess_name = preprocess_fun + if inspect.isfunction(preprocess_fun): + register_preprocess(inputs_count=inputs_count, outputs_count=outputs_count)(preprocess_fun) + preprocess_name = get_servable_dir() + "." + get_func_name(preprocess_fun) + else: + if not isinstance(preprocess_name, str): + raise RuntimeError( + f"Check failed, call_preprocess first must be function or str, now is {type(preprocess_name)}") + check_preprocess(preprocess_name, inputs_count=inputs_count, outputs_count=outputs_count) + + global method_def_context_ + method_def_context_.preprocess_name = preprocess_name + method_def_context_.preprocess_inputs = [item.as_pair() for item in args] + + return _create_tensor_def_outputs(method_tag_preprocess, outputs_count) + + +def call_servable(*args): + """For method registration, define the inputs data of model inference + The length of 'args' should be equal to model inputs number declared by declare_servable + """ + servable_name = get_servable_dir() + inputs_count, outputs_count = method_def_ast_meta_[_call_servable_name] + _servable_storage.declare_servable_input_output(servable_name, inputs_count, outputs_count) + if inputs_count != len(args): + raise RuntimeError(f"Given servable input size {len(args)} not match " + f"{servable_name} ast parse size ${inputs_count}") + + global method_def_context_ + method_def_context_.servable_name = servable_name + method_def_context_.servable_inputs = [item.as_pair() for item in args] + + return _create_tensor_def_outputs(method_tag_predict, outputs_count) + + +def call_postprocess(postprocess_fun, *args): + """For method registration, define the inputs data of postprocess + postprocess_name can be : + Python function of postprocess. + The name of the postprocess implemented by C++ registered by REGISTER_POSTPROCESS. + The input parameters number of implemented python and C++ function should equal to length of 'args' + """ + if _call_postprocess_name not in method_def_ast_meta_: + raise RuntimeError(f"Invalid call of ${_call_postprocess_name}") + inputs_count, outputs_count = method_def_ast_meta_[_call_postprocess_name] + + postprocess_name = postprocess_fun + if inspect.isfunction(postprocess_fun): + register_postprocess(inputs_count=inputs_count, outputs_count=outputs_count)(postprocess_fun) + postprocess_name = get_servable_dir() + "." + get_func_name(postprocess_fun) + else: + if not isinstance(postprocess_name, str): + raise RuntimeError( + f"Check failed, call_postprocess first must be function or str, now is {type(postprocess_name)}") + check_postprocess(postprocess_name, inputs_count=inputs_count, outputs_count=outputs_count) + + global method_def_context_ + method_def_context_.postprocess_name = postprocess_name + method_def_context_.postprocess_inputs = [item.as_pair() for item in args] + + return _create_tensor_def_outputs(method_tag_postprocess, outputs_count) + + +_call_preprocess_name = call_preprocess.__name__ +_call_servable_name = call_servable.__name__ +_call_postprocess_name = call_postprocess.__name__ + + +def _get_method_def_func_meta(method_def_func): + source = inspect.getsource(method_def_func) + call_list = ast.parse(source).body[0].body + func_meta = EasyDict() + + for call_item in call_list: + if not isinstance(call_item, ast.Assign): + continue + target = call_item.targets[0] + if isinstance(target, ast.Name): + outputs_count = 1 + elif isinstance(target, ast.Tuple): + outputs_count = len(target.elts) + else: + continue + + call = call_item.value + if not isinstance(call, ast.Call): + continue + func = call.func + if isinstance(func, ast.Attribute): + func_name = func.attr + elif isinstance(func, ast.Name): + func_name = func.id + else: + continue + + inputs_count = len(call.args) + if func_name in (_call_preprocess_name, _call_postprocess_name): + inputs_count -= 1 + elif func_name == _call_servable_name: + pass + else: + continue + + if inputs_count <= 0: + raise RuntimeError(f"Invalid {func.id} invoke args") + + print(f"call type {func_name}, inputs count {inputs_count}, outputs count {outputs_count}") + func_meta[func_name] = [inputs_count, outputs_count] + + if _call_servable_name not in func_meta: + raise RuntimeError(f"Not find the invoke of {_call_servable_name}") + return func_meta + + +def register_method(output_names): + """register method for servable. + Define the data flow of preprocess, model inference and postprocess in the method. + Preprocess and postprocess are optional. + Example: + @register_method(output_names="y") + def method_name(x1, x2): + x1, x2 = call_preprocess(preprocess_fun, x1, x2) + y = call_servable(y) + y = call_postprocess(postprocess_fun, y) + return y + """ + output_names = check_type.check_and_as_str_tuple_list(output_names) + + def register(func): + name = get_func_name(func) + sig = inspect.signature(func) + input_names = [] + for k, v in sig.parameters.items(): + if v.kind == inspect.Parameter.VAR_POSITIONAL: + raise RuntimeError(name + " input %s cannot be VAR_POSITIONAL !" % k) + if v.kind == inspect.Parameter.VAR_KEYWORD: + raise RuntimeError(name + " input %s cannot be VAR_KEYWORD !" % k) + input_names.append(k) + + input_tensors = [] + for i in range(len(input_names)): + input_tensors.append(_TensorDef(method_tag_input, i)) + + global method_def_context_ + method_def_context_ = MethodSignature_() + global method_def_ast_meta_ + method_def_ast_meta_ = _get_method_def_func_meta(func) + + output_tensors = func(*tuple(input_tensors)) + if isinstance(output_tensors, _TensorDef): + output_tensors = (output_tensors,) + if len(output_tensors) != len(output_names): + raise RuntimeError( + f"Method return output size {len(output_tensors)} not match registed {len(output_names)}") + + method_def_context_.method_name = name + method_def_context_.inputs = input_names + method_def_context_.outputs = output_names + method_def_context_.returns = [item.as_pair() for item in output_tensors] + print("------------Register method: method_name", method_def_context_.method_name, + ", servable_name", method_def_context_.servable_name, ", inputs", input_names, ", outputs", output_names) + + global _servable_storage + _servable_storage.register_method(method_def_context_) + return func + + return register diff --git a/mindspore_serving/worker/register/postprocess.py b/mindspore_serving/worker/register/postprocess.py new file mode 100644 index 0000000..3d624a0 --- /dev/null +++ b/mindspore_serving/worker/register/postprocess.py @@ -0,0 +1,72 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Postprocessing registration interface""" + +from mindspore_serving._mindspore_serving import PostprocessStorage_ +from mindspore_serving.worker import check_type +from mindspore_serving.worker.common import get_servable_dir, get_func_name + + +def check_postprocess(postprocess_name, inputs_count, outputs_count): + postprocess_info = PostprocessStorage_.get_instance().get_pycpp_postprocess_info(postprocess_name) + if not postprocess_info: + return + last_inputs_count, last_output_count = postprocess_info + if last_inputs_count != inputs_count: + raise RuntimeError(f" Postprocess {postprocess_name} inputs count {inputs_count} not match " + f"last registered count {last_inputs_count}") + if last_output_count != outputs_count: + raise RuntimeError(f" Postprocess {postprocess_name} outputs count {outputs_count} not match " + f"last registered count {last_output_count}") + + +class PostprocessStorage: + def __init__(self): + self.postprocess = {} + self.storage = PostprocessStorage_.get_instance() + + def register(self, fun, postprocess_name, inputs_count, outputs_count): + check_postprocess(postprocess_name, inputs_count, outputs_count) + self.postprocess[postprocess_name] = {"fun": fun, "inputs_count": inputs_count, "outputs_count": outputs_count} + self.storage.register(postprocess_name, inputs_count, outputs_count) + + def get(self, postprocess_name): + postprocess = self.postprocess.get(postprocess_name, None) + if postprocess is None: + raise RuntimeError("Postprocess " + postprocess_name + " not found") + return postprocess + + +postprocess_storage = PostprocessStorage() + + +def register_postprocess(inputs_count, outputs_count): + """register postprocess, input_names and output_names can be str, tuple or list of str. + For input_names and output_names, serving only consider the number of names contained in them, + which should be consistent with the number of input and output used in register_method, + and the specific names content are ignored.""" + check_type.check_int(inputs_count) + check_type.check_int(outputs_count) + + def register(func): + servable_name = get_servable_dir() + func_name = get_func_name(func) + name = servable_name + "." + func_name + + print("------------Register postprocess", name, inputs_count, outputs_count) + postprocess_storage.register(func, name, inputs_count, outputs_count) + return func + + return register diff --git a/mindspore_serving/worker/register/preprocess.py b/mindspore_serving/worker/register/preprocess.py new file mode 100644 index 0000000..570f6b2 --- /dev/null +++ b/mindspore_serving/worker/register/preprocess.py @@ -0,0 +1,72 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Preprocessing registration interface""" + +from mindspore_serving._mindspore_serving import PreprocessStorage_ +from mindspore_serving.worker import check_type +from mindspore_serving.worker.common import get_servable_dir, get_func_name + + +def check_preprocess(preprocess_name, inputs_count, outputs_count): + preprocess_info = PreprocessStorage_.get_instance().get_pycpp_preprocess_info(preprocess_name) + if not preprocess_info: + return + last_inputs_count, last_output_count = preprocess_info + if last_inputs_count != inputs_count: + raise RuntimeError(f"Preprocess {preprocess_name} inputs count {inputs_count} not match " + f"last registered count {last_inputs_count}") + if last_output_count != outputs_count: + raise RuntimeError(f"retprocess {preprocess_name} outputs count {outputs_count} not match " + f"last registered count {last_output_count}") + + +class PreprocessStorage: + def __init__(self): + self.preprocess = {} + self.storage = PreprocessStorage_.get_instance() + + def register(self, fun, preprocess_name, inputs_count, outputs_count): + check_preprocess(preprocess_name, inputs_count, outputs_count) + self.preprocess[preprocess_name] = {"fun": fun, "inputs_count": inputs_count, "outputs_count": outputs_count} + self.storage.register(preprocess_name, inputs_count, outputs_count) + + def get(self, preprocess_name): + preprocess = self.preprocess.get(preprocess_name, None) + if preprocess is None: + raise RuntimeError("Preprocess " + preprocess_name + " not found") + return preprocess + + +preprocess_storage = PreprocessStorage() + + +def register_preprocess(inputs_count, outputs_count): + """register preprocess, input_names and output_names can be str, tuple or list of str. + For input_names and output_names, serving only consider the number of names contained in them, + which should be consistent with the number of input and output used in register_method, + and the specific names content are ignored.""" + check_type.check_int(inputs_count) + check_type.check_int(outputs_count) + + def register(func): + servable_name = get_servable_dir() + func_name = get_func_name(func) + name = servable_name + "." + func_name + + print("------------Register preprocess", name, inputs_count, outputs_count) + preprocess_storage.register(func, name, inputs_count, outputs_count) + return func + + return register diff --git a/mindspore_serving/worker/register/servable.py b/mindspore_serving/worker/register/servable.py new file mode 100644 index 0000000..910c16c --- /dev/null +++ b/mindspore_serving/worker/register/servable.py @@ -0,0 +1,48 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Servable declaration interface""" + +from mindspore_serving._mindspore_serving import ServableMeta_ +from mindspore_serving.worker import check_type +from mindspore_serving.worker.common import get_servable_dir +from .method import _servable_storage + + +# with_batch_dim means the model first dim is batch, 'inputs mode' request can split into multi instances, +# otherwise, 'inputs mode' request will view as one instance +# model_format: OM, MindIR +def declare_servable(servable_file, model_format, with_batch_dim=True): + """declare servable's model info, input_names and output_names can be str, tuple or list of str. + For input_names and output_names, serving only consider the number of names contained in them, + which should be consistent with the number of input and output used in register_method and + the number of input and output of the model. + The specific names content are ignored.""" + check_type.check_str(servable_file) + check_type.check_str(model_format) + check_type.check_bool(with_batch_dim) + + model_format = model_format.lower() + if model_format not in ("om", "mindir"): + raise RuntimeError("model format can only be OM or MindIR") + + meta = ServableMeta_() + meta.servable_name = get_servable_dir() + meta.servable_file = servable_file + meta.set_model_format(model_format) + meta.with_batch_dim = with_batch_dim + _servable_storage.declare_servable(meta) + print("------------Declare servable: servable_name", meta.servable_name, + ", servable file", servable_file, + ", model format", model_format, ", model config with batch dim", with_batch_dim) diff --git a/mindspore_serving/worker/task.py b/mindspore_serving/worker/task.py new file mode 100644 index 0000000..1ab929a --- /dev/null +++ b/mindspore_serving/worker/task.py @@ -0,0 +1,253 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""Python run preprocess and postprocess in python""" + +import threading +import time +import logging +from mindspore_serving._mindspore_serving import Worker_ +from mindspore_serving.worker.register.preprocess import preprocess_storage +from mindspore_serving.worker.register.postprocess import postprocess_storage + + +class ServingSystemException(Exception): + def __init__(self, msg): + super(ServingSystemException, self).__init__() + self.msg = msg + + def __str__(self): + return "Serving system error: " + self.msg + + +task_type_stop = "stop" +task_type_empty = "empty" +task_type_preprocess = "preprocess" +task_type_postprocess = "postprocess" + + +class PyTask: + def __init__(self, switch_batch, task_name): + super(PyTask, self).__init__() + self.task_name = task_name + self.switch_batch = switch_batch + self.temp_result = None + self.task = None + self.index = 0 + self.instances_size = 0 + self.stop_flag = False + self.result_batch = [] + + def push_failed_impl(self, count): + raise NotImplementedError + + def push_result_batch_impl(self, result_batch): + raise NotImplementedError + + def get_task_info(self, task_name): + raise NotImplementedError + + def push_failed(self, count): + self.push_result_batch() # push success first + self.push_failed_impl(count) + self.index += count + + def push_result_batch(self): + if len(self.result_batch) == 0: + return + + get_result_time_end = time.time() + last_index = self.index + + self.index += len(self.result_batch) + self.push_result_batch_impl(tuple(self.result_batch)) + self.result_batch = [] + + get_result_time = time.time() + print(f"-----------------{self.task_name} push result {last_index} ~ {self.index - 1} cost time", + (get_result_time - get_result_time_end) * 1000, "ms") + + def in_processing(self): + return self.temp_result is not None + + def run(self, task=None): + if not self.temp_result: + assert task is not None + self.instances_size = len(task.instance_list) + self.index = 0 + self.task = task + self.temp_result = self._handle_task() + if not self.temp_result: + return + while self.index < self.instances_size: + try: + get_result_time_end = time.time() + last_index = self.index + + for i in range(self.index, min(self.index + self.switch_batch, self.instances_size)): + output = next(self.temp_result) + output = self._handle_result(output) + self.result_batch.append(output) + + get_result_time = time.time() + print(f"-----------------{self.task_name} get result {last_index} ~ cost time", + (get_result_time - get_result_time_end) * 1000, "ms") + + self.push_result_batch() + break + except StopIteration: + self.push_result_batch() + self.push_failed(self.instances_size - self.index) + raise RuntimeError( + f"expecting {self.task_name} yield count equal to instance size {self.instances_size}") + except ServingSystemException as e: + raise e + except Exception as e: # catch exception and try next + print("{self.task_name} get result catch exception: ") + logging.exception(e) + self.push_failed(1) # push success results and a failed result + self.temp_result = self._handle_task_continue() + + if self.index >= self.instances_size: + self.temp_result = None + + def _handle_task(self): + self.task_info = self.get_task_info(self.task.name) + instance_list = self.task.instance_list + + self.context_list = self.task.context_list + # check input + for input in instance_list: + if not isinstance(input, tuple) or len(input) != self.task_info["inputs_count"]: + raise RuntimeError("length of given inputs " + str(len(input)) + + " not match {self.task_name} required " + str(self.task_info["inputs_count"])) + return self._handle_task_continue() + + def _handle_task_continue(self): + if self.index >= self.instances_size: + return None + instance_list = self.task.instance_list + try: + outputs = self.task_info["fun"](instance_list[self.index:]) + return outputs + except Exception as e: + print(f"{self.task_name} invoke catch exception: ") + logging.exception(e) + self.push_failed(len(instance_list) - self.index) + return None + + def _handle_result(self, output): + if not isinstance(output, (tuple, list)): + output = (output,) + if len(output) != self.task_info["outputs_count"]: + raise ServingSystemException(f"length of return output {len(output)} " + f"not match {self.task_name} signatures " + + str(self.task_info["outputs_count"])) + output = (item.asnumpy() if callable(getattr(item, "asnumpy", None)) else item for item in output) + return output + + +class PyPreprocess(PyTask): + def __init__(self, switch_batch): + super(PyPreprocess, self).__init__(switch_batch, "preprocess") + + def push_failed_impl(self, count): + Worker_.push_preprocess_failed(count) + + def push_result_batch_impl(self, result_batch): + Worker_.push_preprocess_result(result_batch) + + def get_task_info(self, task_name): + return preprocess_storage.get(task_name) + + +class PyPostprocess(PyTask): + def __init__(self, switch_batch): + super(PyPostprocess, self).__init__(switch_batch, "postprocess") + + def push_failed_impl(self, count): + Worker_.push_postprocess_failed(count) + + def push_result_batch_impl(self, result_batch): + Worker_.push_postprocess_result(result_batch) + + def get_task_info(self, task_name): + return postprocess_storage.get(task_name) + + +class PyTaskThread(threading.Thread): + def __init__(self, switch_batch): + super(PyTaskThread, self).__init__() + self.switch_batch = switch_batch + if self.switch_batch <= 0: + self.switch_batch = 8 + self.preprocess = PyPreprocess(self.switch_batch) + self.postprocess = PyPostprocess(self.switch_batch) + + def run(self): + print("start py task for preprocess and postprocess, switch_batch", self.switch_batch) + preprocess_turn = True + while True: + try: + if not self.preprocess.in_processing() and not self.postprocess.in_processing(): + task = Worker_.get_py_task() + if task.task_type == task_type_stop: + break + if task.task_type == task_type_preprocess: + self.preprocess.run(task) + preprocess_turn = False + elif task.task_type == task_type_postprocess: + self.postprocess.run(task) + preprocess_turn = True + + # in preprocess turn, when preprocess is still running, switch to running preprocess + # otherwise try get next preprocess task when postprocess is running + # when next preprocess is not available, switch to running postprocess + if preprocess_turn: + if self.preprocess.in_processing(): + self.preprocess.run() + elif self.postprocess.in_processing(): + task = Worker_.try_get_preprocess_py_task() + if task.task_type == task_type_stop: + break + if task.task_type != task_type_empty: + self.preprocess.run(task) + preprocess_turn = False + else: + if self.postprocess.in_processing(): + self.postprocess.run() + elif self.preprocess.in_processing(): + task = Worker_.try_get_postprocess_py_task() + if task.task_type == task_type_stop: + break + if task.task_type != task_type_empty: + self.postprocess.run(task) + preprocess_turn = True + + except Exception as e: + print("py task catch exception and exit: ") + logging.exception(e) + break + print("end py task for preprocess and postprocess") + Worker_.stop() + + +py_task_thread = None + + +def start_py_task(switch_batch): + global py_task_thread + if py_task_thread is None: + py_task_thread = PyTaskThread(switch_batch) + py_task_thread.start() diff --git a/scripts/format_source_code.sh b/scripts/format_source_code.sh new file mode 100755 index 0000000..829a0d3 --- /dev/null +++ b/scripts/format_source_code.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# Copyright 2019 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ + +set -e + +CLANG_FORMAT=$(which clang-format) || (echo "Please install 'clang-format' tool first"; exit 1) + +version=$("${CLANG_FORMAT}" --version | sed -n "s/.*\ \([0-9]*\)\.[0-9]*\.[0-9]*.*/\1/p") +if [[ "${version}" -lt "8" ]]; then + echo "clang-format's version must be at least 8.0.0" + exit 1 +fi + +CURRENT_PATH=$(pwd) +SCRIPTS_PATH=$(dirname "$0") + +echo "CURRENT_PATH=${CURRENT_PATH}" +echo "SCRIPTS_PATH=${SCRIPTS_PATH}" + +# print usage message +function usage() +{ + echo "Format the specified source files to conform the code style." + echo "Usage:" + echo "bash $0 [-a] [-c] [-l] [-h]" + echo "e.g. $0 -c" + echo "" + echo "Options:" + echo " -a format of all files" + echo " -c format of the files changed compared to last commit, default case" + echo " -l format of the files changed in last commit" + echo " -h Print usage" +} + +# check and set options +function checkopts() +{ + # init variable + mode="changed" # default format changed files + + # Process the options + while getopts 'aclh' opt + do + case "${opt}" in + a) + mode="all" + ;; + c) + mode="changed" + ;; + l) + mode="lastcommit" + ;; + h) + usage + exit 0 + ;; + *) + echo "Unknown option ${opt}!" + usage + exit 1 + esac + done +} + +# init variable +# check options +checkopts "$@" + +# switch to project root path, which contains clang-format config file '.clang-format' +cd "${SCRIPTS_PATH}/../.." || exit 1 + +FMT_FILE_LIST='__format_files_list__' + +if [[ "X${mode}" == "Xall" ]]; then + find ./ -type f -name "*" | grep "\.h$\|\.cc$" > "${FMT_FILE_LIST}" || true +elif [[ "X${mode}" == "Xchanged" ]]; then + git diff --name-only | grep "\.h$\|\.cc$" > "${FMT_FILE_LIST}" || true +else # "X${mode}" == "Xlastcommit" + git diff --name-only HEAD~ HEAD | grep "\.h$\|\.cc$" > "${FMT_FILE_LIST}" || true +fi + +while read line; do + if [ -f "${line}" ]; then + ${CLANG_FORMAT} -i "${line}" + fi +done < "${FMT_FILE_LIST}" + +rm "${FMT_FILE_LIST}" +cd "${CURRENT_PATH}" || exit 1 + +echo "Specified cpp source files have been format successfully." diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ceca1fd --- /dev/null +++ b/setup.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""setup package.""" +import os +import stat +import platform + +from setuptools import setup, find_packages +from setuptools.command.egg_info import egg_info +from setuptools.command.build_py import build_py + +version = '0.1.0' + +backend_policy = os.getenv('BACKEND_POLICY') +commit_id = os.getenv('COMMIT_ID').replace("\n", "") +package_name = os.getenv('MS_PACKAGE_NAME').replace("\n", "") + +pwd = os.path.dirname(os.path.realpath(__file__)) +pkg_dir = os.path.join(pwd, 'build/package') + + +def _read_file(filename): + with open(os.path.join(pwd, filename), encoding='UTF-8') as f: + return f.read() + + +readme = _read_file('README.md') +release = _read_file('RELEASE.md') + + +def _write_version(file): + file.write("__version__ = '{}'\n".format(version)) + + +def _write_config(file): + file.write("__backend__ = '{}'\n".format(backend_policy)) + + +def _write_commit_file(file): + file.write("__commit_id__ = '{}'\n".format(commit_id)) + + +def _write_package_name(file): + file.write("__package_name__ = '{}'\n".format(package_name)) + + +def build_dependencies(): + """generate python file""" + version_file = os.path.join(pkg_dir, 'mindspore_serving', 'version.py') + with open(version_file, 'w') as f: + _write_version(f) + + version_file = os.path.join(pwd, 'mindspore_serving', 'version.py') + with open(version_file, 'w') as f: + _write_version(f) + + config_file = os.path.join(pkg_dir, 'mindspore_serving', 'default_config.py') + with open(config_file, 'w') as f: + _write_config(f) + + config_file = os.path.join(pwd, 'mindspore_serving', 'default_config.py') + with open(config_file, 'w') as f: + _write_config(f) + + package_info = os.path.join(pkg_dir, 'mindspore_serving', 'default_config.py') + with open(package_info, 'a') as f: + _write_package_name(f) + + package_info = os.path.join(pwd, 'mindspore_serving', 'default_config.py') + with open(package_info, 'a') as f: + _write_package_name(f) + + commit_file = os.path.join(pkg_dir, 'mindspore_serving', '.commit_id') + with open(commit_file, 'w') as f: + _write_commit_file(f) + + commit_file = os.path.join(pwd, 'mindspore_serving', '.commit_id') + with open(commit_file, 'w') as f: + _write_commit_file(f) + + +build_dependencies() + +required_package = [ + 'numpy >= 1.17.0', + 'protobuf >= 3.8.0', + 'scipy == 1.3.3', + 'easydict >= 1.9', + 'sympy >= 1.4', + 'decorator >= 4.4.0', + 'packaging >= 20.0' +] + +package_data = { + '': [ + '*.so*', + '*.pyd', + '*.dll', + 'lib/*.so*', + 'lib/*.a', + '.commit_id', + '_mindspore_serving', + 'proto/*.py' + ] +} + + +def update_permissions(path): + """ + Update permissions. + + Args: + path (str): Target directory path. + """ + if platform.system() == "Windows": + return + + for dirpath, dirnames, filenames in os.walk(path): + for dirname in dirnames: + dir_fullpath = os.path.join(dirpath, dirname) + os.chmod(dir_fullpath, stat.S_IREAD | stat.S_IWRITE | + stat.S_IEXEC | stat.S_IRGRP | stat.S_IXGRP) + for filename in filenames: + file_fullpath = os.path.join(dirpath, filename) + os.chmod(file_fullpath, stat.S_IREAD) + + +def bin_files(): + """ + Gets the binary files to be installed. + """ + data_files = [] + binary_files = [] + + cache_server_bin = os.path.join('mindspore_serving', 'bin', 'cache_server') + if not os.path.exists(cache_server_bin): + return data_files + binary_files.append(cache_server_bin) + cache_admin_bin = os.path.join('mindspore_serving', 'bin', 'cache_admin') + if not os.path.exists(cache_admin_bin): + return data_files + binary_files.append(cache_admin_bin) + data_files.append(('bin', binary_files)) + return data_files + + +class EggInfo(egg_info): + """Egg info.""" + + def run(self): + super().run() + egg_info_dir = os.path.join(pkg_dir, 'mindspore_serving.egg-info') + update_permissions(egg_info_dir) + + +class BuildPy(build_py): + """BuildPy.""" + + def run(self): + super().run() + mindspore_dir = os.path.join(pkg_dir, 'build', 'lib', 'mindspore_serving') + update_permissions(mindspore_dir) + mindspore_dir = os.path.join(pkg_dir, 'build', 'lib', 'akg') + update_permissions(mindspore_dir) + + +setup( + name=package_name, + version=version, + author='The MindSpore Authors', + author_email='contact@mindspore.cn', + url='https://www.mindspore.cn', + download_url='https://gitee.com/mindspore/serving/tags', + project_urls={ + 'Sources': 'https://gitee.com/mindspore/serving', + 'Issue Tracker': 'https://gitee.com/mindspore/serving/issues', + }, + description='MindSpore is a new open source deep learning training/inference ' + 'framework that could be used for mobile, edge and cloud scenarios.', + # long_description="\n\n".join([readme, release]), + long_description="\n\n".join([readme]), + long_description_content_type="text/markdown", + data_files=bin_files(), + packages=find_packages(), + package_data=package_data, + include_package_data=True, + cmdclass={ + 'egg_info': EggInfo, + 'build_py': BuildPy, + }, + python_requires='>=3.7', + install_requires=required_package, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Science/Research', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: C++', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'Topic :: Software Development', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + license='Apache 2.0', + keywords='mindspore machine learning', +) diff --git a/tests/ut/python/servables/add/servable_config.py b/tests/ut/python/servables/add/servable_config.py new file mode 100644 index 0000000..f43d687 --- /dev/null +++ b/tests/ut/python/servables/add/servable_config.py @@ -0,0 +1,51 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""add model servable config""" + +from mindspore_serving.worker import register +import numpy as np + + +# define preprocess pipeline, the function arg is multi instances, every instance is tuple of inputs +# this example has one input and one output +def add_trans_datatype(instances): + """preprocess python implement""" + for instance in instances: + x1 = instance[0] + x2 = instance[1] + yield x1.astype(np.float32), x2.astype(np.float32) + + +# when with_batch_dim set to False, only support 2x2 add +# when with_batch_dim set to True(default), support Nx2 add, while N is view as batch +# float32 inputs/outputs +register.declare_servable(servable_file="tensor_add.mindir", model_format="MindIR", with_batch_dim=False) + + +# register add_common method in add +@register.register_method(output_names=["y"]) +def add_common(x1, x2): # only support float32 inputs + """method add_common data flow definition, only call model servable""" + y = register.call_servable(x1, x2) + return y + + +# register add_cast method in add +@register.register_method(output_names=["y"]) +def add_cast(x1, x2): + """method add_cast data flow definition, only call preprocess and model servable""" + x1, x2 = register.call_preprocess(add_trans_datatype, x1, x2) # cast input to float32 + y = register.call_servable(x1, x2) + return y diff --git a/tests/ut/python/servables/add_error_model_type/servable_config.py b/tests/ut/python/servables/add_error_model_type/servable_config.py new file mode 100644 index 0000000..d830d5e --- /dev/null +++ b/tests/ut/python/servables/add_error_model_type/servable_config.py @@ -0,0 +1,51 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""add model servable config""" + +from mindspore_serving.worker import register +import numpy as np + + +# define preprocess pipeline, the function arg is multi instances, every instance is tuple of inputs +# this example has one input and one output +def add_trans_datatype(instances): + """preprocess python implement""" + for instance in instances: + x1 = instance[0] + x2 = instance[1] + yield x1.astype(np.float32), x2.astype(np.float32) + + +# when with_batch_dim set to False, only support 2x2 add +# when with_batch_dim set to True(default), support Nx2 add, while N is view as batch +# float32 inputs/outputs +register.declare_servable(servable_file="tensor_add.mindir", model_format="OM", with_batch_dim=False) + + +# register add_common method in add +@register.register_method(output_names=["y"]) +def add_common(x1, x2): # only support float32 inputs + """method add_common data flow definition, only call model servable""" + y = register.call_servable(x1, x2) + return y + + +# register add_cast method in add +@register.register_method(output_names=["y"]) +def add_cast(x1, x2): + """method add_cast data flow definition, only call preprocess and model servable""" + x1, x2 = register.call_preprocess(add_trans_datatype, x1, x2) # cast input to float32 + y = register.call_servable(x1, x2) + return y diff --git a/tests/ut/python/servables/export_model/add_model.py b/tests/ut/python/servables/export_model/add_model.py new file mode 100644 index 0000000..371aa75 --- /dev/null +++ b/tests/ut/python/servables/export_model/add_model.py @@ -0,0 +1,61 @@ +# Copyright 2020 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================ +"""add model generator""" + +import numpy as np + +import mindspore.context as context +import mindspore.nn as nn +from mindspore.ops import operations as P +from mindspore import Tensor +from mindspore.train.serialization import export +import os +from shutil import copyfile + +context.set_context(mode=context.GRAPH_MODE, device_target="Ascend") + + +class Net(nn.Cell): + def __init__(self): + super(Net, self).__init__() + self.add = P.TensorAdd() + + def construct(self, x_, y_): + return self.add(x_, y_) + + +def export_net(): + x = np.ones([2, 2]).astype(np.float32) + y = np.ones([2, 2]).astype(np.float32) + add = Net() + output = add(Tensor(x), Tensor(y)) + export(add, Tensor(x), Tensor(y), file_name='tensor_add.mindir', file_format='MINDIR') + try: + os.mkdir("../1") + except: + pass + try: + copyfile('tensor_add.mindir', '../1/tensor_add.mindir') + print("copy tensor_add.mindir to ../1/ success") + except: + print("copy tensor_add.mindir to ../1/ failed") + + print(x) + print(y) + print(output.asnumpy()) + + +if __name__ == "__main__": + export_net() diff --git a/tests/ut/python/test_worker_start_servable.py b/tests/ut/python/test_worker_start_servable.py new file mode 100644 index 0000000..921a588 --- /dev/null +++ b/tests/ut/python/test_worker_start_servable.py @@ -0,0 +1,43 @@ +from mindspore_serving import worker +import os + + +def export_add_net(): + from .servables.export_model import add_model + add_model.export_net() + + +export_add_net() + + +servable_dir = os.path.abspath(".") +"/servables" + + +def test_model_type_mindir_success(): + worker.start_servable(servable_directory=servable_dir, + servable_name="add", + version_number=0, + device_id=0, + master_ip="127.0.0.1", + master_port=6500, + host_ip="127.0.0.1", + host_port=7000) + worker.stop() + + +def test_model_type_mindir_error_model_type(): + try: + worker.start_servable(servable_directory=servable_dir, + servable_name="add_error_model_type", + version_number=0, + device_id=0, + master_ip="127.0.0.1", + master_port=6500, + host_ip="127.0.0.1", + host_port=7000) + worker.stop() + except: + return + assert (False) + + diff --git a/third_party/mindspore b/third_party/mindspore new file mode 160000 index 0000000..0241aa8 --- /dev/null +++ b/third_party/mindspore @@ -0,0 +1 @@ +Subproject commit 0241aa8797f624f1961a9d35c32781916faf8c55 diff --git a/third_party/securec/CMakeLists.txt b/third_party/securec/CMakeLists.txt new file mode 100644 index 0000000..2a10e32 --- /dev/null +++ b/third_party/securec/CMakeLists.txt @@ -0,0 +1,14 @@ +SET(CMAKE_BUILD_TYPE "Debug") +if (CMAKE_SYSTEM_NAME MATCHES "Windows") + SET(CMAKE_C_FLAGS_DEBUG "$ENV{CFLAGS} -fPIC -O0 -Wall -Wno-deprecated-declarations -g2 -ggdb -fno-inline-functions -fno-omit-frame-pointer -fstack-protector-all") +else() + SET(CMAKE_C_FLAGS_DEBUG "$ENV{CFLAGS} -fPIC -O0 -Wall -Wno-deprecated-declarations -g2 -ggdb -fno-inline-functions -fno-omit-frame-pointer -fstack-protector-all -D_LIBCPP_INLINE_VISIBILITY='' -D'_LIBCPP_EXTERN_TEMPLATE(...)='") +endif() +SET(CMAKE_C_FLAGS_RELEASE "$ENV{CFLAGS} -fPIC -O3 -Wall -Wno-deprecated-declarations -fstack-protector-all") +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +#add flags +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include -Werror") + +include_directories(./include) +add_subdirectory(src) diff --git a/third_party/securec/include/securec.h b/third_party/securec/include/securec.h new file mode 100644 index 0000000..b627a3c --- /dev/null +++ b/third_party/securec/include/securec.h @@ -0,0 +1,634 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __SECUREC_H__5D13A042_DC3F_4ED9_A8D1_882811274C27 +#define __SECUREC_H__5D13A042_DC3F_4ED9_A8D1_882811274C27 + +#include "securectype.h" +#include + +#ifndef SECUREC_HAVE_ERRNO_H +#if SECUREC_IN_KERNEL +#define SECUREC_HAVE_ERRNO_H 0 +#else +#define SECUREC_HAVE_ERRNO_H 1 +#endif +#endif + +/* EINVAL ERANGE may defined in errno.h */ +#if SECUREC_HAVE_ERRNO_H +#include +#endif + +/* define error code */ +#if defined(SECUREC_NEED_ERRNO_TYPE) || !defined(__STDC_WANT_LIB_EXT1__) || \ + (defined(__STDC_WANT_LIB_EXT1__) && (__STDC_WANT_LIB_EXT1__ == 0)) +#ifndef SECUREC_DEFINED_ERRNO_TYPE +#define SECUREC_DEFINED_ERRNO_TYPE +/* just check whether macrodefinition exists. */ +#ifndef errno_t +typedef int errno_t; +#endif +#endif +#endif + +/* success */ +#ifndef EOK +#define EOK 0 +#endif + +#ifndef EINVAL +/* The src buffer is not correct and destination buffer cant not be reset */ +#define EINVAL 22 +#endif + +#ifndef EINVAL_AND_RESET +/* Once the error is detected, the dest buffer must be reseted! */ +#define EINVAL_AND_RESET (22 | 128) +#endif + +#ifndef ERANGE +/* The destination buffer is not long enough and destination buffer can not be reset */ +#define ERANGE 34 +#endif + +#ifndef ERANGE_AND_RESET +/* Once the error is detected, the dest buffer must be reseted! */ +#define ERANGE_AND_RESET (34 | 128) +#endif + +#ifndef EOVERLAP_AND_RESET +/* Once the buffer overlap is detected, the dest buffer must be reseted! */ +#define EOVERLAP_AND_RESET (54 | 128) +#endif + +/* if you need export the function of this library in Win32 dll, use __declspec(dllexport) */ +#ifndef SECUREC_API +#if defined(SECUREC_DLL_EXPORT) +#define SECUREC_API __declspec(dllexport) +#elif defined(SECUREC_DLL_IMPORT) +#define SECUREC_API __declspec(dllimport) +#else +/* Standardized function declaration . If a security function is declared in the your code, + * it may cause a compilation alarm,Please delete the security function you declared + * Adding extern under windows will cause the system to have inline functions to expand, + * so do not add the extern in default + */ +#if defined(_MSC_VER) +#define SECUREC_API +#else +#define SECUREC_API extern +#endif +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + /* + * Description: The GetHwSecureCVersion function get SecureC Version string and version number. + * Parameter: verNumber - to store version number + * Return: version string + */ + SECUREC_API const char *GetHwSecureCVersion(unsigned short *verNumber); + +#if SECUREC_ENABLE_MEMSET + /* + * Description: The memset_s function copies the value of c (converted to an unsigned char) into each of + * the first count characters of the object pointed to by dest. + * Parameter: dest - destination address + * Parameter: destMax -The maximum length of destination buffer + * Parameter: c - the value to be copied + * Parameter: count -copies fisrt count characters of dest + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t memset_s(void *dest, size_t destMax, int c, size_t count); +#endif + +#ifndef SECUREC_ONLY_DECLARE_MEMSET +#define SECUREC_ONLY_DECLARE_MEMSET 0 +#endif + +#if SECUREC_ONLY_DECLARE_MEMSET == 0 + +#if SECUREC_ENABLE_MEMMOVE + /* + * Description: The memmove_s function copies n characters from the object pointed to by src + * into the object pointed to by dest. + * Parameter: dest - destination address + * Parameter: destMax -The maximum length of destination buffer + * Parameter: src -source address + * Parameter: count -copies count wide characters from the src + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t memmove_s(void *dest, size_t destMax, const void *src, size_t count); +#endif + +#if SECUREC_ENABLE_MEMCPY + /* + * Description: The memcpy_s function copies n characters from the object pointed to + * by src into the object pointed to by dest. + * Parameter: dest - destination address + * Parameter: destMax -The maximum length of destination buffer + * Parameter: src -source address + * Parameter: count -copies count characters from the src + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t memcpy_s(void *dest, size_t destMax, const void *src, size_t count); +#endif + +#if SECUREC_ENABLE_STRCPY + /* + * Description: The strcpy_s function copies the string pointed to by strSrc (including + * the terminating null character) into the array pointed to by strDest + * Parameter: strDest - destination address + * Parameter: destMax -The maximum length of destination buffer(including the terminating null character) + * Parameter: strSrc -source address + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t strcpy_s(char *strDest, size_t destMax, const char *strSrc); +#endif + +#if SECUREC_ENABLE_STRNCPY + /* + * Description: The strncpy_s function copies not more than n successive characters (not including + * the terminating null character) + * from the array pointed to by strSrc to the array pointed to by strDest + * Parameter: strDest - destination address + * Parameter: destMax -The maximum length of destination buffer(including the terminating null character) + * Parameter: strSrc -source address + * Parameter: count -copies count characters from the src + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t strncpy_s(char *strDest, size_t destMax, const char *strSrc, size_t count); +#endif + +#if SECUREC_ENABLE_STRCAT + /* + * Description: The strcat_s function appends a copy of the string pointed to by strSrc (including + * the terminating null character) + * to the end of the string pointed to by strDest + * Parameter: strDest - destination address + * Parameter: destMax -The maximum length of destination buffer(including the terminating null wide character) + * Parameter: strSrc -source address + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t strcat_s(char *strDest, size_t destMax, const char *strSrc); +#endif + +#if SECUREC_ENABLE_STRNCAT + /* + * Description: The strncat_s function appends not more than n successive characters (not including + * the terminating null character) + * from the array pointed to by strSrc to the end of the string pointed to by strDest. + * Parameter: strDest - destination address + * Parameter: destMax -The maximum length of destination buffer(including the terminating null character) + * Parameter: strSrc -source address + * Parameter: count -copies count characters from the src + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t strncat_s(char *strDest, size_t destMax, const char *strSrc, size_t count); +#endif + +#if SECUREC_ENABLE_VSPRINTF + /* + * Description: The vsprintf_s function is equivalent to the vsprintf function except for the Parameter: destMax + * and the explicit runtime-constraints violation + * Parameter: strDest - produce output according to a format ,write to the character string strDest + * Parameter: destMax - The maximum length of destination buffer(including the terminating null wide characte) + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of characters printed(not including the terminating null byte ('\0')), + * If an error occurred Return: -1. + */ + SECUREC_API int vsprintf_s(char *strDest, size_t destMax, const char *format, + va_list argList) SECUREC_ATTRIBUTE(3, 0); +#endif + +#if SECUREC_ENABLE_SPRINTF + /* + * Description: The sprintf_s function is equivalent to the sprintf function except for the Parameter: destMax + * and the explicit runtime-constraints violation + * Parameter: strDest - produce output according to a format ,write to the character string strDest + * Parameter: destMax - The maximum length of destination buffer(including the terminating null byte ('\0')) + * Parameter: format - fromat string + * Return: the number of characters printed(not including the terminating null byte ('\0')), + * If an error occurred Return: -1. + */ + SECUREC_API int sprintf_s(char *strDest, size_t destMax, const char *format, ...) SECUREC_ATTRIBUTE(3, 4); +#endif + +#if SECUREC_ENABLE_VSNPRINTF + /* + * Description: The vsnprintf_s function is equivalent to the vsnprintf function except for the Parameter: + * destMax/count and the explicit runtime-constraints violation + * Parameter: strDest - produce output according to a format ,write to the character string strDest + * Parameter: destMax - The maximum length of destination buffer(including the terminating null byte ('\0')) + * Parameter: count - do not write more than count bytes to strDest(not including the terminating null byte ('\0')) + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of characters printed(not including the terminating null byte ('\0')), + * If an error occurred Return: -1.Pay special attention to returning -1 when truncation occurs + */ + SECUREC_API int vsnprintf_s(char *strDest, size_t destMax, size_t count, const char *format, + va_list argList) SECUREC_ATTRIBUTE(4, 0); +#endif + +#if SECUREC_ENABLE_SNPRINTF + /* + * Description: The snprintf_s function is equivalent to the snprintf function except for the Parameter: + * destMax/count and the explicit runtime-constraints violation + * Parameter: strDest - produce output according to a format ,write to the character string strDest + * Parameter: destMax - The maximum length of destination buffer(including the terminating null byte ('\0')) + * Parameter: count - do not write more than count bytes to strDest(not including the terminating null byte ('\0')) + * Parameter: format - fromat string + * Return: the number of characters printed(not including the terminating null byte ('\0')), + * If an error occurred Return: -1.Pay special attention to returning -1 when truncation occurs + */ + SECUREC_API int snprintf_s(char *strDest, size_t destMax, size_t count, const char *format, + ...) SECUREC_ATTRIBUTE(4, 5); +#endif + +#if SECUREC_SNPRINTF_TRUNCATED + /* + * Description: The vsnprintf_truncated_s function is equivalent to the vsnprintf_s function except + * no count Parameter: and Return: value + * Parameter: strDest - produce output according to a format ,write to the character string strDest + * Parameter: destMax - The maximum length of destination buffer(including the terminating null byte ('\0')) + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of characters printed(not including the terminating null byte ('\0')), + * If an error occurred Return: -1.Pay special attention to returning destMax - 1 when truncation occurs + */ + SECUREC_API int vsnprintf_truncated_s(char *strDest, size_t destMax, const char *format, + va_list argList) SECUREC_ATTRIBUTE(3, 0); + + /* + * Description: The snprintf_truncated_s function is equivalent to the snprintf_2 function except + * no count Parameter: and Return: value + * Parameter: strDest - produce output according to a format ,write to the character string strDest + * Parameter: destMax - The maximum length of destination buffer(including the terminating null byte ('\0')) + * Parameter: format - fromat string + * Return: the number of characters printed(not including the terminating null byte ('\0')), + * If an error occurred Return: -1.Pay special attention to returning destMax - 1 when truncation occurs + */ + SECUREC_API int snprintf_truncated_s(char *strDest, size_t destMax, + const char *format, ...) SECUREC_ATTRIBUTE(3, 4); +#endif + +#if SECUREC_ENABLE_SCANF + /* + * Description: The scanf_s function is equivalent to fscanf_s with the argument stdin + * interposed before the arguments to scanf_s + * Parameter: format - fromat string + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int scanf_s(const char *format, ...); +#endif + +#if SECUREC_ENABLE_VSCANF + /* + * Description: The vscanf_s function is equivalent to scanf_s, with the variable argument list replaced by argList + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int vscanf_s(const char *format, va_list argList); +#endif + +#if SECUREC_ENABLE_SSCANF + /* + * Description: The sscanf_s function is equivalent to fscanf_s, except that input is obtained from a + * string (specified by the argument buffer) rather than from a stream + * Parameter: buffer - read character from buffer + * Parameter: format - fromat string + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int sscanf_s(const char *buffer, const char *format, ...); +#endif + +#if SECUREC_ENABLE_VSSCANF + /* + * Description: The vsscanf_s function is equivalent to sscanf_s, with the variable argument list + * replaced by argList + * Parameter: buffer - read character from buffer + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int vsscanf_s(const char *buffer, const char *format, va_list argList); +#endif + +#if SECUREC_ENABLE_FSCANF + /* + * Description: The fscanf_s function is equivalent to fscanf except that the c, s, and [ conversion specifiers + * apply to a pair of arguments (unless assignment suppression is indicated by a*) + * Parameter: stream - stdio file stream + * Parameter: format - fromat string + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int fscanf_s(FILE *stream, const char *format, ...); +#endif + +#if SECUREC_ENABLE_VFSCANF + /* + * Description: The vfscanf_s function is equivalent to fscanf_s, with the variable argument list + * replaced by argList + * Parameter: stream - stdio file stream + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int vfscanf_s(FILE *stream, const char *format, va_list argList); +#endif + +#if SECUREC_ENABLE_STRTOK + /* + * Description: The strtok_s function parses a string into a sequence of strToken, + * replace all characters in strToken string that match to strDelimit set with 0. + * On the first call to strtok_s the string to be parsed should be specified in strToken. + * In each subsequent call that should parse the same string, strToken should be NULL + * Parameter: strToken - the string to be delimited + * Parameter: strDelimit -specifies a set of characters that delimit the tokens in the parsed string + * Parameter: context -is a pointer to a char * variable that is used internally by strtok_s function + * Return: On the first call returns the address of the first non \0 character, otherwise NULL is returned. + * In subsequent calls, the strtoken is set to NULL, and the context set is the same as the previous call, + * return NULL if the *context string length is equal 0, otherwise return *context. + */ + SECUREC_API char *strtok_s(char *strToken, const char *strDelimit, char **context); +#endif + +#if SECUREC_ENABLE_GETS && SECUREC_IN_KERNEL == 0 + /* + * Description: The gets_s function reads at most one less than the number of characters specified + * by destMax from the stream pointed to by stdin, into the array pointed to by buffer + * Parameter: buffer - destination address + * Parameter: destMax -The maximum length of destination buffer(including the terminating null character) + * Return: buffer if there was no runtime-constraint violation,If an error occurred Return: NULL. + */ + SECUREC_API char *gets_s(char *buffer, size_t destMax); +#endif + + +#if SECUREC_ENABLE_WCHAR_FUNC +#if SECUREC_ENABLE_MEMCPY + /* + * Description: The wmemcpy_s function copies n successive wide characters from the object pointed to + * by src into the object pointed to by dest. + * Parameter: dest - destination address + * Parameter: destMax -The maximum length of destination buffer + * Parameter: src -source address + * Parameter: count -copies count wide characters from the src + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t wmemcpy_s(wchar_t *dest, size_t destMax, const wchar_t *src, size_t count); +#endif + +#if SECUREC_ENABLE_MEMMOVE + /* + * Description: The wmemmove_s function copies n successive wide characters from the object + * pointed to by src into the object pointed to by dest. + * Parameter: dest - destination address + * Parameter: destMax -The maximum length of destination buffer + * Parameter: src -source address + * Parameter: count -copies count wide characters from the src + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t wmemmove_s(wchar_t *dest, size_t destMax, const wchar_t *src, size_t count); +#endif + +#if SECUREC_ENABLE_STRCPY + /* + * Description: The wcscpy_s function copies the wide string pointed to by strSrc (including theterminating + * null wide character) into the array pointed to by strDest + * Parameter: strDest - destination address + * Parameter: destMax -The maximum length of destination buffer + * Parameter: strSrc -source address + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t wcscpy_s(wchar_t *strDest, size_t destMax, const wchar_t *strSrc); +#endif + +#if SECUREC_ENABLE_STRNCPY + /* + * Description: The wcsncpy_s function copies not more than n successive wide characters (not including the + * terminating null wide character) from the array pointed to by strSrc to the array pointed to by strDest + * Parameter: strDest - destination address + * Parameter: destMax -The maximum length of destination buffer(including the terminating wide character) + * Parameter: strSrc -source address + * Parameter: count -copies count wide characters from the src + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t wcsncpy_s(wchar_t *strDest, size_t destMax, const wchar_t *strSrc, size_t count); +#endif + +#if SECUREC_ENABLE_STRCAT + /* + * Description: The wcscat_s function appends a copy of the wide string pointed to by strSrc (including the + * terminating null wide character) to the end of the wide string pointed to by strDest + * Parameter: strDest - destination address + * Parameter: destMax -The maximum length of destination buffer(including the terminating wide character) + * Parameter: strSrc -source address + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t wcscat_s(wchar_t *strDest, size_t destMax, const wchar_t *strSrc); +#endif + +#if SECUREC_ENABLE_STRNCAT + /* + * Description: The wcsncat_s function appends not more than n successive wide characters (not including the + * terminating null wide character) from the array pointed to by strSrc to the end of the wide string pointed to + * by strDest. + * Parameter: strDest - destination address + * Parameter: destMax -The maximum length of destination buffer(including the terminating wide character) + * Parameter: strSrc -source address + * Parameter: count -copies count wide characters from the src + * Return: EOK if there was no runtime-constraint violation + */ + SECUREC_API errno_t wcsncat_s(wchar_t *strDest, size_t destMax, const wchar_t *strSrc, size_t count); +#endif + +#if SECUREC_ENABLE_STRTOK + /* + * Description: The wcstok_s function is the wide-character equivalent of the strtok_s function + * Parameter: strToken - the string to be delimited + * Parameter: strDelimit -specifies a set of characters that delimit the tokens in the parsed string + * Parameter: context -is a pointer to a char * variable that is used internally by strtok_s function + * Return: a pointer to the first character of a token, or a null pointer if there is no token + * or there is a runtime-constraint violation. + */ + SECUREC_API wchar_t *wcstok_s(wchar_t *strToken, const wchar_t *strDelimit, wchar_t **context); +#endif + +#if SECUREC_ENABLE_VSPRINTF + /* + * Description: The vswprintf_s function is the wide-character equivalent of the vsprintf_s function + * Parameter: strDest - produce output according to a format ,write to the character string strDest + * Parameter: destMax - The maximum length of destination buffer(including the terminating null ) + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of characters printed(not including the terminating null wide characte), + * If an error occurred Return: -1. + */ + SECUREC_API int vswprintf_s(wchar_t *strDest, size_t destMax, const wchar_t *format, va_list argList); +#endif + +#if SECUREC_ENABLE_SPRINTF + + /* + * Description: The swprintf_s function is the wide-character equivalent of the sprintf_s function + * Parameter: strDest - produce output according to a format ,write to the character string strDest + * Parameter: destMax - The maximum length of destination buffer(including the terminating null ) + * Parameter: format - fromat string + * Return: the number of characters printed(not including the terminating null wide characte), + * If an error occurred Return: -1. + */ + SECUREC_API int swprintf_s(wchar_t *strDest, size_t destMax, const wchar_t *format, ...); +#endif + +#if SECUREC_ENABLE_FSCANF + /* + * Description: The fwscanf_s function is the wide-character equivalent of the fscanf_s function + * Parameter: stream - stdio file stream + * Parameter: format - fromat string + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int fwscanf_s(FILE *stream, const wchar_t *format, ...); +#endif + +#if SECUREC_ENABLE_VFSCANF + /* + * Description: The vfwscanf_s function is the wide-character equivalent of the vfscanf_s function + * Parameter: stream - stdio file stream + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int vfwscanf_s(FILE *stream, const wchar_t *format, va_list argList); +#endif + +#if SECUREC_ENABLE_SCANF + /* + * Description: The wscanf_s function is the wide-character equivalent of the scanf_s function + * Parameter: format - fromat string + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int wscanf_s(const wchar_t *format, ...); +#endif + +#if SECUREC_ENABLE_VSCANF + /* + * Description: The vwscanf_s function is the wide-character equivalent of the vscanf_s function + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int vwscanf_s(const wchar_t *format, va_list argList); +#endif + +#if SECUREC_ENABLE_SSCANF + /* + * Description: The swscanf_s function is the wide-character equivalent of the sscanf_s function + * Parameter: buffer - read character from buffer + * Parameter: format - fromat string + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int swscanf_s(const wchar_t *buffer, const wchar_t *format, ...); +#endif + +#if SECUREC_ENABLE_VSSCANF + /* + * Description: The vswscanf_s function is the wide-character equivalent of the vsscanf_s function + * Parameter: buffer - read character from buffer + * Parameter: format - fromat string + * Parameter: argList - instead of a variable number of arguments + * Return: the number of input items assigned, If an error occurred Return: -1. + */ + SECUREC_API int vswscanf_s(const wchar_t *buffer, const wchar_t *format, va_list argList); +#endif +#endif /* SECUREC_ENABLE_WCHAR_FUNC */ +#endif + + /* those functions are used by macro ,must declare hare , also for without function declaration warning */ + extern errno_t strncpy_error(char *strDest, size_t destMax, const char *strSrc, size_t count); + extern errno_t strcpy_error(char *strDest, size_t destMax, const char *strSrc); + +#if SECUREC_WITH_PERFORMANCE_ADDONS + /* those functions are used by macro */ + extern errno_t memset_sOptAsm(void *dest, size_t destMax, int c, size_t count); + extern errno_t memset_sOptTc(void *dest, size_t destMax, int c, size_t count); + extern errno_t memcpy_sOptAsm(void *dest, size_t destMax, const void *src, size_t count); + extern errno_t memcpy_sOptTc(void *dest, size_t destMax, const void *src, size_t count); + +/* strcpy_sp is a macro, NOT a function in performance optimization mode. */ +#define strcpy_sp(dest, destMax, src) ((__builtin_constant_p((destMax)) && \ + __builtin_constant_p((src))) ? \ + SECUREC_STRCPY_SM((dest), (destMax), (src)) : \ + strcpy_s((dest), (destMax), (src))) + +/* strncpy_sp is a macro, NOT a function in performance optimization mode. */ +#define strncpy_sp(dest, destMax, src, count) ((__builtin_constant_p((count)) && \ + __builtin_constant_p((destMax)) && \ + __builtin_constant_p((src))) ? \ + SECUREC_STRNCPY_SM((dest), (destMax), (src), (count)) : \ + strncpy_s((dest), (destMax), (src), (count))) + +/* strcat_sp is a macro, NOT a function in performance optimization mode. */ +#define strcat_sp(dest, destMax, src) ((__builtin_constant_p((destMax)) && \ + __builtin_constant_p((src))) ? \ + SECUREC_STRCAT_SM((dest), (destMax), (src)) : \ + strcat_s((dest), (destMax), (src))) + +/* strncat_sp is a macro, NOT a function in performance optimization mode. */ +#define strncat_sp(dest, destMax, src, count) ((__builtin_constant_p((count)) && \ + __builtin_constant_p((destMax)) && \ + __builtin_constant_p((src))) ? \ + SECUREC_STRNCAT_SM((dest), (destMax), (src), (count)) : \ + strncat_s((dest), (destMax), (src), (count))) + +/* memcpy_sp is a macro, NOT a function in performance optimization mode. */ +#define memcpy_sp(dest, destMax, src, count) (__builtin_constant_p((count)) ? \ + (SECUREC_MEMCPY_SM((dest), (destMax), (src), (count))) : \ + (__builtin_constant_p((destMax)) ? \ + (((size_t)(destMax) > 0 && \ + (((unsigned long long)(destMax) & \ + (unsigned long long)(-2)) < SECUREC_MEM_MAX_LEN)) ? \ + memcpy_sOptTc((dest), (destMax), (src), (count)) : ERANGE) : \ + memcpy_sOptAsm((dest), (destMax), (src), (count)))) + +/* memset_sp is a macro, NOT a function in performance optimization mode. */ +#define memset_sp(dest, destMax, c, count) (__builtin_constant_p((count)) ? \ + (SECUREC_MEMSET_SM((dest), (destMax), (c), (count))) : \ + (__builtin_constant_p((destMax)) ? \ + (((size_t)(destMax) > 0 && \ + (((unsigned long long)(destMax) & \ + (unsigned long long)(-2)) < SECUREC_MEM_MAX_LEN)) ? \ + memset_sOptTc((dest), (destMax), (c), (count)) : ERANGE) : \ + memset_sOptAsm((dest), (destMax), (c), (count)))) +#else +#define strcpy_sp strcpy_s +#define strncpy_sp strncpy_s +#define strcat_sp strcat_s +#define strncat_sp strncat_s +#define memcpy_sp memcpy_s +#define memset_sp memset_s +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* __SECUREC_H__5D13A042_DC3F_4ED9_A8D1_882811274C27 */ + diff --git a/third_party/securec/include/securectype.h b/third_party/securec/include/securectype.h new file mode 100644 index 0000000..0aed2a6 --- /dev/null +++ b/third_party/securec/include/securectype.h @@ -0,0 +1,542 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __SECURECTYPE_H__A7BBB686_AADA_451B_B9F9_44DACDAE18A7 +#define __SECURECTYPE_H__A7BBB686_AADA_451B_B9F9_44DACDAE18A7 + +#ifndef SECUREC_USING_STD_SECURE_LIB +#if defined(_MSC_VER) && _MSC_VER >= 1400 +#if defined(__STDC_WANT_SECURE_LIB__) && __STDC_WANT_SECURE_LIB__ == 0 +/* Security functions have been provided since vs2005, default use of system library functions */ +#define SECUREC_USING_STD_SECURE_LIB 0 +#else +#define SECUREC_USING_STD_SECURE_LIB 1 +#endif +#else +#define SECUREC_USING_STD_SECURE_LIB 0 +#endif +#endif + + +/* Compatibility with older Secure C versions, shielding VC symbol redefinition warning */ +#if defined(_MSC_VER) && _MSC_VER >= 1400 && SECUREC_USING_STD_SECURE_LIB == 0 +#ifndef SECUREC_DISABLE_CRT_FUNC +#define SECUREC_DISABLE_CRT_FUNC 1 +#endif +#ifndef SECUREC_DISABLE_CRT_IMP +#define SECUREC_DISABLE_CRT_IMP 1 +#endif +#else /* MSC VER */ +#ifndef SECUREC_DISABLE_CRT_FUNC +#define SECUREC_DISABLE_CRT_FUNC 0 +#endif +#ifndef SECUREC_DISABLE_CRT_IMP +#define SECUREC_DISABLE_CRT_IMP 0 +#endif +#endif + +#if SECUREC_DISABLE_CRT_FUNC +#ifdef __STDC_WANT_SECURE_LIB__ +#undef __STDC_WANT_SECURE_LIB__ +#endif +#define __STDC_WANT_SECURE_LIB__ 0 +#endif + +#if SECUREC_DISABLE_CRT_IMP +#ifdef _CRTIMP_ALTERNATIVE +#undef _CRTIMP_ALTERNATIVE +#endif +#define _CRTIMP_ALTERNATIVE /* comment microsoft *_s function */ +#endif + +/* Compile in kernel under macro control */ +#ifndef SECUREC_IN_KERNEL +#ifdef __KERNEL__ +#define SECUREC_IN_KERNEL 1 +#else +#define SECUREC_IN_KERNEL 0 +#endif +#endif + +#if SECUREC_IN_KERNEL +#ifndef SECUREC_ENABLE_SCANF_FILE +#define SECUREC_ENABLE_SCANF_FILE 0 +#endif +#ifndef SECUREC_ENABLE_WCHAR_FUNC +#define SECUREC_ENABLE_WCHAR_FUNC 0 +#endif +#else /* SECUREC_IN_KERNEL */ +#ifndef SECUREC_ENABLE_SCANF_FILE +#define SECUREC_ENABLE_SCANF_FILE 1 +#endif +#ifndef SECUREC_ENABLE_WCHAR_FUNC +#define SECUREC_ENABLE_WCHAR_FUNC 1 +#endif +#endif + + +/* Default secure function declaration, default declarations for non-standard functions */ +#ifndef SECUREC_SNPRINTF_TRUNCATED +#define SECUREC_SNPRINTF_TRUNCATED 1 +#endif + +#if SECUREC_USING_STD_SECURE_LIB +#if defined(_MSC_VER) && _MSC_VER >= 1400 +/* Declare secure functions that are not available in the vs compiler */ +#ifndef SECUREC_ENABLE_MEMSET +#define SECUREC_ENABLE_MEMSET 1 +#endif +/* vs 2005 have vsnprintf_s function */ +#ifndef SECUREC_ENABLE_VSNPRINTF +#define SECUREC_ENABLE_VSNPRINTF 0 +#endif +#ifndef SECUREC_ENABLE_SNPRINTF +/* vs 2005 have vsnprintf_s function Adapt the snprintf_s of the security function */ +#define snprintf_s _snprintf_s +#define SECUREC_ENABLE_SNPRINTF 0 +#endif +/* befor vs 2010 do not have v functions */ +#if _MSC_VER <= 1600 || defined(SECUREC_FOR_V_SCANFS) +#ifndef SECUREC_ENABLE_VFSCANF +#define SECUREC_ENABLE_VFSCANF 1 +#endif +#ifndef SECUREC_ENABLE_VSCANF +#define SECUREC_ENABLE_VSCANF 1 +#endif +#ifndef SECUREC_ENABLE_VSSCANF +#define SECUREC_ENABLE_VSSCANF 1 +#endif +#endif + +#else /* _MSC_VER */ +#ifndef SECUREC_ENABLE_MEMSET +#define SECUREC_ENABLE_MEMSET 0 +#endif +#ifndef SECUREC_ENABLE_SNPRINTF +#define SECUREC_ENABLE_SNPRINTF 0 +#endif +#ifndef SECUREC_ENABLE_VSNPRINTF +#define SECUREC_ENABLE_VSNPRINTF 0 +#endif +#endif + +#ifndef SECUREC_ENABLE_MEMMOVE +#define SECUREC_ENABLE_MEMMOVE 0 +#endif +#ifndef SECUREC_ENABLE_MEMCPY +#define SECUREC_ENABLE_MEMCPY 0 +#endif +#ifndef SECUREC_ENABLE_STRCPY +#define SECUREC_ENABLE_STRCPY 0 +#endif +#ifndef SECUREC_ENABLE_STRNCPY +#define SECUREC_ENABLE_STRNCPY 0 +#endif +#ifndef SECUREC_ENABLE_STRCAT +#define SECUREC_ENABLE_STRCAT 0 +#endif +#ifndef SECUREC_ENABLE_STRNCAT +#define SECUREC_ENABLE_STRNCAT 0 +#endif +#ifndef SECUREC_ENABLE_SPRINTF +#define SECUREC_ENABLE_SPRINTF 0 +#endif +#ifndef SECUREC_ENABLE_VSPRINTF +#define SECUREC_ENABLE_VSPRINTF 0 +#endif +#ifndef SECUREC_ENABLE_SSCANF +#define SECUREC_ENABLE_SSCANF 0 +#endif +#ifndef SECUREC_ENABLE_VSSCANF +#define SECUREC_ENABLE_VSSCANF 0 +#endif +#ifndef SECUREC_ENABLE_SCANF +#define SECUREC_ENABLE_SCANF 0 +#endif +#ifndef SECUREC_ENABLE_VSCANF +#define SECUREC_ENABLE_VSCANF 0 +#endif + +#ifndef SECUREC_ENABLE_FSCANF +#define SECUREC_ENABLE_FSCANF 0 +#endif +#ifndef SECUREC_ENABLE_VFSCANF +#define SECUREC_ENABLE_VFSCANF 0 +#endif +#ifndef SECUREC_ENABLE_STRTOK +#define SECUREC_ENABLE_STRTOK 0 +#endif +#ifndef SECUREC_ENABLE_GETS +#define SECUREC_ENABLE_GETS 0 +#endif + +#else /* SECUREC_USE_STD_SECURE_LIB */ + +#ifndef SECUREC_ENABLE_MEMSET +#define SECUREC_ENABLE_MEMSET 1 +#endif +#ifndef SECUREC_ENABLE_MEMMOVE +#define SECUREC_ENABLE_MEMMOVE 1 +#endif +#ifndef SECUREC_ENABLE_MEMCPY +#define SECUREC_ENABLE_MEMCPY 1 +#endif +#ifndef SECUREC_ENABLE_STRCPY +#define SECUREC_ENABLE_STRCPY 1 +#endif +#ifndef SECUREC_ENABLE_STRNCPY +#define SECUREC_ENABLE_STRNCPY 1 +#endif +#ifndef SECUREC_ENABLE_STRCAT +#define SECUREC_ENABLE_STRCAT 1 +#endif +#ifndef SECUREC_ENABLE_STRNCAT +#define SECUREC_ENABLE_STRNCAT 1 +#endif +#ifndef SECUREC_ENABLE_SPRINTF +#define SECUREC_ENABLE_SPRINTF 1 +#endif +#ifndef SECUREC_ENABLE_VSPRINTF +#define SECUREC_ENABLE_VSPRINTF 1 +#endif +#ifndef SECUREC_ENABLE_SNPRINTF +#define SECUREC_ENABLE_SNPRINTF 1 +#endif +#ifndef SECUREC_ENABLE_VSNPRINTF +#define SECUREC_ENABLE_VSNPRINTF 1 +#endif +#ifndef SECUREC_ENABLE_SSCANF +#define SECUREC_ENABLE_SSCANF 1 +#endif +#ifndef SECUREC_ENABLE_VSSCANF +#define SECUREC_ENABLE_VSSCANF 1 +#endif +#ifndef SECUREC_ENABLE_SCANF +#if SECUREC_ENABLE_SCANF_FILE +#define SECUREC_ENABLE_SCANF 1 +#else +#define SECUREC_ENABLE_SCANF 0 +#endif +#endif +#ifndef SECUREC_ENABLE_VSCANF +#if SECUREC_ENABLE_SCANF_FILE +#define SECUREC_ENABLE_VSCANF 1 +#else +#define SECUREC_ENABLE_VSCANF 0 +#endif +#endif + +#ifndef SECUREC_ENABLE_FSCANF +#if SECUREC_ENABLE_SCANF_FILE +#define SECUREC_ENABLE_FSCANF 1 +#else +#define SECUREC_ENABLE_FSCANF 0 +#endif +#endif +#ifndef SECUREC_ENABLE_VFSCANF +#if SECUREC_ENABLE_SCANF_FILE +#define SECUREC_ENABLE_VFSCANF 1 +#else +#define SECUREC_ENABLE_VFSCANF 0 +#endif +#endif + +#ifndef SECUREC_ENABLE_STRTOK +#define SECUREC_ENABLE_STRTOK 1 +#endif +#ifndef SECUREC_ENABLE_GETS +#define SECUREC_ENABLE_GETS 1 +#endif +#endif /* SECUREC_USE_STD_SECURE_LIB */ + +#if SECUREC_ENABLE_SCANF_FILE == 0 +#if SECUREC_ENABLE_FSCANF +#undef SECUREC_ENABLE_FSCANF +#define SECUREC_ENABLE_FSCANF 0 +#endif +#if SECUREC_ENABLE_VFSCANF +#undef SECUREC_ENABLE_VFSCANF +#define SECUREC_ENABLE_VFSCANF 0 +#endif +#if SECUREC_ENABLE_SCANF +#undef SECUREC_ENABLE_SCANF +#define SECUREC_ENABLE_SCANF 0 +#endif +#if SECUREC_ENABLE_FSCANF +#undef SECUREC_ENABLE_FSCANF +#define SECUREC_ENABLE_FSCANF 0 +#endif + +#endif + +#if SECUREC_IN_KERNEL +#include +#include +#else +#include +#include +#include +#endif + +/* If you need high performance, enable the SECUREC_WITH_PERFORMANCE_ADDONS macro, default is enable . + * The macro is automatically closed on the windows platform and linux kernel + */ +#ifndef SECUREC_WITH_PERFORMANCE_ADDONS +#if SECUREC_IN_KERNEL +#define SECUREC_WITH_PERFORMANCE_ADDONS 0 +#else +#define SECUREC_WITH_PERFORMANCE_ADDONS 1 +#endif +#endif + +/* if enable SECUREC_COMPATIBLE_WIN_FORMAT, the output format will be compatible to Windows. */ +#if (defined(_WIN32) || defined(_WIN64) || defined(_MSC_VER)) && !defined(SECUREC_COMPATIBLE_LINUX_FORMAT) +#if !defined(SECUREC_COMPATIBLE_WIN_FORMAT) +#define SECUREC_COMPATIBLE_WIN_FORMAT +#endif +#endif + +#if defined(SECUREC_COMPATIBLE_WIN_FORMAT) +/* in windows platform, can't use optimized function for there is no __builtin_constant_p like function */ +/* If need optimized macro, can define this: define __builtin_constant_p(x) 0 */ +#ifdef SECUREC_WITH_PERFORMANCE_ADDONS +#undef SECUREC_WITH_PERFORMANCE_ADDONS +#define SECUREC_WITH_PERFORMANCE_ADDONS 0 +#endif +#endif + +#if defined(__VXWORKS__) || defined(__vxworks) || defined(__VXWORKS) || defined(_VXWORKS_PLATFORM_) || \ + defined(SECUREC_VXWORKS_VERSION_5_4) +#if !defined(SECUREC_VXWORKS_PLATFORM) +#define SECUREC_VXWORKS_PLATFORM +#endif +#endif + +/* if enable SECUREC_COMPATIBLE_LINUX_FORMAT, the output format will be compatible to Linux. */ +#if !(defined(SECUREC_COMPATIBLE_WIN_FORMAT) || defined(SECUREC_VXWORKS_PLATFORM)) +#if !defined(SECUREC_COMPATIBLE_LINUX_FORMAT) +#define SECUREC_COMPATIBLE_LINUX_FORMAT +#endif +#endif + +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT +#include +#endif + +/* add the -DSECUREC_SUPPORT_FORMAT_WARNING compiler option to supoort -Wformat. + * default does not check the format is that the same data type in the actual code + * in the product is different in the original data type definition of VxWorks and Linux. + */ +#ifndef SECUREC_SUPPORT_FORMAT_WARNING +#define SECUREC_SUPPORT_FORMAT_WARNING 0 +#endif + +/* SECUREC_PCLINT for tool do not recognize __attribute__ just for pclint */ +#if SECUREC_SUPPORT_FORMAT_WARNING && !defined(SECUREC_PCLINT) +#define SECUREC_ATTRIBUTE(x, y) __attribute__((format(printf, (x), (y)))) +#else +#define SECUREC_ATTRIBUTE(x, y) +#endif + +/* SECUREC_PCLINT for tool do not recognize __builtin_expect, just for pclint */ +#if defined(__GNUC__) && \ + ((__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 3))) && \ + !defined(SECUREC_PCLINT) +/* This is a built-in function that can be used without a declaration, if you encounter an undeclared compilation alarm, + * you can add -DSECUREC_NEED_BUILTIN_EXPECT_DECLARE to complier options + */ +#if defined(SECUREC_NEED_BUILTIN_EXPECT_DECLARE) +long __builtin_expect(long exp, long c); +#endif +#define SECUREC_LIKELY(x) __builtin_expect(!!(x), 1) +#define SECUREC_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else +#define SECUREC_LIKELY(x) (x) +#define SECUREC_UNLIKELY(x) (x) +#endif + +/* define the max length of the string */ +#ifndef SECUREC_STRING_MAX_LEN +#define SECUREC_STRING_MAX_LEN (0x7fffffffUL) +#endif +#define SECUREC_WCHAR_STRING_MAX_LEN (SECUREC_STRING_MAX_LEN / sizeof(wchar_t)) + +/* add SECUREC_MEM_MAX_LEN for memcpy and memmove */ +#ifndef SECUREC_MEM_MAX_LEN +#define SECUREC_MEM_MAX_LEN (0x7fffffffUL) +#endif +#define SECUREC_WCHAR_MEM_MAX_LEN (SECUREC_MEM_MAX_LEN / sizeof(wchar_t)) + +#if SECUREC_STRING_MAX_LEN > 0x7fffffff +#error "max string is 2G" +#endif + +#if (defined(__GNUC__) && defined(__SIZEOF_POINTER__)) +#if (__SIZEOF_POINTER__ != 4) && (__SIZEOF_POINTER__ != 8) +#error "unsupported system" +#endif +#endif + +#if defined(_WIN64) || defined(WIN64) || defined(__LP64__) || defined(_LP64) +#define SECUREC_ON_64BITS +#endif + +#if (!defined(SECUREC_ON_64BITS) && defined(__GNUC__) && defined(__SIZEOF_POINTER__)) +#if __SIZEOF_POINTER__ == 8 +#define SECUREC_ON_64BITS +#endif +#endif + +#if defined(__SVR4) || defined(__svr4__) +#define SECUREC_ON_SOLARIS +#endif + +#if (defined(__hpux) || defined(_AIX) || defined(SECUREC_ON_SOLARIS)) +#define SECUREC_ON_UNIX +#endif + +/* codes should run under the macro SECUREC_COMPATIBLE_LINUX_FORMAT in unknow system on default, + * and strtold. The function + * strtold is referenced first at ISO9899:1999(C99), and some old compilers can + * not support these functions. Here provides a macro to open these functions: + * SECUREC_SUPPORT_STRTOLD -- if defined, strtold will be used + */ +#ifndef SECUREC_SUPPORT_STRTOLD +#define SECUREC_SUPPORT_STRTOLD 0 +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT)) +#if defined(__USE_ISOC99) || \ + (defined(_AIX) && defined(_ISOC99_SOURCE)) || \ + (defined(__hpux) && defined(__ia64)) || \ + (defined(SECUREC_ON_SOLARIS) && (!defined(_STRICT_STDC) && !defined(__XOPEN_OR_POSIX)) || \ + defined(_STDC_C99) || defined(__EXTENSIONS__)) +#undef SECUREC_SUPPORT_STRTOLD +#define SECUREC_SUPPORT_STRTOLD 1 +#endif +#endif +#if ((defined(SECUREC_WRLINUX_BELOW4) || defined(_WRLINUX_BELOW4_))) +#undef SECUREC_SUPPORT_STRTOLD +#define SECUREC_SUPPORT_STRTOLD 0 +#endif +#endif + + +#if SECUREC_WITH_PERFORMANCE_ADDONS + +#ifndef SECUREC_TWO_MIN +#define SECUREC_TWO_MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +/* for strncpy_s performance optimization */ +#define SECUREC_STRNCPY_SM(dest, destMax, src, count) \ + (((void *)(dest) != NULL && (void *)(src) != NULL && (size_t)(destMax) > 0 && \ + (((unsigned long long)(destMax) & (unsigned long long)(-2)) < SECUREC_STRING_MAX_LEN) && \ + (SECUREC_TWO_MIN((size_t)(count), strlen(src)) + 1) <= (size_t)(destMax)) ? \ + (((size_t)(count) < strlen(src)) ? (memcpy((dest), (src), (count)), *((char *)(dest) + (count)) = '\0', EOK) : \ + (memcpy((dest), (src), strlen(src) + 1), EOK)) : (strncpy_error((dest), (destMax), (src), (count)))) + +#define SECUREC_STRCPY_SM(dest, destMax, src) \ + (((void *)(dest) != NULL && (void *)(src) != NULL && (size_t)(destMax) > 0 && \ + (((unsigned long long)(destMax) & (unsigned long long)(-2)) < SECUREC_STRING_MAX_LEN) && \ + (strlen(src) + 1) <= (size_t)(destMax)) ? (memcpy((dest), (src), strlen(src) + 1), EOK) : \ + (strcpy_error((dest), (destMax), (src)))) + +/* for strcat_s performance optimization */ +#if defined(__GNUC__) +#define SECUREC_STRCAT_SM(dest, destMax, src) ({ \ + int catRet = EOK; \ + if ((void *)(dest) != NULL && (void *)(src) != NULL && (size_t)(destMax) > 0 && \ + (((unsigned long long)(destMax) & (unsigned long long)(-2)) < SECUREC_STRING_MAX_LEN)) { \ + char *catTmpDst = (char *)(dest); \ + size_t catRestSize = (destMax); \ + while (catRestSize > 0 && *catTmpDst != '\0') { \ + ++catTmpDst; \ + --catRestSize; \ + } \ + if (catRestSize == 0) { \ + catRet = EINVAL; \ + } else if ((strlen(src) + 1) <= catRestSize) { \ + memcpy(catTmpDst, (src), strlen(src) + 1); \ + catRet = EOK; \ + } else { \ + catRet = ERANGE; \ + } \ + if (catRet != EOK) { \ + catRet = strcat_s((dest), (destMax), (src)); \ + } \ + } else { \ + catRet = strcat_s((dest), (destMax), (src)); \ + } \ + catRet; \ +}) +#else +#define SECUREC_STRCAT_SM(dest, destMax, src) strcat_s((dest), (destMax), (src)) +#endif + +/* for strncat_s performance optimization */ +#if defined(__GNUC__) +#define SECUREC_STRNCAT_SM(dest, destMax, src, count) ({ \ + int ncatRet = EOK; \ + if ((void *)(dest) != NULL && (void *)(src) != NULL && (size_t)(destMax) > 0 && \ + (((unsigned long long)(destMax) & (unsigned long long)(-2)) < SECUREC_STRING_MAX_LEN) && \ + (((unsigned long long)(count) & (unsigned long long)(-2)) < SECUREC_STRING_MAX_LEN)) { \ + char *ncatTmpDest = (char *)(dest); \ + size_t ncatRestSize = (size_t)(destMax); \ + while (ncatRestSize > 0 && *ncatTmpDest != '\0') { \ + ++ncatTmpDest; \ + --ncatRestSize; \ + } \ + if (ncatRestSize == 0) { \ + ncatRet = EINVAL; \ + } else if ((SECUREC_TWO_MIN((count), strlen(src)) + 1) <= ncatRestSize) { \ + if ((size_t)(count) < strlen(src)) { \ + memcpy(ncatTmpDest, (src), (count)); \ + *(ncatTmpDest + (count)) = '\0'; \ + } else { \ + memcpy(ncatTmpDest, (src), strlen(src) + 1); \ + } \ + } else { \ + ncatRet = ERANGE; \ + } \ + if (ncatRet != EOK) { \ + ncatRet = strncat_s((dest), (destMax), (src), (count)); \ + } \ + } else { \ + ncatRet = strncat_s((dest), (destMax), (src), (count)); \ + } \ + ncatRet; \ +}) +#else +#define SECUREC_STRNCAT_SM(dest, destMax, src, count) strncat_s((dest), (destMax), (src), (count)) +#endif + +/* SECUREC_MEMCPY_SM do NOT check buffer overlap by default */ +#define SECUREC_MEMCPY_SM(dest, destMax, src, count) \ + (!(((size_t)(destMax) == 0) || \ + (((unsigned long long)(destMax) & (unsigned long long)(-2)) > SECUREC_MEM_MAX_LEN) || \ + ((size_t)(count) > (size_t)(destMax)) || ((void *)(dest)) == NULL || ((void *)(src) == NULL))? \ + (memcpy((dest), (src), (count)), EOK) : \ + (memcpy_s((dest), (destMax), (src), (count)))) + +#define SECUREC_MEMSET_SM(dest, destMax, c, count) \ + (!(((size_t)(destMax) == 0) || \ + (((unsigned long long)(destMax) & (unsigned long long)(-2)) > SECUREC_MEM_MAX_LEN) || \ + ((void *)(dest) == NULL) || ((size_t)(count) > (size_t)(destMax))) ? \ + (memset((dest), (c), (count)), EOK) : \ + (memset_s((dest), (destMax), (c), (count)))) + +#endif +#endif /* __SECURECTYPE_H__A7BBB686_AADA_451B_B9F9_44DACDAE18A7 */ + diff --git a/third_party/securec/src/CMakeLists.txt b/third_party/securec/src/CMakeLists.txt new file mode 100644 index 0000000..2af0f76 --- /dev/null +++ b/third_party/securec/src/CMakeLists.txt @@ -0,0 +1,6 @@ +if (CMAKE_SYSTEM_NAME MATCHES "Windows") + list(APPEND SECUREC_SRCS "memset_s.c") +else() + aux_source_directory(. SECUREC_SRCS) +endif() +add_library(securec STATIC ${SECUREC_SRCS}) diff --git a/third_party/securec/src/fscanf_s.c b/third_party/securec/src/fscanf_s.c new file mode 100644 index 0000000..8ceda9a --- /dev/null +++ b/third_party/securec/src/fscanf_s.c @@ -0,0 +1,56 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * + * The fscanf_s function is equivalent to fscanf except that the c, s, + * and [ conversion specifiers apply to a pair of arguments (unless assignment suppression is indicated by a*) + * The fscanf function reads data from the current position of stream into + * the locations given by argument (if any). Each argument must be a pointer + * to a variable of a type that corresponds to a type specifier in format. + * format controls the interpretation of the input fields and has the same + * form and function as the format argument for scanf. + * + * + * stream Pointer to FILE structure. + * format Format control string, see Format Specifications. + * ... Optional arguments. + * + * + * ... The convered value stored in user assigned address + * + * + * Each of these functions returns the number of fields successfully converted + * and assigned; the return value does not include fields that were read but + * not assigned. A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int fscanf_s(FILE *stream, const char *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vfscanf_s(stream, format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} + + diff --git a/third_party/securec/src/fwscanf_s.c b/third_party/securec/src/fwscanf_s.c new file mode 100644 index 0000000..f826b7d --- /dev/null +++ b/third_party/securec/src/fwscanf_s.c @@ -0,0 +1,55 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * + * The fwscanf_s function is the wide-character equivalent of the fscanf_s function + * The fwscanf_s function reads data from the current position of stream into + * the locations given by argument (if any). Each argument must be a pointer + * to a variable of a type that corresponds to a type specifier in format. + * format controls the interpretation of the input fields and has the same + * form and function as the format argument for scanf. + * + * + * stream Pointer to FILE structure. + * format Format control string, see Format Specifications. + * ... Optional arguments. + * + * + * ... The converted value stored in user assigned address + * + * + * Each of these functions returns the number of fields successfully converted + * and assigned; the return value does not include fields that were read but + * not assigned. A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int fwscanf_s(FILE *stream, const wchar_t *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vfwscanf_s(stream, format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} + + diff --git a/third_party/securec/src/gets_s.c b/third_party/securec/src/gets_s.c new file mode 100644 index 0000000..57fd623 --- /dev/null +++ b/third_party/securec/src/gets_s.c @@ -0,0 +1,75 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securecutil.h" + +static void SecTrimCRLF(char *buffer, size_t len) +{ + int i; + /* No need to determine whether integer overflow exists */ + for (i = (int)(len - 1); i >= 0 && (buffer[i] == '\r' || buffer[i] == '\n'); --i) { + buffer[i] = '\0'; + } + return; +} + +/* + * + * The gets_s function reads at most one less than the number of characters + * specified by destMax from the stream pointed to by stdin, into the array pointed to by buffer + * The line consists of all characters up to and including + * the first newline character ('\n'). gets_s then replaces the newline + * character with a null character ('\0') before returning the line. + * If the first character read is the end-of-file character, a null character + * is stored at the beginning of buffer and NULL is returned. + * + * + * buffer Storage location for input string. + * numberOfElements The size of the buffer. + * + * + * buffer is updated + * + * + * buffer Successful operation + * NULL Improper parameter or read fail + */ +char *gets_s(char *buffer, size_t numberOfElements) +{ + size_t len; +#ifdef SECUREC_COMPATIBLE_WIN_FORMAT + size_t bufferSize = ((numberOfElements == (size_t)-1) ? SECUREC_STRING_MAX_LEN : numberOfElements); +#else + size_t bufferSize = numberOfElements; +#endif + + if (buffer == NULL || bufferSize == 0 || bufferSize > SECUREC_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_PARAMTER("gets_s"); + return NULL; + } + + if (fgets(buffer, (int)bufferSize, stdin) == NULL) { + return NULL; + } + + len = strlen(buffer); + if (len > 0 && len < bufferSize) { + SecTrimCRLF(buffer, len); + } + + return buffer; +} + diff --git a/third_party/securec/src/input.inl b/third_party/securec/src/input.inl new file mode 100644 index 0000000..a5a92e5 --- /dev/null +++ b/third_party/securec/src/input.inl @@ -0,0 +1,2125 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INPUT_INL_5D13A042_DC3F_4ED9_A8D1_882811274C27 +#define INPUT_INL_5D13A042_DC3F_4ED9_A8D1_882811274C27 + +#if SECUREC_IN_KERNEL +#include +#ifndef EOF +#define EOF (-1) +#endif +#else +#if !defined(SECUREC_SYSAPI4VXWORKS) && !defined(SECUREC_CTYPE_MACRO_ADAPT) +#include +#ifdef SECUREC_FOR_WCHAR +#include /* for iswspace */ +#endif +#endif +#endif + +#define SECUREC_NUM_WIDTH_SHORT 0 +#define SECUREC_NUM_WIDTH_INT 1 +#define SECUREC_NUM_WIDTH_LONG 2 +#define SECUREC_NUM_WIDTH_LONG_LONG 3 /* also long double */ + +#define SECUREC_BUF_EXT_MUL 2 +#define SECUREC_BUFFERED_BLOK_SIZE 1024 + +#if defined(SECUREC_VXWORKS_PLATFORM) && !defined(va_copy) && !defined(__va_copy) +/* the name is the same as system macro. */ +#define __va_copy(d, s) do { \ + size_t size_of_d = (size_t)sizeof(d); \ + size_t size_of_s = (size_t)sizeof(s); \ + if (size_of_d != size_of_s) { \ + (void)memcpy((d), (s), sizeof(va_list)); \ + } else { \ + (void)memcpy(&(d), &(s), sizeof(va_list)); \ + } \ +} SECUREC_WHILE_ZERO +#endif + + +#define SECUREC_MULTI_BYTE_MAX_LEN 6 +/* Record a flag for each bit */ +#define SECUREC_BRACKET_INDEX(x) ((unsigned int)(x) >> 3) +#define SECUREC_BRACKET_VALUE(x) ((unsigned char)(1 << ((unsigned int)(x) & 7))) + + +/* Compatibility macro name cannot be modifie */ +#ifndef UNALIGNED +#if !(defined(_M_IA64)) && !(defined(_M_AMD64)) +#define UNALIGNED +#else +#define UNALIGNED __unaligned +#endif +#endif + +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) +/* Max 64bit value is 0xffffffffffffffff */ +#define SECUREC_MAX_64BITS_VALUE 18446744073709551615ULL +#define SECUREC_MAX_64BITS_VALUE_DIV_TEN 1844674407370955161ULL +#define SECUREC_MAX_64BITS_VALUE_CUT_LAST_DIGIT 18446744073709551610ULL +#define SECUREC_MIN_64BITS_NEG_VALUE 9223372036854775808ULL +#define SECUREC_MAX_64BITS_POS_VALUE 9223372036854775807ULL +#define SECUREC_MIN_32BITS_NEG_VALUE 2147483648ULL +#define SECUREC_MAX_32BITS_POS_VALUE 2147483647ULL +#define SECUREC_MAX_32BITS_VALUE 4294967295ULL +#define SECUREC_MAX_32BITS_VALUE_INC 4294967296ULL +#define SECUREC_MAX_32BITS_VALUE_DIV_TEN 429496729ULL +#define SECUREC_LONG_BIT_NUM ((unsigned int)(sizeof(long) << 3U)) + +#define SECUREC_LONG_HEX_BEYOND_MAX(number) (((number) >> (SECUREC_LONG_BIT_NUM - 4U)) > 0) +#define SECUREC_LONG_OCTAL_BEYOND_MAX(number) (((number) >> (SECUREC_LONG_BIT_NUM - 3U)) > 0) + +#define SECUREC_QWORD_HEX_BEYOND_MAX(number) (((number) >> (64U - 4U)) > 0) +#define SECUREC_QWORD_OCTAL_BEYOND_MAX(number) (((number) >> (64U - 3U)) > 0) + +#define SECUREC_LP64_BIT_WIDTH 64 +#define SECUREC_LP32_BIT_WIDTH 32 + +#endif + +#define SECUREC_CHAR(x) (x) +#define SECUREC_BRACE '{' /* [ to { */ + +#ifdef SECUREC_FOR_WCHAR +#define SECUREC_SCANF_BRACKET_CONDITION(comChr, ch, table, mask) ((comChr) == SECUREC_BRACE && \ + (table) != NULL && \ + (((table)[((unsigned int)(int)(ch) & SECUREC_CHAR_MASK) >> 3] ^ (mask)) & \ + (1 << ((unsigned int)(int)(ch) & 7)))) +#else +#define SECUREC_SCANF_BRACKET_CONDITION(comChr, ch, table, mask) ((comChr) == SECUREC_BRACE && \ + (((table)[((unsigned char)(ch) & 0xff) >> 3] ^ (mask)) & (1 << ((unsigned char)(ch) & 7)))) +#endif +#define SECUREC_SCANF_STRING_CONDITION(comChr, ch) ((comChr) == SECUREC_CHAR('s') && \ + (!((ch) >= SECUREC_CHAR('\t') && (ch) <= SECUREC_CHAR('\r')) && (ch) != SECUREC_CHAR(' '))) + +/* Do not use |= optimize this code, it will cause compiling warning */ +/* only supports wide characters with a maximum length of two bytes */ +#define SECUREC_BRACKET_SET_BIT(table, ch) do { \ + unsigned int tableIndex = SECUREC_BRACKET_INDEX(((unsigned int)(int)(ch) & SECUREC_CHAR_MASK)); \ + unsigned int tableValue = SECUREC_BRACKET_VALUE(((unsigned int)(int)(ch) & SECUREC_CHAR_MASK)); \ + (table)[tableIndex] = (unsigned char)((table)[tableIndex] | tableValue); \ +} SECUREC_WHILE_ZERO + +#ifdef SECUREC_FOR_WCHAR +/* table size is 32 x 256 */ +#define SECUREC_BRACKET_TABLE_SIZE 8192 +#define SECUREC_EOF WEOF +#define SECUREC_MB_LEN 16 /* max. # bytes in multibyte char ,see MB_LEN_MAX */ +/* int to unsigned int clear e571 */ +#define SECUREC_IS_DIGIT(chr) (!((unsigned int)(int)(chr) & 0xff00) && isdigit(((unsigned int)(int)(chr) & 0x00ff))) +#define SECUREC_IS_XDIGIT(chr) (!((unsigned int)(int)(chr) & 0xff00) && isxdigit(((unsigned int)(int)(chr) & 0x00ff))) +#define SECUREC_IS_SPACE(chr) iswspace((wint_t)(int)(chr)) +#else +#define SECUREC_BRACKET_TABLE_SIZE 32 +#define SECUREC_EOF EOF +#define SECUREC_IS_DIGIT(chr) isdigit((unsigned char)(chr) & 0x00ff) +#define SECUREC_IS_XDIGIT(chr) isxdigit((unsigned char)(chr) & 0x00ff) +#define SECUREC_IS_SPACE(chr) isspace((unsigned char)(chr) & 0x00ff) +#endif + + +static SecInt SecSkipSpaceChar(SecFileStream *stream, int *counter); +static SecInt SecGetChar(SecFileStream *stream, int *counter); +static void SecUnGetChar(SecInt ch, SecFileStream *stream, int *counter); + +typedef struct { +#ifdef SECUREC_FOR_WCHAR + unsigned char *table; /* default NULL */ +#else + unsigned char table[SECUREC_BRACKET_TABLE_SIZE]; /* Array length is large enough in application scenarios */ +#endif + unsigned char mask; /* default 0 */ +} SecBracketTable; + +#ifdef SECUREC_FOR_WCHAR +#define SECUREC_INIT_BRACKET_TABLE { NULL, 0 } +#else +#define SECUREC_INIT_BRACKET_TABLE { { 0 }, 0 } +#endif + +#if SECUREC_ENABLE_SCANF_FLOAT +typedef struct { + size_t floatStrSize; /* tialization must be length of buffer in charater */ + size_t floatStrUsedLen; /* store float string len */ + SecChar buffer[SECUREC_FLOAT_BUFSIZE + 1]; + SecChar *floatStr; /* Initialization must point to buffer */ + SecChar *allocatedFloatStr; /* Initialization must be NULL to store alloced point */ +} SecFloatSpec; +#endif + +typedef struct { + SecUnsignedInt64 number64; + unsigned long number; + int numberWidth; /* 0 = SHORT, 1 = int, > 1 long or L_DOUBLE */ + int isInt64Arg; /* 1 for 64-bit integer, 0 otherwise */ + int negative; /* 0 is positive */ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + int beyondMax; /* Non-zero means beyond */ +#endif + void *argPtr; /* Variable parameter pointer */ + size_t arrayWidth; /* length of pointer Variable parameter, in charaters */ + int width; /* width number in format */ + int widthSet; /* 0 is not set width in format */ + int comChr; /* Lowercase format conversion characters */ + int oriComChr; /* store number conversion */ + signed char isWChar; /* -1/0 not wchar, 1 for wchar */ + char suppress; /* 0 is not have %* in format */ +} SecScanSpec; + +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) +#define SECUREC_INIT_NUMBER_SPEC { 0, 0, 0, 0, 0, 0, NULL, 0, 0, 0, 0, 0, 0 } +#else +#define SECUREC_INIT_NUMBER_SPEC { 0, 0, 0, 0, 0, 0, NULL, 0, 0, 0, 0, 0 } +#endif + +#ifdef SECUREC_FOR_WCHAR +#define SECUREC_GETC fgetwc +#define SECUREC_UN_GETC ungetwc +#define SECUREC_CHAR_MASK 0xffff +#else +#define SECUREC_GETC fgetc +#define SECUREC_UN_GETC ungetc +#define SECUREC_CHAR_MASK 0xff +#endif + +/* + * Determine if it is a 64-bit pointer function + * return 0 is not ,1 is 64bit pointer + */ +static int SecIs64BitPtr(size_t sizeOfVoidStar) +{ + /* point size is 4 or 8 , Under the 64 bit system, the value not 0 */ + /* to clear e778 */ + if ((sizeOfVoidStar & sizeof(SecInt64)) != 0) { + return 1; + } + return 0; +} + +#if SECUREC_ENABLE_SCANF_FLOAT + +/* + * Convert a floating point string to a floating point number + */ +static void SecAssignFloat(const char *floatStr, int numberWidth, void *argPtr) +{ + char *endPtr = NULL; + double d; +#if SECUREC_SUPPORT_STRTOLD + if (numberWidth == SECUREC_NUM_WIDTH_LONG_LONG) { + long double d2 = strtold(floatStr, &endPtr); + *(long double UNALIGNED *)(argPtr) = d2; + return; + } +#endif + d = strtod(floatStr, &endPtr); + if (numberWidth > SECUREC_NUM_WIDTH_INT) { + *(double UNALIGNED *)(argPtr) = (double)d; + } else { + *(float UNALIGNED *)(argPtr) = (float)d; + } +} + +#ifdef SECUREC_FOR_WCHAR +/* + * Convert a floating point wchar string to a floating point number + * Success ret 0 + */ +static int SecAssignFloatW(const SecFloatSpec *floatSpec, const SecScanSpec *spec) +{ + /* convert float string */ + size_t mbsLen; + size_t tempFloatStrLen = (size_t)(floatSpec->floatStrSize + 1) * sizeof(wchar_t); + char *tempFloatStr = (char *)SECUREC_MALLOC(tempFloatStrLen); + + if (tempFloatStr == NULL) { + return -1; + } + tempFloatStr[0] = '\0'; + SECUREC_MASK_MSVC_CRT_WARNING + mbsLen = wcstombs(tempFloatStr, floatSpec->floatStr, tempFloatStrLen - 1); + SECUREC_END_MASK_MSVC_CRT_WARNING + if (mbsLen != (size_t)-1) { + tempFloatStr[mbsLen] = '\0'; + SecAssignFloat(tempFloatStr, spec->numberWidth, spec->argPtr); + } else { + SECUREC_FREE(tempFloatStr); + return -1; + } + SECUREC_FREE(tempFloatStr); + return 0; +} +#endif +/* + * Splice floating point string + * return 0 OK + */ +static int SecUpdateFloatString(SecChar ch, SecFloatSpec *floatSpec) +{ + floatSpec->floatStr[floatSpec->floatStrUsedLen++] = ch; /* ch must be '0' - '9' */ + if (floatSpec->floatStrUsedLen < floatSpec->floatStrSize) { + return 0; + } + if (floatSpec->allocatedFloatStr == NULL) { + /* add 1 to clear ZERO LENGTH ALLOCATIONS warning */ + size_t oriBufSize = floatSpec->floatStrSize* (SECUREC_BUF_EXT_MUL * sizeof(SecChar)) + 1; + void *tmpPointer = (void *)SECUREC_MALLOC(oriBufSize); + if (tmpPointer == NULL) { + return -1; + } + if (memcpy_s(tmpPointer, oriBufSize, floatSpec->floatStr, floatSpec->floatStrSize * sizeof(SecChar)) != EOK) { + SECUREC_FREE(tmpPointer); /* This is a dead code, just to meet the coding requirements */ + return -1; + } + floatSpec->floatStr = (SecChar *) (tmpPointer); + floatSpec->allocatedFloatStr = (SecChar *) (tmpPointer); /* use to clear free on stack warning */ + floatSpec->floatStrSize *= SECUREC_BUF_EXT_MUL; /* this is OK, oriBufSize plus 1 just clear warning */ + return 0; + } else { + /* LSD 2014.3.6 fix, replace realloc to malloc to avoid heap injection */ + size_t oriBufSize = floatSpec->floatStrSize * sizeof(SecChar); + size_t nextSize = (oriBufSize * SECUREC_BUF_EXT_MUL) + 1; /* add 1 to clear satic check tool warning */ + /* Prevents integer overflow when calculating the wide character length. + * The maximum length of SECUREC_MAX_WIDTH_LEN is enough + */ + if (nextSize <= SECUREC_MAX_WIDTH_LEN) { + void *tmpPointer = (void *)SECUREC_MALLOC(nextSize); + if (tmpPointer == NULL) { + return -1; + } + if (memcpy_s(tmpPointer, nextSize, floatSpec->floatStr, oriBufSize) != EOK) { + SECUREC_FREE(tmpPointer); /* This is a dead code, just to meet the coding requirements */ + return -1; + } + if (memset_s(floatSpec->floatStr, oriBufSize, 0, oriBufSize) != EOK) { + SECUREC_FREE(tmpPointer); /* This is a dead code, just to meet the coding requirements */ + return -1; + } + SECUREC_FREE(floatSpec->floatStr); + + floatSpec->floatStr = (SecChar *) (tmpPointer); + floatSpec->allocatedFloatStr = (SecChar *) (tmpPointer); /* use to clear free on stack warning */ + floatSpec->floatStrSize *= SECUREC_BUF_EXT_MUL; /* this is OK, oriBufSize plus 1 just clear warning */ + return 0; + } + } + return -1; +} +#endif + +#ifndef SECUREC_FOR_WCHAR +/* LSD only multi-bytes string need isleadbyte() function */ +static int SecIsLeadByte(SecInt ch) +{ + unsigned int c = (unsigned int)ch; +#if !(defined(_MSC_VER) || defined(_INC_WCTYPE)) + return (int)(c & 0x80); +#else + return (int)isleadbyte((int)(c & 0xff)); +#endif +} +#endif + +/* + * Parsing whether it is a wide character + */ +static void SecUpdateWcharFlagByType(SecUnsignedChar ch, SecScanSpec *spec) +{ +#if defined(SECUREC_FOR_WCHAR) && (defined(SECUREC_COMPATIBLE_WIN_FORMAT)) + signed char flagForUpperType = -1; + signed char flagForLowerType = 1; +#else + signed char flagForUpperType = 1; + signed char flagForLowerType = -1; +#endif + /* if no l or h flag */ + if (spec->isWChar == 0) { + if ((ch == SECUREC_CHAR('C')) || (ch == SECUREC_CHAR('S'))) { + spec->isWChar = flagForUpperType; + } else { + spec->isWChar = flagForLowerType; + } + } + return; +} +/* + * decode %l %ll + */ +static void SecDecodeScanQualifierL(const SecUnsignedChar **format, SecScanSpec *spec) +{ + const SecUnsignedChar *fmt = *format; + if (*(fmt + 1) == SECUREC_CHAR('l')) { + spec->isInt64Arg = 1; + spec->numberWidth = SECUREC_NUM_WIDTH_LONG_LONG; + ++fmt; + } else { + spec->numberWidth = SECUREC_NUM_WIDTH_LONG; +#if defined(SECUREC_ON_64BITS) && !(defined(SECUREC_COMPATIBLE_WIN_FORMAT)) + /* on window 64 system sizeof long is 32bit */ + spec->isInt64Arg = 1; +#endif + spec->isWChar = 1; + } + *format = fmt; +} + +/* + * decode %I %I43 %I64 %Id %Ii %Io ... + * set finishFlag to 1 finish Flag + */ +static void SecDecodeScanQualifierI(const SecUnsignedChar **format, SecScanSpec *spec, int *finishFlag) +{ + const SecUnsignedChar *fmt = *format; + if ((*(fmt + 1) == SECUREC_CHAR('6')) && + (*(fmt + 2) == SECUREC_CHAR('4'))) { /* offset 2 for I64 */ + spec->isInt64Arg = 1; + *format = *format + 2; /* add 2 to skip I64 point to '4' next loop will inc */ + } else if ((*(fmt + 1) == SECUREC_CHAR('3')) && + (*(fmt + 2) == SECUREC_CHAR('2'))) { /* offset 2 for I32 */ + *format = *format + 2; /* add 2 to skip I32 point to '2' next loop will inc */ + } else if ((*(fmt + 1) == SECUREC_CHAR('d')) || + (*(fmt + 1) == SECUREC_CHAR('i')) || + (*(fmt + 1) == SECUREC_CHAR('o')) || + (*(fmt + 1) == SECUREC_CHAR('x')) || + (*(fmt + 1) == SECUREC_CHAR('X'))) { + spec->isInt64Arg = SecIs64BitPtr(sizeof(void *)); + } else { + /* for %I */ + spec->isInt64Arg = SecIs64BitPtr(sizeof(void *)); + *finishFlag = 1; + } +} + +static int SecDecodeScanWidth(const SecUnsignedChar **format, SecScanSpec *spec) +{ + const SecUnsignedChar *fmt = *format; + while (SECUREC_IS_DIGIT(*fmt)) { + spec->widthSet = 1; + if (SECUREC_MUL_TEN_ADD_BEYOND_MAX(spec->width)) { + return -1; + } + spec->width = (int)SECUREC_MUL_TEN((unsigned int)spec->width) + (unsigned char)(*fmt - SECUREC_CHAR('0')); + ++fmt; + } + *format = fmt; + return 0; +} + +/* + * init default flags for each format + */ +static void SecSetDefaultScanSpec(SecScanSpec *spec) +{ + spec->number64 = 0; + spec->number = 0; + spec->numberWidth = SECUREC_NUM_WIDTH_INT; /* 0 = SHORT, 1 = int, > 1 long or L_DOUBLE */ + spec->isInt64Arg = 0; /* 1 for 64-bit integer, 0 otherwise */ + spec->negative = 0; +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + spec->beyondMax = 0; +#endif + spec->argPtr = NULL; + spec->arrayWidth = 0; + spec->width = 0; + spec->widthSet = 0; + spec->comChr = 0; + spec->isWChar = 0; + spec->suppress = 0; +} + +/* + * decode qualifier %I %L %h ... + * set finishFlag to 1 finish Flag + */ +static void SecDecodeScanQualifier(const SecUnsignedChar **format, SecScanSpec *spec, int *finishFlag) +{ + switch ((int)(unsigned char)(**(format))) { + case SECUREC_CHAR('F'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('N'): + break; + case SECUREC_CHAR('h'): + --spec->numberWidth; /* h for SHORT , hh for CHAR */ + spec->isWChar = -1; + break; +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT + case SECUREC_CHAR('j'): + spec->numberWidth = SECUREC_NUM_WIDTH_LONG_LONG; /* intmax_t or uintmax_t */ + spec->isInt64Arg = 1; + break; + case SECUREC_CHAR('t'): /* fall-through */ /* FALLTHRU */ +#endif + case SECUREC_CHAR('z'): +#ifdef SECUREC_ON_64BITS + spec->numberWidth = SECUREC_NUM_WIDTH_LONG_LONG; + spec->isInt64Arg = 1; +#else + spec->numberWidth = SECUREC_NUM_WIDTH_LONG; +#endif + break; + case SECUREC_CHAR('L'): /* long double */ /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('q'): + spec->numberWidth = SECUREC_NUM_WIDTH_LONG_LONG; + spec->isInt64Arg = 1; + break; + case SECUREC_CHAR('l'): + SecDecodeScanQualifierL(format, spec); + break; + case SECUREC_CHAR('w'): + spec->isWChar = 1; + break; + case SECUREC_CHAR('*'): + spec->suppress = 1; + break; + case SECUREC_CHAR('I'): + SecDecodeScanQualifierI(format, spec, finishFlag); + break; + default: + *finishFlag = 1; + break; + } + +} +/* + * decode width and qualifier in format + */ +static int SecDecodeScanFlag(const SecUnsignedChar **format, SecScanSpec *spec) +{ + const SecUnsignedChar *fmt = *format; + int finishFlag = 0; + + do { + ++fmt; /* first skip % , next seek fmt */ + /* may %*6d , so put it inside the loop */ + if (SecDecodeScanWidth(&fmt, spec) != 0) { + return -1; + } + SecDecodeScanQualifier(&fmt, spec, &finishFlag); + } while (finishFlag == 0); + *format = fmt; + return 0; +} + + + + + +/* + * Judging whether a zeroing buffer is needed according to different formats + */ +static int SecDecodeClearFormat(const SecUnsignedChar *format, int *comChr) +{ + const SecUnsignedChar *fmt = format; + /* to lowercase */ + int ch = (unsigned char)(*fmt) | (SECUREC_CHAR('a') - SECUREC_CHAR('A')); + if (!(ch == SECUREC_CHAR('c') || ch == SECUREC_CHAR('s') || ch == SECUREC_BRACE)) { + return -1; /* first argument is not a string type */ + } + if (ch == SECUREC_BRACE) { +#if !(defined(SECUREC_COMPATIBLE_WIN_FORMAT)) + if (*fmt == SECUREC_CHAR('{')) { + return -1; + } +#endif + ++fmt; + if (*fmt == SECUREC_CHAR('^')) { + ++fmt; + } + if (*fmt == SECUREC_CHAR(']')) { + ++fmt; + } + while ((*fmt != SECUREC_CHAR('\0')) && (*fmt != SECUREC_CHAR(']'))) { + ++fmt; + } + if (*fmt == SECUREC_CHAR('\0')) { + return -1; /* trunc'd format string */ + } + } + *comChr = ch; + return 0; +} + +/* + * add L'\0' for wchar string , add '\0' for char string + */ +static void SecAddEndingZero(void *ptr, const SecScanSpec *spec) +{ + *(char *)ptr = '\0'; + (void)spec; /* clear not use */ +#if SECUREC_HAVE_WCHART + if (spec->isWChar > 0) { + *(wchar_t UNALIGNED *)ptr = L'\0'; + } +#endif +} + +#ifdef SECUREC_FOR_WCHAR +/* + * Clean up the first %s %c buffer to zero for wchar version + */ +void SecClearDestBufW(const wchar_t *buffer, const wchar_t *format, va_list argList) +#else +/* + * Clean up the first %s %c buffer to zero for char version + */ +void SecClearDestBuf(const char *buffer, const char *format, va_list argList) +#endif +{ + + va_list argListSave; /* backup for argList value, this variable don't need initialized */ + SecScanSpec spec; + int comChr = 0; + const SecUnsignedChar *fmt = (const SecUnsignedChar *)format; + if (fmt == NULL) { + return; + } + + /* find first % */ + while (*fmt != SECUREC_CHAR('\0') && *fmt != SECUREC_CHAR('%')) { + ++fmt; + } + if (*fmt == SECUREC_CHAR('\0')) { + return; + } + + SecSetDefaultScanSpec(&spec); + if (SecDecodeScanFlag(&fmt, &spec) != 0) { + return; + } + + /* update wchar flag for %S %C */ + SecUpdateWcharFlagByType(*fmt, &spec); + + if (spec.suppress != 0 || SecDecodeClearFormat(fmt, &comChr) != 0) { + return; + } + + if ((buffer != NULL) && (*buffer != SECUREC_CHAR('\0')) && (comChr != SECUREC_CHAR('s'))) { + /* when buffer not empty just clear %s. + * example call sscanf by argment of (" \n", "%s", s, sizeof(s)) + */ + return; + } + (void)memset(&argListSave, 0, sizeof(va_list)); /* to clear e530 argListSave not initialized */ +#if defined(va_copy) + va_copy(argListSave, argList); +#elif defined(__va_copy) /* for vxworks */ + __va_copy(argListSave, argList); +#else + argListSave = argList; +#endif + do { + void *argPtr = (void *)va_arg(argListSave, void *); + /* Get the next argument - size of the array in characters */ + size_t arrayWidth = ((size_t)(va_arg(argListSave, size_t))) & 0xFFFFFFFFUL; + va_end(argListSave); + /* to clear e438 last value assigned not used , the compiler will optimize this code */ + (void)argListSave; + /* There is no need to judge the upper limit */ + if (arrayWidth == 0 || argPtr == NULL) { + return; + } + + /* clear one char */ + SecAddEndingZero(argPtr, &spec); + } SECUREC_WHILE_ZERO; + return; + +} + +/* + * Assign number to output buffer + */ +static void SecAssignNumber(const SecScanSpec *spec) +{ + void *argPtr = spec->argPtr; + if (spec->isInt64Arg != 0) { +#if defined(SECUREC_VXWORKS_PLATFORM) +#if defined(SECUREC_VXWORKS_PLATFORM_COMP) + *(SecInt64 UNALIGNED *)argPtr = (SecInt64)(spec->number64); +#else + /* take number64 as unsigned number unsigned to int clear Compile warning */ + *(SecInt64 UNALIGNED *)argPtr = *(SecUnsignedInt64 *)(&(spec->number64)); +#endif +#else + /* take number64 as unsigned number */ + *(SecInt64 UNALIGNED *)argPtr = (SecInt64)(spec->number64); +#endif + return; + } + if (spec->numberWidth > SECUREC_NUM_WIDTH_INT) { + /* take number as unsigned number */ + *(long UNALIGNED *)argPtr = (long)(spec->number); + } else if (spec->numberWidth == SECUREC_NUM_WIDTH_INT) { + *(int UNALIGNED *)argPtr = (int)(spec->number); + } else if (spec->numberWidth == SECUREC_NUM_WIDTH_SHORT) { + /* take number as unsigned number */ + *(short UNALIGNED *)argPtr = (short)(spec->number); + } else { /* < 0 for hh format modifier */ + /* take number as unsigned number */ + *(char UNALIGNED *)argPtr = (char)(spec->number); + } +} + +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) +/* + * Judge the long bit width + */ +static int SecIsLongBitEqual(int bitNum) +{ + return (unsigned int)bitNum == SECUREC_LONG_BIT_NUM; +} +#endif +/* + * Convert hexadecimal characters to decimal value + */ +static int SecHexValueOfChar(SecInt ch) +{ + /* use isdigt Causing tool false alarms */ + return (int)((ch >= '0' && ch <= '9') ? ((unsigned char)ch - '0') : + ((((unsigned char)ch | (unsigned char)('a' - 'A')) - ('a')) + 10)); /* Adding 10 is to hex value */ +} + + + +/* + * Parse decimal character to integer for 32bit . + */ +static void SecDecodeNumberDecimal(SecInt ch, SecScanSpec *spec) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + unsigned long decimalEdge = SECUREC_MAX_32BITS_VALUE_DIV_TEN; +#ifdef SECUREC_ON_64BITS + if (SecIsLongBitEqual(SECUREC_LP64_BIT_WIDTH)) { + decimalEdge = (unsigned long)SECUREC_MAX_64BITS_VALUE_DIV_TEN; + } +#else + if (SecIsLongBitEqual(SECUREC_LP32_BIT_WIDTH)) { + decimalEdge = SECUREC_MAX_32BITS_VALUE_DIV_TEN; + } +#endif + if (spec->number > decimalEdge) { + spec->beyondMax = 1; + } +#endif + spec->number = SECUREC_MUL_TEN(spec->number); +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (spec->number == SECUREC_MUL_TEN(decimalEdge)) { + SecUnsignedInt64 number64As = (unsigned long)SECUREC_MAX_64BITS_VALUE - spec->number; + if (number64As < (SecUnsignedInt64)((SecUnsignedInt)ch - SECUREC_CHAR('0'))) { + spec->beyondMax = 1; + } + } +#endif + spec->number += (unsigned long)((SecUnsignedInt)ch - SECUREC_CHAR('0')); + +} + + +/* + * Parse Hex character to integer for 32bit . + */ +static void SecDecodeNumberHex(SecInt ch, SecScanSpec *spec) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (SECUREC_LONG_HEX_BEYOND_MAX(spec->number)) { + spec->beyondMax = 1; + } +#endif + spec->number = SECUREC_MUL_SIXTEEN(spec->number); + spec->number += (unsigned long)(unsigned int)SecHexValueOfChar(ch); +} + + +/* + * Parse Octal character to integer for 32bit . + */ +static void SecDecodeNumberOctal(SecInt ch, SecScanSpec *spec) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (SECUREC_LONG_OCTAL_BEYOND_MAX(spec->number)) { + spec->beyondMax = 1; + } +#endif + spec->number = SECUREC_MUL_EIGHT(spec->number); + spec->number += (unsigned long)((SecUnsignedInt)ch - SECUREC_CHAR('0')); +} + + +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) +/* Compatible with integer negative values other than int */ +static void SecFinishNumberNegativeOther(int comChr, int numberWidth, SecScanSpec *spec) +{ + if ((comChr == SECUREC_CHAR('d')) || (comChr == SECUREC_CHAR('i'))) { + if (spec->number > (unsigned long)(1ULL << (SECUREC_LONG_BIT_NUM - 1))) { + spec->number = (unsigned long)(1ULL << (SECUREC_LONG_BIT_NUM - 1)); + } else { + spec->number = (unsigned long)(-(long)spec->number); + } + if (spec->beyondMax != 0) { + if (numberWidth < SECUREC_NUM_WIDTH_INT) { + spec->number = 0; + } else if (numberWidth == SECUREC_NUM_WIDTH_LONG) { + spec->number = ((unsigned long)(1UL << (SECUREC_LONG_BIT_NUM - 1))); + } + } + } else { /* o, u, x, X, p */ + spec->number = (unsigned long)(-(long)spec->number); + if (spec->beyondMax != 0) { + spec->number |= (unsigned long)SECUREC_MAX_64BITS_VALUE; + } + } +} +/* Compatible processing of integer negative numbers */ +static void SecFinishNumberNegativeInt(int comChr, SecScanSpec *spec) +{ + if ((comChr == SECUREC_CHAR('d')) || (comChr == SECUREC_CHAR('i'))) { +#ifdef SECUREC_ON_64BITS + if (SecIsLongBitEqual(SECUREC_LP64_BIT_WIDTH)) { + if ((spec->number > SECUREC_MIN_64BITS_NEG_VALUE)) { + spec->number = 0; + } else { + spec->number = (unsigned int)(-(int)spec->number); + } + } +#else + if (SecIsLongBitEqual(SECUREC_LP32_BIT_WIDTH)) { + if ((spec->number > SECUREC_MIN_32BITS_NEG_VALUE)) { + spec->number = SECUREC_MIN_32BITS_NEG_VALUE; + } else { + spec->number = (unsigned int)(-(int)spec->number); + } + } +#endif + if (spec->beyondMax != 0) { +#ifdef SECUREC_ON_64BITS + if (SecIsLongBitEqual(SECUREC_LP64_BIT_WIDTH)) { + spec->number = 0; + } +#else + if (SecIsLongBitEqual(SECUREC_LP32_BIT_WIDTH)) { + spec->number = SECUREC_MIN_32BITS_NEG_VALUE; + } +#endif + } + } else { /* o, u, x, X ,p */ +#ifdef SECUREC_ON_64BITS + if (spec->number > SECUREC_MAX_32BITS_VALUE_INC) { + spec->number = SECUREC_MAX_32BITS_VALUE; + } else { + spec->number = (unsigned int)(-(int)spec->number); + } +#else + spec->number = (unsigned int)(-(int)spec->number); +#endif + if (spec->beyondMax != 0) { + spec->number |= (unsigned long)SECUREC_MAX_64BITS_VALUE; + } + } +} + +/* Compatible with integer positive values other than int */ +static void SecFinishNumberPositiveOther(int comChr, int numberWidth, SecScanSpec *spec) +{ + if (comChr == SECUREC_CHAR('d') || comChr == SECUREC_CHAR('i')) { + if (spec->number > ((unsigned long)(1UL << (SECUREC_LONG_BIT_NUM - 1)) - 1)) { + spec->number = ((unsigned long)(1UL << (SECUREC_LONG_BIT_NUM - 1)) - 1); + } + if ((spec->beyondMax != 0 && numberWidth < SECUREC_NUM_WIDTH_INT)) { + spec->number |= (unsigned long)SECUREC_MAX_64BITS_VALUE; + } + if (spec->beyondMax != 0 && numberWidth == SECUREC_NUM_WIDTH_LONG) { + spec->number = ((unsigned long)(1UL << (SECUREC_LONG_BIT_NUM - 1)) - 1); + } + } else { + if (spec->beyondMax != 0) { + spec->number |= (unsigned long)SECUREC_MAX_64BITS_VALUE; + } + } +} + +/* Compatible processing of integer positive numbers */ +static void SecFinishNumberPositiveInt(int comChr, SecScanSpec *spec) +{ + if ((comChr == SECUREC_CHAR('d')) || (comChr == SECUREC_CHAR('i'))) { +#ifdef SECUREC_ON_64BITS + if (SecIsLongBitEqual(SECUREC_LP64_BIT_WIDTH)) { + if (spec->number > SECUREC_MAX_64BITS_POS_VALUE) { + spec->number |= (unsigned long)SECUREC_MAX_64BITS_VALUE; + } + } + if (spec->beyondMax != 0 && SecIsLongBitEqual(SECUREC_LP64_BIT_WIDTH)) { + spec->number |= (unsigned long)SECUREC_MAX_64BITS_VALUE; + } +#else + if (SecIsLongBitEqual(SECUREC_LP32_BIT_WIDTH)) { + if (spec->number > SECUREC_MAX_32BITS_POS_VALUE) { + spec->number = SECUREC_MAX_32BITS_POS_VALUE; + } + } + if (spec->beyondMax != 0 && SecIsLongBitEqual(SECUREC_LP32_BIT_WIDTH)) { + spec->number = SECUREC_MAX_32BITS_POS_VALUE; + } +#endif + } else { /* o,u,x,X,p */ + if (spec->beyondMax != 0) { + spec->number = SECUREC_MAX_32BITS_VALUE; + } + } +} + +#endif + + +/* + * Parse decimal character to integer for 64bit . + */ +static void SecDecodeNumber64Decimal(SecInt ch, SecScanSpec *spec) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (spec->number64 > SECUREC_MAX_64BITS_VALUE_DIV_TEN) { + spec->beyondMax = 1; + } +#endif + spec->number64 = SECUREC_MUL_TEN(spec->number64); +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (spec->number64 == SECUREC_MAX_64BITS_VALUE_CUT_LAST_DIGIT) { + SecUnsignedInt64 number64As = (SecUnsignedInt64)SECUREC_MAX_64BITS_VALUE - spec->number64; + if (number64As < (SecUnsignedInt64)((SecUnsignedInt)ch - SECUREC_CHAR('0'))) { + spec->beyondMax = 1; + } + } +#endif + spec->number64 += (SecUnsignedInt64)((SecUnsignedInt)ch - SECUREC_CHAR('0')); +} + +/* + * Parse Hex character to integer for 64bit . + */ +static void SecDecodeNumber64Hex(SecInt ch, SecScanSpec *spec) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (SECUREC_QWORD_HEX_BEYOND_MAX(spec->number64)) { + spec->beyondMax = 1; + } +#endif + spec->number64 = SECUREC_MUL_SIXTEEN(spec->number64); + spec->number64 += (SecUnsignedInt64)(unsigned int)SecHexValueOfChar(ch); + +} + +/* + * Parse Octal character to integer for 64bit . + */ +static void SecDecodeNumber64Octal(SecInt ch, SecScanSpec *spec) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (SECUREC_QWORD_OCTAL_BEYOND_MAX(spec->number64)) { + spec->beyondMax = 1; + } +#endif + spec->number64 = SECUREC_MUL_EIGHT(spec->number64); + spec->number64 += (SecUnsignedInt64)((SecUnsignedInt)ch - SECUREC_CHAR('0')); +} + +#define SECUREC_DECODE_NUMBER_FUNC_NUM 2 +/* Function name cannot add address symbol, causing 546 alarm */ +static void (*g_secDecodeNumberHex[SECUREC_DECODE_NUMBER_FUNC_NUM])(SecInt ch, SecScanSpec *spec) = \ + { SecDecodeNumberHex, SecDecodeNumber64Hex }; +static void (*g_secDecodeNumberOctal[SECUREC_DECODE_NUMBER_FUNC_NUM])(SecInt ch, SecScanSpec *spec) = \ + { SecDecodeNumberOctal, SecDecodeNumber64Octal }; +static void (*g_secDecodeNumberDecimal[SECUREC_DECODE_NUMBER_FUNC_NUM])(SecInt ch, SecScanSpec *spec) = \ + { SecDecodeNumberDecimal, SecDecodeNumber64Decimal }; + +/* + * Parse 64-bit integer formatted input, return 0 when ch is a number. + */ +static int SecDecodeNumber(SecInt ch, SecScanSpec *spec) +{ + if (spec->comChr == SECUREC_CHAR('x') || spec->comChr == SECUREC_CHAR('p')) { + if (SECUREC_IS_XDIGIT(ch)) { + (*g_secDecodeNumberHex[spec->isInt64Arg])(ch, spec); + } else { + return -1; + } + return 0; + } + if (!(SECUREC_IS_DIGIT(ch))) { + return -1; + } + if (spec->comChr == SECUREC_CHAR('o')) { + if (ch < SECUREC_CHAR('8')) { + (*g_secDecodeNumberOctal[spec->isInt64Arg])(ch, spec); + } else { + return -1; + } + } else { /* comChr is 'd' */ + (*g_secDecodeNumberDecimal[spec->isInt64Arg])(ch, spec); + } + return 0; +} + + +/* + * Complete the final 32-bit integer formatted input + */ +static void SecFinishNumber(SecScanSpec *spec) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (spec->negative != 0) { + if (spec->numberWidth == SECUREC_NUM_WIDTH_INT) { + SecFinishNumberNegativeInt(spec->oriComChr, spec); + } else { + SecFinishNumberNegativeOther(spec->oriComChr, spec->numberWidth, spec); + } + } else { + if (spec->numberWidth == SECUREC_NUM_WIDTH_INT) { + SecFinishNumberPositiveInt(spec->oriComChr, spec); + } else { + SecFinishNumberPositiveOther(spec->oriComChr, spec->numberWidth, spec); + } + } +#else + if (spec->negative != 0) { +#if defined(__hpux) + if (spec->oriComChr != SECUREC_CHAR('p')) { + spec->number = (unsigned long)(-(long)spec->number); + } +#else + spec->number = (unsigned long)(-(long)spec->number); +#endif + } +#endif + return; +} + +/* + * Complete the final 64-bit integer formatted input + */ +static void SecFinishNumber64(SecScanSpec *spec) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && !(defined(SECUREC_ON_UNIX))) + if (spec->negative != 0) { + if (spec->oriComChr == (SECUREC_CHAR('d')) || (spec->oriComChr == SECUREC_CHAR('i'))) { + if (spec->number64 > SECUREC_MIN_64BITS_NEG_VALUE) { + spec->number64 = SECUREC_MIN_64BITS_NEG_VALUE; + } else { + spec->number64 = (SecUnsignedInt64)(-(SecInt64)spec->number64); + } + if (spec->beyondMax != 0) { + spec->number64 = SECUREC_MIN_64BITS_NEG_VALUE; + } + } else { /* o, u, x, X, p */ + spec->number64 = (SecUnsignedInt64)(-(SecInt64)spec->number64); + if (spec->beyondMax != 0) { + spec->number64 = SECUREC_MAX_64BITS_VALUE; + } + } + } else { + if ((spec->oriComChr == SECUREC_CHAR('d')) || (spec->oriComChr == SECUREC_CHAR('i'))) { + if (spec->number64 > SECUREC_MAX_64BITS_POS_VALUE) { + spec->number64 = SECUREC_MAX_64BITS_POS_VALUE; + } + if (spec->beyondMax != 0) { + spec->number64 = SECUREC_MAX_64BITS_POS_VALUE; + } + } else { + if (spec->beyondMax != 0) { + spec->number64 = SECUREC_MAX_64BITS_VALUE; + } + } + } +#else + if (spec->negative != 0) { +#if defined(__hpux) + if (spec->oriComChr != SECUREC_CHAR('p')) { + spec->number64 = (SecUnsignedInt64)(-(SecInt64)spec->number64); + } +#else + spec->number64 = (SecUnsignedInt64)(-(SecInt64)spec->number64); +#endif + } +#endif + return; +} +static void (*g_secFinishNumber[SECUREC_DECODE_NUMBER_FUNC_NUM])(SecScanSpec *spec) = \ + { SecFinishNumber, SecFinishNumber64 }; + +#if SECUREC_ENABLE_SCANF_FILE + +/* + * Adjust the pointer position of the file stream + */ +static void SecSeekStream(SecFileStream *stream) +{ + if ((stream->count == 0) && feof(stream->pf)) { + /* file pointer at the end of file, don't need to seek back */ + stream->base[0] = '\0'; + return; + } + /* LSD seek to original position, bug fix 2014 1 21 */ + if (fseek(stream->pf, stream->oriFilePos, SEEK_SET)) { + /* seek failed, ignore it */ + stream->oriFilePos = 0; + return; + } + + if (stream->fileRealRead > 0) { /* LSD bug fix. when file reach to EOF, don't seek back */ +#if (defined(SECUREC_COMPATIBLE_WIN_FORMAT)) + int loops; + for (loops = 0; loops < (stream->fileRealRead / SECUREC_BUFFERED_BLOK_SIZE); ++loops) { + if (fread(stream->base, (size_t)1, (size_t)SECUREC_BUFFERED_BLOK_SIZE, + stream->pf) != SECUREC_BUFFERED_BLOK_SIZE) { + break; + } + } + if ((stream->fileRealRead % SECUREC_BUFFERED_BLOK_SIZE) != 0) { + size_t ret = fread(stream->base, (size_t)((unsigned int)stream->fileRealRead % SECUREC_BUFFERED_BLOK_SIZE), + (size_t)1, stream->pf); + if ((ret == 1 || ret == 0) && (ftell(stream->pf) < stream->oriFilePos + stream->fileRealRead)) { + (void)fseek(stream->pf, stream->oriFilePos + stream->fileRealRead, SEEK_SET); + } + } + +#else + /* in linux like system */ + if (fseek(stream->pf, stream->oriFilePos + stream->fileRealRead, SEEK_SET)) { + /* seek failed, ignore it */ + stream->oriFilePos = 0; + } +#endif + } + + return; +} + +/* + * Adjust the pointer position of the file stream and free memory + */ +static void SecAdjustStream(SecFileStream *stream) +{ + if (stream != NULL && (stream->flag & SECUREC_FILE_STREAM_FLAG) && stream->base != NULL) { + SecSeekStream(stream); + SECUREC_FREE(stream->base); + stream->base = NULL; + } + return; +} +#endif + +static void SecSkipSpaceFormat(const SecUnsignedChar **format) +{ + const SecUnsignedChar *fmt = *format; + while (SECUREC_IS_SPACE(*fmt)) { + ++fmt; + } + *format = fmt; +} +#ifndef SECUREC_FOR_WCHAR +/* + * Handling multi-character characters + */ +static int SecDecodeLeadByte(SecInt ch, const SecUnsignedChar **format, SecFileStream *stream, int *counter) +{ +#if SECUREC_HAVE_MBTOWC + char temp[SECUREC_MULTI_BYTE_MAX_LEN]; + const SecUnsignedChar *fmt = *format; + wchar_t tempWChar = L'\0'; + int ch2 = SecGetChar(stream, counter); + if (*fmt == SECUREC_CHAR('\0') || (int)(*fmt) != (ch2)) { + /* LSD in console mode, ungetc twice may cause problem */ + SecUnGetChar(ch2, stream, counter); + SecUnGetChar(ch, stream, counter); + return -1; + } + ++fmt; + if (MB_CUR_MAX >= SECUREC_UTF8_BOM_HEADER_SIZE && + (((unsigned char)ch & SECUREC_UTF8_LEAD_1ST) == SECUREC_UTF8_LEAD_1ST) && + (((unsigned char)ch2 & SECUREC_UTF8_LEAD_2ND) == SECUREC_UTF8_LEAD_2ND)) { + /* this char is very likely to be a UTF-8 char */ + int ch3 = SecGetChar(stream, counter); + temp[0] = (char)ch; + temp[1] = (char)ch2; /* 1 index of second character */ + temp[2] = (char)ch3; /* 2 index of third character */ + temp[3] = '\0'; /* 3 of string terminator position */ + + if (mbtowc(&tempWChar, temp, sizeof(temp)) > 0) { + /* succeed */ + if (*fmt == SECUREC_CHAR('\0') || (int)(*fmt) != (int)ch3) { + SecUnGetChar(ch3, stream, counter); + return -1; + } + ++fmt; + *counter = *counter - 1; + } else { + SecUnGetChar(ch3, stream, counter); + } + } + *counter = *counter - 1; /* only count as one character read */ + *format = fmt; + return 0; +#else + SecUnGetChar(ch, stream, counter); + (void)format; + return -1; +#endif +} +#endif + + + +/* + * Resolving sequence of characters from %[ format + */ +static int SecSetupBracketTable(const SecUnsignedChar **format, SecBracketTable *bracketTable) +{ + const SecUnsignedChar *fmt = *format; + SecUnsignedChar prevChar = 0; + SecUnsignedChar expCh; + SecUnsignedChar last = 0; +#if !(defined(SECUREC_COMPATIBLE_WIN_FORMAT)) + if (*fmt == SECUREC_CHAR('{')) { + return -1; + } +#endif + /* for building "table" data */ + ++fmt; /* skip [ */ + bracketTable->mask = 0; + if (*fmt == SECUREC_CHAR('^')) { + ++fmt; + bracketTable->mask = (unsigned char)0xff; + } + if (*fmt == SECUREC_CHAR(']')) { + prevChar = SECUREC_CHAR(']'); + ++fmt; + SECUREC_BRACKET_SET_BIT(bracketTable->table, SECUREC_CHAR(']')); + } + while (*fmt != SECUREC_CHAR('\0') && *fmt != SECUREC_CHAR(']')) { + expCh = *fmt++; + if (expCh != SECUREC_CHAR('-') || prevChar == 0 || *fmt == SECUREC_CHAR(']')) { + /* normal character */ + prevChar = expCh; + SECUREC_BRACKET_SET_BIT(bracketTable->table, expCh); + } else { + /* for %[a-z] */ + expCh = *fmt++; /* get end of range */ + if (prevChar < expCh) { /* %[a-z] */ + last = expCh; + } else { + prevChar = expCh; +#if (defined(SECUREC_COMPATIBLE_WIN_FORMAT)) + /* %[z-a] */ + last = prevChar; + +#else + SECUREC_BRACKET_SET_BIT(bracketTable->table, SECUREC_CHAR('-')); + SECUREC_BRACKET_SET_BIT(bracketTable->table, expCh); + continue; +#endif + } + /* format %[a-\xff] last is 0xFF, condition (rnch <= last) cause dead loop */ + for (expCh = prevChar; expCh < last; ++expCh) { + SECUREC_BRACKET_SET_BIT(bracketTable->table, expCh); + } + SECUREC_BRACKET_SET_BIT(bracketTable->table, last); + prevChar = 0; + } + } + *format = fmt; + return 0; +} + + +#ifdef SECUREC_FOR_WCHAR +static int SecInputForWchar(SecInt ch, SecScanSpec *spec) +{ + void *endPtr = spec->argPtr; + if (spec->isWChar > 0) { + *(wchar_t UNALIGNED *)endPtr = (wchar_t)ch; + endPtr = (wchar_t *)endPtr + 1; + --spec->arrayWidth; + } else { +#if SECUREC_HAVE_WCTOMB + int temp; + char tmpBuf[SECUREC_MB_LEN + 1]; + SECUREC_MASK_MSVC_CRT_WARNING temp = wctomb(tmpBuf, (wchar_t)ch); + SECUREC_END_MASK_MSVC_CRT_WARNING + if (temp <= 0 || ((size_t)(unsigned int)temp) > sizeof(tmpBuf)) { + /* if wctomb error, then ignore character */ + return 0; + } + if (((size_t)(unsigned int)temp) > spec->arrayWidth) { + return -1; + } + if (memcpy_s(endPtr, spec->arrayWidth, tmpBuf, (size_t)(unsigned int)temp) != EOK) { + return -1; + } + endPtr = (char *)endPtr + temp; + spec->arrayWidth -= (size_t)(unsigned int)temp; +#else + return -1; +#endif + } + spec->argPtr = endPtr; + return 0; +} +#endif + + +#ifndef SECUREC_FOR_WCHAR +static int SecInputForChar(SecInt ch, SecScanSpec *spec, SecFileStream *stream, int *charCount) +{ + void *endPtr = spec->argPtr; + if (spec->isWChar > 0) { + wchar_t tempWChar = L'?'; /* set default char as ? */ +#if SECUREC_HAVE_MBTOWC + char temp[SECUREC_MULTI_BYTE_MAX_LEN + 1]; + temp[0] = (char)ch; + temp[1] = '\0'; +#if defined(SECUREC_COMPATIBLE_WIN_FORMAT) + if (SecIsLeadByte(ch)) { + temp[1] = (char)SecGetChar(stream, charCount); + temp[2] = '\0'; /* 2 of string terminator position */ + } + if (mbtowc(&tempWChar, temp, sizeof(temp)) <= 0) { + /* no string termination error for tool */ + tempWChar = L'?'; + } +#else + if (SecIsLeadByte(ch)) { + int convRes = 0; + int di = 1; + /* in Linux like system, the string is encoded in UTF-8 */ + while (convRes <= 0 && di < (int)MB_CUR_MAX && di < SECUREC_MULTI_BYTE_MAX_LEN) { + temp[di++] = (char)SecGetChar(stream, charCount); + temp[di] = '\0'; + convRes = mbtowc(&tempWChar, temp, sizeof(temp)); + } + if (convRes <= 0) { + tempWChar = L'?'; + } + } else { + if (mbtowc(&tempWChar, temp, sizeof(temp)) <= 0) { + /* no string termination error for tool */ + tempWChar = L'?'; + } + } +#endif +#endif /* SECUREC_HAVE_MBTOWC */ + *(wchar_t UNALIGNED *)endPtr = tempWChar; + /* just copy L'?' if mbtowc fails, errno is set by mbtowc */ + endPtr = (wchar_t *)endPtr + 1; + --spec->arrayWidth; + (void)charCount; + (void)stream; + } else { + *(char *)endPtr = (char)ch; + endPtr = (char *)endPtr + 1; + --spec->arrayWidth; + } + spec->argPtr = endPtr; + return 0; +} +#endif + + +#if SECUREC_ENABLE_SCANF_FLOAT + +/* no not use localeconv()->decimal_pointif onlay support '.' */ +#define SECURE_IS_FLOAT_DECIMAL(ch) ((ch) == SECUREC_CHAR('.')) +/* + * init SecFloatSpec befor parse format + */ +static void SecInitFloatSpec(SecFloatSpec *floatSpec) +{ + floatSpec->floatStr = floatSpec->buffer; + floatSpec->allocatedFloatStr = NULL; + floatSpec->floatStrSize = sizeof(floatSpec->buffer) / sizeof(floatSpec->buffer[0]); + floatSpec->floatStr = floatSpec->buffer; + floatSpec->floatStrUsedLen = 0; +} + +static void SecClearFloatSpec(SecFloatSpec *floatSpec, int *doneCount) +{ + /* LSD 2014.3.6 add, clear the stack data */ + if (memset_s(floatSpec->buffer, sizeof(floatSpec->buffer), 0, + sizeof(floatSpec->buffer)) != EOK) { + *doneCount = 0; /* This is a dead code, just to meet the coding requirements */ + } + if (floatSpec->allocatedFloatStr != NULL) { + /* pFloatStr can be alloced in SecUpdateFloatString function, clear and free it */ + if (memset_s(floatSpec->allocatedFloatStr, floatSpec->floatStrSize * sizeof(SecChar), 0, + floatSpec->floatStrSize * sizeof(SecChar)) != EOK) { + *doneCount = 0; /* This is a dead code, just to meet the coding requirements */ + } + SECUREC_FREE(floatSpec->allocatedFloatStr); + floatSpec->allocatedFloatStr = NULL; + floatSpec->floatStr = NULL; + } +} + + +/* + * scan value of exponent. + * return 0 OK + */ +static int SecInputFloatE(SecFileStream *stream, SecScanSpec *spec, SecFloatSpec *floatSpec, int *charCount) +{ + SecInt ch = SecGetChar(stream, charCount); + if (ch == SECUREC_CHAR('+') || ch == SECUREC_CHAR('-')) { + if (ch == SECUREC_CHAR('-') && SecUpdateFloatString((SecChar)'-', floatSpec) != 0) { + return -1; + } + if (spec->width != 0) { + ch = SecGetChar(stream, charCount); + --spec->width; + } + } + + while (SECUREC_IS_DIGIT(ch) && spec->width-- != 0) { + if (SecUpdateFloatString((SecChar)ch, floatSpec) != 0) { + return -1; + } + ch = SecGetChar(stream, charCount); + } + return 0; +} + +/* + * scan %f. + * return 0 OK + */ +static int SecInputFloat(SecFileStream *stream, SecScanSpec *spec, SecFloatSpec *floatSpec, int *charCount) +{ + int started = -1; + SecInt ch = SecGetChar(stream, charCount); + + floatSpec->floatStrUsedLen = 0; + if (ch == SECUREC_CHAR('-')) { + floatSpec->floatStr[floatSpec->floatStrUsedLen++] = SECUREC_CHAR('-'); + --spec->width; + ch = SecGetChar(stream, charCount); + } else if (ch == SECUREC_CHAR('+')) { + --spec->width; + ch = SecGetChar(stream, charCount); + } + + if (spec->widthSet == 0) { /* must care width */ + spec->width = -1; /* -1 is unlimited */ + } + + /* now get integral part */ + while (SECUREC_IS_DIGIT(ch) && spec->width-- != 0) { + started = 0; + /* ch must be '0' - '9' */ + if (SecUpdateFloatString((SecChar)ch, floatSpec) != 0) { + return -1; + } + ch = SecGetChar(stream, charCount); + } + + /* now get fractional part */ + if (SECURE_IS_FLOAT_DECIMAL((SecChar)ch) && spec->width-- != 0) { + /* now check for decimal */ + if (SecUpdateFloatString((SecChar)ch, floatSpec) != 0) { + return -1; + } + ch = SecGetChar(stream, charCount); + while (SECUREC_IS_DIGIT(ch) && spec->width-- != 0) { + started = 0; + if (SecUpdateFloatString((SecChar)ch, floatSpec) != 0) { + return -1; + } + ch = SecGetChar(stream, charCount); + } + } + + /* now get exponent part */ + if (started == 0 && (ch == SECUREC_CHAR('e') || ch == SECUREC_CHAR('E')) && spec->width-- != 0) { + if (SecUpdateFloatString((SecChar)'e', floatSpec) != 0) { + return -1; + } + if (SecInputFloatE(stream, spec, floatSpec, charCount) != 0) { + return -1; + } + } + /* un set the last character that is not a floating point number */ + SecUnGetChar(ch, stream, charCount); + /* Make sure have a string terminator, buffer is large enough */ + floatSpec->floatStr[floatSpec->floatStrUsedLen] = SECUREC_CHAR('\0'); + return started; + +} +#endif + +/* + * scan digital part of %d %i %o %u %x %p. + * return 0 OK + */ +static int SecInputNumberDigital(SecInt firstCh, SecFileStream *stream, SecScanSpec *spec, int *charCount) +{ + SecInt ch = firstCh; + int loopFlag = 0; + int started = -1; + while (loopFlag == 0) { + /* decode ch to number */ + loopFlag = SecDecodeNumber(ch, spec); + if (loopFlag == 0) { + started = 0; + if (spec->widthSet != 0 && --spec->width == 0) { + loopFlag = 1; + } else { + ch = SecGetChar(stream, charCount); + } + } else { + SecUnGetChar(ch, stream, charCount); + } + } + + /* Handling integer negative numbers and beyond max */ + (*g_secFinishNumber[spec->isInt64Arg])(spec); + return started; + +} + +/* + * scan %d %i %o %u %x %p. + * return 0 OK + */ +static int SecInputNumber(SecFileStream *stream, SecScanSpec *spec, int *charCount) +{ + SecInt ch = SecGetChar(stream, charCount); + + if (ch == SECUREC_CHAR('+') || ch == SECUREC_CHAR('-')) { + if (ch == SECUREC_CHAR('-')) { + spec->negative = 1; + } + if (spec->widthSet != 0 && --spec->width == 0) { + return -1; + } else { + ch = SecGetChar(stream, charCount); + } + } + + if (spec->oriComChr == SECUREC_CHAR('i')) { + /* i could be d, o, or x, use d as default */ + spec->comChr = SECUREC_CHAR('d'); + } + + if (spec->oriComChr == SECUREC_CHAR('x') || spec->oriComChr == SECUREC_CHAR('i')) { + if (ch != SECUREC_CHAR('0')) { + /* scan number */ + return SecInputNumberDigital(ch, stream, spec, charCount); + } + /* now input string may be 0x123 or 0X123 or just 0 */ + /* get next char */ + ch = SecGetChar(stream, charCount); + if ((SecChar)(ch) == SECUREC_CHAR('x') || (SecChar)ch == SECUREC_CHAR('X')) { + spec->comChr = SECUREC_CHAR('x'); + ch = SecGetChar(stream, charCount); + /* length of 0x is 2 */ + if (spec->widthSet != 0 && spec->width <= (1 + 1)) { + /* length not enough for "0x" */ + return -1; + } + spec->width -= 2; /* Subtract 2 for the length of "0x" */ + } else { + if (spec->oriComChr != SECUREC_CHAR('x')) { + spec->comChr = SECUREC_CHAR('o'); + } + /* unset the character after 0 back to stream, input only '0' result is OK */ + SecUnGetChar(ch, stream, charCount); + ch = SECUREC_CHAR('0'); + } + } + /* scan number */ + return SecInputNumberDigital(ch, stream, spec, charCount); +} +/* + * scan %c %s %[ + * return 0 OK + */ +static int SecInputString(SecFileStream *stream, SecScanSpec *spec, + const SecBracketTable *bracketTable, int *charCount, int *doneCount) +{ + void *startPtr = spec->argPtr; + int suppressed= 0; + int errNoMem = 0; + + while (spec->widthSet == 0 || spec->width-- != 0) { + SecInt ch = SecGetChar(stream, charCount); + /* char condition or string condition and bracket condition. + * only supports wide characters with a maximum length of two bytes + */ + if ((ch != SECUREC_EOF) && (spec->comChr == SECUREC_CHAR('c') || + SECUREC_SCANF_STRING_CONDITION(spec->comChr, ch) || + SECUREC_SCANF_BRACKET_CONDITION(spec->comChr, ch, bracketTable->table, bracketTable->mask))) { + if (spec->suppress != 0) { + /* Used to identify processed data for %* + * use endPtr to identify will cause 613, so use suppressed + */ + suppressed = 1; + continue; + } + /* now suppress is not set */ + if (spec->arrayWidth == 0) { + errNoMem = 1; /* We have exhausted the user's buffer */ + break; + } +#ifdef SECUREC_FOR_WCHAR + errNoMem = SecInputForWchar(ch, spec); +#else + errNoMem = SecInputForChar(ch, spec, stream, charCount); +#endif + if (errNoMem != 0) { + break; + } + } else { + SecUnGetChar(ch, stream, charCount); + break; + } + } + + if (errNoMem != 0) { + /* In case of error, blank out the input buffer */ + if (spec->suppress == 0) { + SecAddEndingZero(startPtr, spec); + } + return -1; + } + + /* No input was scanned */ + if ((spec->suppress != 0 && suppressed == 0) || + (spec->suppress == 0 && startPtr == spec->argPtr)) { + return -1; + } + + if (spec->suppress == 0) { + if (spec->comChr != 'c') { + /* null-terminate strings */ + SecAddEndingZero(spec->argPtr, spec); + } + *doneCount = *doneCount + 1; + } + return 0; +} + +#ifdef SECUREC_FOR_WCHAR +/* + * alloce buffer for wchar version of %[. + * return 0 OK + */ +static int SecAllocBracketTable(SecBracketTable *bracketTable) +{ + if (bracketTable->table == NULL) { + /* table should be freed after use */ + bracketTable->table = (unsigned char *)SECUREC_MALLOC(SECUREC_BRACKET_TABLE_SIZE); + if (bracketTable->table == NULL) { + return -1; + } + } + return 0; +} + +/* + * free buffer for wchar version of %[ + */ +static void SecFreeBracketTable(SecBracketTable *bracketTable) +{ + if (bracketTable->table != NULL) { + SECUREC_FREE(bracketTable->table); + bracketTable->table = NULL; + } +} +#endif + +#ifdef SECUREC_FOR_WCHAR +/* + * Formatting input core functions for wchar version.Called by a function such as vsscanf_s + */ +int SecInputSW(SecFileStream *stream, const wchar_t *cFormat, va_list argList) +#else +/* + * Formatting input core functions for char version.Called by a function such as vswscanf_s + */ +int SecInputS(SecFileStream *stream, const char *cFormat, va_list argList) +#endif +{ + const SecUnsignedChar *format = (const SecUnsignedChar *)cFormat; + SecBracketTable bracketTable = SECUREC_INIT_BRACKET_TABLE; + SecScanSpec spec; + SecInt ch = 0; + int charCount = 0; + int doneCount = 0; + int formatError = 0; + int paraIsNull = 0; +#if SECUREC_ENABLE_SCANF_FLOAT + SecFloatSpec floatSpec; +#endif + int match = 0; + int errRet = 0; +#if SECUREC_ENABLE_SCANF_FLOAT + SecInitFloatSpec(&floatSpec); +#endif + /* format must not NULL */ + /* use err < 1 to claer 845 */ + while (errRet < 1 && *format != SECUREC_CHAR('\0')) { + /* skip space in format and space in input */ + if (SECUREC_IS_SPACE(*format)) { + SecInt nonSpaceChar = SecSkipSpaceChar(stream, &charCount); + /* eat all space chars and put fist no space char backup */ + SecUnGetChar(nonSpaceChar, stream, &charCount); + SecSkipSpaceFormat(&format); + continue; + } + + if (*format != SECUREC_CHAR('%')) { + ch = SecGetChar(stream, &charCount); + if ((int)(*format++) != (int)(ch)) { + SecUnGetChar(ch, stream, &charCount); + ++errRet; /* use plus to clear 845 */ + continue; + } +#ifndef SECUREC_FOR_WCHAR + if (SecIsLeadByte(ch) && SecDecodeLeadByte(ch, &format, stream, &charCount) != 0) { + ++errRet; + continue; + } +#endif + /* for next %n */ + if ((ch == SECUREC_EOF) && ((*format != SECUREC_CHAR('%')) || (*(format + 1) != SECUREC_CHAR('n')))) { + break; + } + continue; + } + + /* now *format is % */ + /* set default value for each % */ + SecSetDefaultScanSpec(&spec); + if (SecDecodeScanFlag(&format, &spec) != 0) { + formatError = 1; + ++errRet; + continue; + } + /* update wchar flag for %S %C */ + SecUpdateWcharFlagByType(*format, &spec); + +#if SECUREC_HAVE_WCHART == 0 + /* in kernel not support wide char */ + if (spec.isWChar > 0) { + formatError = 1; + ++errRet; + continue; + } +#endif + if (spec.widthSet != 0 && spec.width == 0) { + /* 0 width in format */ + ++errRet; + continue; + } + + spec.comChr = (unsigned char)(*format) | (SECUREC_CHAR('a') - SECUREC_CHAR('A')); /* to lowercase */ + spec.oriComChr = spec.comChr; + + if (spec.comChr != SECUREC_CHAR('n')) { + if (spec.comChr != SECUREC_CHAR('c') && spec.comChr != SECUREC_BRACE) { + ch = SecSkipSpaceChar(stream, &charCount); + } else { + ch = SecGetChar(stream, &charCount); + } + if (ch == SECUREC_EOF) { + ++errRet; + continue; + } + } + + /* now no 0 width in format and get one char from input */ + switch (spec.comChr) { + case SECUREC_CHAR('c'): /* also 'C' */ + /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('s'): /* also 'S': */ + /* fall-through */ /* FALLTHRU */ + case SECUREC_BRACE: + /* check dest buffer and size */ + if (spec.suppress == 0) { + spec.argPtr = (void *)va_arg(argList, void *); + if (spec.argPtr == NULL) { + paraIsNull = 1; + ++errRet; + continue; + } + /* Get the next argument - size of the array in characters */ +#ifdef SECUREC_ON_64BITS + spec.arrayWidth = ((size_t)(va_arg(argList, size_t))) & 0xFFFFFFFFUL; +#else /* !SECUREC_ON_64BITS */ + spec.arrayWidth = (size_t)va_arg(argList, size_t); +#endif + if (spec.arrayWidth == 0 || (spec.isWChar <= 0 && spec.arrayWidth > SECUREC_STRING_MAX_LEN) || + (spec.isWChar > 0 && spec.arrayWidth > SECUREC_WCHAR_STRING_MAX_LEN)) { + /* do not clear buffer just go error */ + ++errRet; + continue; + } + /* One element is needed for '\0' for %s and %[ */ + if (spec.comChr != SECUREC_CHAR('c')) { + --spec.arrayWidth; + } + } else { + /* Set argPtr to NULL is necessary, in supress mode we don't use argPtr to store data */ + spec.argPtr = NULL; + } + + if (spec.comChr == 'c') { + if (spec.widthSet == 0) { + spec.widthSet = 1; + spec.width = 1; + } + } else if (spec.comChr == SECUREC_BRACE) { + /* malloc when first %[ is meet for wchar version */ +#ifdef SECUREC_FOR_WCHAR + if (SecAllocBracketTable(&bracketTable) != 0) { + ++errRet; + continue; + } + +#endif + (void)memset(bracketTable.table, 0, (size_t)SECUREC_BRACKET_TABLE_SIZE); + if (SecSetupBracketTable(&format, &bracketTable) != 0) { + ++errRet; + continue; + } + + if (*format == SECUREC_CHAR('\0')) { + if (spec.suppress == 0 && spec.arrayWidth > 0) { + SecAddEndingZero(spec.argPtr, &spec); + } + ++errRet; + /* truncated format */ + continue; + } + + } + /* un set last char to stream */ + SecUnGetChar(ch, stream, &charCount); + /* scanset completed. Now read string */ + if (SecInputString(stream, &spec, &bracketTable, &charCount, &doneCount) != 0) { + ++errRet; + continue; + } + break; + case SECUREC_CHAR('p'): + /* make %hp same as %p */ + spec.numberWidth = SECUREC_NUM_WIDTH_INT; +#ifdef SECUREC_ON_64BITS + spec.isInt64Arg = 1; +#endif + /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('o'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('u'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('d'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('i'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('x'): + /* un set last char to stream */ + SecUnGetChar(ch, stream, &charCount); + if (SecInputNumber(stream, &spec, &charCount) != 0) { + ++errRet; + continue; + } + if (spec.suppress == 0) { + spec.argPtr = (void *)va_arg(argList, void *); + if (spec.argPtr == NULL) { + paraIsNull = 1; + ++errRet; + continue; + } + SecAssignNumber(&spec); + ++doneCount; + } + break; + case SECUREC_CHAR('n'): /* char count */ + if (spec.suppress == 0) { + spec.argPtr = (void *)va_arg(argList, void *); + if (spec.argPtr == NULL) { + paraIsNull = 1; + ++errRet; + continue; + } + spec.number = (unsigned long)(unsigned int)charCount; + spec.isInt64Arg = 0; + SecAssignNumber(&spec); + } + break; + case SECUREC_CHAR('e'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('f'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('g'): /* scan a float */ +#if SECUREC_ENABLE_SCANF_FLOAT + /* un set last char to stream */ + SecUnGetChar(ch, stream, &charCount); + if (SecInputFloat(stream, &spec, &floatSpec, &charCount) != 0) { + ++errRet; + continue; + } + if (spec.suppress == 0) { + spec.argPtr = (void *)va_arg(argList, void *); + if (spec.argPtr == NULL) { + ++errRet; + paraIsNull = 1; + continue; + } +#ifdef SECUREC_FOR_WCHAR + if (SecAssignFloatW(&floatSpec, &spec) != 0) { + ++errRet; + continue; + } +#else + SecAssignFloat(floatSpec.floatStr, spec.numberWidth, spec.argPtr); +#endif + ++doneCount; + } + + break; +#else /* SECUREC_ENABLE_SCANF_FLOAT */ + ++errRet; + continue; +#endif + default: + if ((int)(*format) != (int)ch) { + SecUnGetChar(ch, stream, &charCount); + formatError = 1; + ++errRet; + continue; + } else { + --match; + } + } + + ++match; + ++format; + if ((ch == SECUREC_EOF) && ((*format != SECUREC_CHAR('%')) || (*(format + 1) != SECUREC_CHAR('n')))) { + break; + } + } + +#ifdef SECUREC_FOR_WCHAR + SecFreeBracketTable(&bracketTable); +#endif + +#if SECUREC_ENABLE_SCANF_FLOAT + SecClearFloatSpec(&floatSpec, &doneCount); +#endif + +#if SECUREC_ENABLE_SCANF_FILE + SecAdjustStream(stream); +#endif + + if (ch == SECUREC_EOF) { + return ((doneCount || match) ? doneCount : SECUREC_SCANF_EINVAL); + } else if (formatError != 0 || paraIsNull != 0) { + /* Invalid Input Format or parameter */ + return SECUREC_SCANF_ERROR_PARA; + } + + return doneCount; +} + +#if SECUREC_ENABLE_SCANF_FILE + +#if defined(SECUREC_NO_STD_UNGETC) +/* + * Get char from stdin or buffer + */ +static SecInt SecGetCharFromStdin(SecFileStream *stream) +{ + SecInt ch; + if (stream->fUnget == 1) { + ch = (SecInt) stream->lastChar; + stream->fUnget = 0; + } else { + ch = SECUREC_GETC(stream->pf); + stream->lastChar = (unsigned int)ch; + } + return ch; +} +#else +/* + * Get char from stdin or buffer use std function + */ +static SecInt SecGetCharFromStdin(const SecFileStream *stream) +{ + SecInt ch; + ch = SECUREC_GETC(stream->pf); + return ch; +} +#endif + +static void SecSkipBomHeader(SecFileStream *stream) +{ +#ifdef SECUREC_FOR_WCHAR + if (stream->count >= SECUREC_BOM_HEADER_SIZE && + (((unsigned char)(stream->base[0]) == SECUREC_BOM_HEADER_LE_1ST && + (unsigned char)(stream->base[1]) == SECUREC_BOM_HEADER_LE_2ST) || + ((unsigned char)(stream->base[0]) == SECUREC_BOM_HEADER_BE_1ST && + (unsigned char)(stream->base[1]) == SECUREC_BOM_HEADER_BE_2ST))) { + + /* the stream->count must be a multiple of sizeof(SecChar), + * otherwise this function will return SECUREC_EOF when read the last character + */ + if ((stream->count - SECUREC_BOM_HEADER_SIZE) % (int)sizeof(SecChar) != 0) { + int ret = (int)fread(stream->base + stream->count, (size_t)1, + (size_t)SECUREC_BOM_HEADER_SIZE, stream->pf); + if (ret > 0 && ret <= SECUREC_BUFFERED_BLOK_SIZE) { + stream->count += ret; + } + } + /* it's BOM header, skip */ + stream->count -= SECUREC_BOM_HEADER_SIZE; + stream->cur += SECUREC_BOM_HEADER_SIZE; + } +#else + if (stream->count >= SECUREC_UTF8_BOM_HEADER_SIZE && + (unsigned char)(stream->base[0]) == SECUREC_UTF8_BOM_HEADER_1ST && + (unsigned char)(stream->base[1]) == SECUREC_UTF8_BOM_HEADER_2ND && + (unsigned char)(stream->base[2]) == SECUREC_UTF8_BOM_HEADER_3RD) { /* 2 offset of third head character */ + /* it's BOM header, skip */ + stream->count -= SECUREC_UTF8_BOM_HEADER_SIZE; + stream->cur += SECUREC_UTF8_BOM_HEADER_SIZE; + } +#endif +} +/* + * Get char from file stream or buffer + */ +static SecInt SecGetCharFromFile(SecFileStream *stream) +{ + SecInt ch; + if (stream->count == 0) { + int firstReadOnFile = 0; + /* load file to buffer */ + if (stream->base == NULL) { + stream->base = (char *)SECUREC_MALLOC(SECUREC_BUFFERED_BLOK_SIZE + 1); + if (stream->base == NULL) { + return SECUREC_EOF; + } + stream->base[SECUREC_BUFFERED_BLOK_SIZE] = '\0'; /* for tool Warning string null */ + } + /* LSD add 2014.3.21 */ + if (stream->oriFilePos == SECUREC_UNINITIALIZED_FILE_POS) { + stream->oriFilePos = ftell(stream->pf); /* save original file read position */ + firstReadOnFile = 1; + } + stream->count = (int)fread(stream->base, (size_t)1, (size_t)SECUREC_BUFFERED_BLOK_SIZE, stream->pf); + stream->base[SECUREC_BUFFERED_BLOK_SIZE] = '\0'; /* for tool Warning string null */ + if (stream->count == 0 || stream->count > SECUREC_BUFFERED_BLOK_SIZE) { + return SECUREC_EOF; + } + stream->cur = stream->base; + stream->flag |= SECUREC_LOAD_FILE_TO_MEM_FLAG; + if (firstReadOnFile != 0) { + SecSkipBomHeader(stream); + } + } + /* according wchar_t has two bytes */ + ch = (SecInt)((stream->count -= (int)sizeof(SecChar)) >= 0 ? \ + (SecInt)(SECUREC_CHAR_MASK & \ + (unsigned int)(int)(*((const SecChar *)(const void *)stream->cur))) : SECUREC_EOF); + stream->cur += sizeof(SecChar); + + if (ch != SECUREC_EOF && stream->base != NULL) { + stream->fileRealRead += (int)sizeof(SecChar); + } + return ch; +} +#endif + +/* + * Get char for wchar version + */ +static SecInt SecGetChar(SecFileStream *stream, int *counter) +{ + SecInt ch = SECUREC_EOF; +#if SECUREC_ENABLE_SCANF_FILE + if ((stream->flag & SECUREC_FROM_STDIN_FLAG) > 0) { + ch = SecGetCharFromStdin(stream); + } else if ((stream->flag & SECUREC_FILE_STREAM_FLAG) > 0) { + ch = SecGetCharFromFile(stream); + } +#endif + if ((stream->flag & SECUREC_MEM_STR_FLAG) > 0) { + /* according wchar_t has two bytes */ + ch = (SecInt)((stream->count -= (int)sizeof(SecChar)) >= 0 ? \ + (SecInt)(SECUREC_CHAR_MASK & \ + (unsigned int)(int)(*((const SecChar *)(const void *)stream->cur))) : SECUREC_EOF); + stream->cur += sizeof(SecChar); + } + *counter = *counter + 1; + return ch; +} + +/* + * Unget Public realizatio char for wchar and char version + */ +static void SecUnGetCharImpl(SecInt ch, SecFileStream *stream) +{ + if ((stream->flag & SECUREC_FROM_STDIN_FLAG) > 0) { +#if SECUREC_ENABLE_SCANF_FILE +#if defined(SECUREC_NO_STD_UNGETC) + stream->lastChar = (unsigned int)ch; + stream->fUnget = 1; +#else + (void)SECUREC_UN_GETC(ch, stream->pf); +#endif +#else + (void)ch; /* to clear e438 last value assigned not used , the compiler will optimize this code */ +#endif + } else if ((stream->flag & SECUREC_MEM_STR_FLAG) || (stream->flag & SECUREC_LOAD_FILE_TO_MEM_FLAG) > 0) { + if (stream->cur > stream->base) { + stream->cur -= sizeof(SecChar); + stream->count += (int)sizeof(SecChar); + } + } +#if SECUREC_ENABLE_SCANF_FILE + if ((stream->flag & SECUREC_FILE_STREAM_FLAG) > 0 && stream->base) { + stream->fileRealRead -= (int)sizeof(SecChar); + } +#endif +} + +/* + * Unget char for char version + */ +static void SecUnGetChar(SecInt ch, SecFileStream *stream, int *counter) +{ + if (ch != SECUREC_EOF) { + SecUnGetCharImpl(ch, stream); + } + *counter = *counter - 1; +} + +/* + * Skip space char by isspace + */ +static SecInt SecSkipSpaceChar(SecFileStream *stream, int *counter) +{ + SecInt ch; + do { + ch = SecGetChar(stream, counter); + } while (ch != SECUREC_EOF && SECUREC_IS_SPACE(ch)); + return ch; +} +#endif /* __INPUT_INL__5D13A042_DC3F_4ED9_A8D1_882811274C27 */ + diff --git a/third_party/securec/src/memcpy_s.c b/third_party/securec/src/memcpy_s.c new file mode 100644 index 0000000..5eb100f --- /dev/null +++ b/third_party/securec/src/memcpy_s.c @@ -0,0 +1,577 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_DO_MEMCPY 1 +#include "securecutil.h" + +#ifndef SECUREC_MEMCOPY_WITH_PERFORMANCE +#define SECUREC_MEMCOPY_WITH_PERFORMANCE 0 +#endif + +#if SECUREC_WITH_PERFORMANCE_ADDONS || SECUREC_MEMCOPY_WITH_PERFORMANCE +#ifndef SECUREC_MEMCOPY_THRESHOLD_SIZE +#define SECUREC_MEMCOPY_THRESHOLD_SIZE 64UL +#endif +/* + * Determine whether the address is 8-byte aligned, use static to increase performance + * return 0 is aligned + */ +static int SecIsAddrAligned8(const void *addr, const void *zeroAddr) +{ + return (int)(((size_t)((const char*)addr - (const char*)zeroAddr)) & 7); /* use 7 to check aligned 8 */ +} + +#define SECUREC_SMALL_MEM_COPY do { \ + if (SECUREC_ADDR_ALIGNED_8(dest) && SECUREC_ADDR_ALIGNED_8(src)) { \ + /* use struct assignment */ \ + switch (count) { \ + case 1: \ + *(SecStrBuf1 *)dest = *(const SecStrBuf1 *)src; \ + break; \ + case 2: \ + *(SecStrBuf2 *)dest = *(const SecStrBuf2 *)src; \ + break; \ + case 3: \ + *(SecStrBuf3 *)dest = *(const SecStrBuf3 *)src; \ + break; \ + case 4: \ + *(SecStrBuf4 *)dest = *(const SecStrBuf4 *)src; \ + break; \ + case 5: \ + *(SecStrBuf5 *)dest = *(const SecStrBuf5 *)src; \ + break; \ + case 6: \ + *(SecStrBuf6 *)dest = *(const SecStrBuf6 *)src; \ + break; \ + case 7: \ + *(SecStrBuf7 *)dest = *(const SecStrBuf7 *)src; \ + break; \ + case 8: \ + *(SecStrBuf8 *)dest = *(const SecStrBuf8 *)src; \ + break; \ + case 9: \ + *(SecStrBuf9 *)dest = *(const SecStrBuf9 *)src; \ + break; \ + case 10: \ + *(SecStrBuf10 *)dest = *(const SecStrBuf10 *)src; \ + break; \ + case 11: \ + *(SecStrBuf11 *)dest = *(const SecStrBuf11 *)src; \ + break; \ + case 12: \ + *(SecStrBuf12 *)dest = *(const SecStrBuf12 *)src; \ + break; \ + case 13: \ + *(SecStrBuf13 *)dest = *(const SecStrBuf13 *)src; \ + break; \ + case 14: \ + *(SecStrBuf14 *)dest = *(const SecStrBuf14 *)src; \ + break; \ + case 15: \ + *(SecStrBuf15 *)dest = *(const SecStrBuf15 *)src; \ + break; \ + case 16: \ + *(SecStrBuf16 *)dest = *(const SecStrBuf16 *)src; \ + break; \ + case 17: \ + *(SecStrBuf17 *)dest = *(const SecStrBuf17 *)src; \ + break; \ + case 18: \ + *(SecStrBuf18 *)dest = *(const SecStrBuf18 *)src; \ + break; \ + case 19: \ + *(SecStrBuf19 *)dest = *(const SecStrBuf19 *)src; \ + break; \ + case 20: \ + *(SecStrBuf20 *)dest = *(const SecStrBuf20 *)src; \ + break; \ + case 21: \ + *(SecStrBuf21 *)dest = *(const SecStrBuf21 *)src; \ + break; \ + case 22: \ + *(SecStrBuf22 *)dest = *(const SecStrBuf22 *)src; \ + break; \ + case 23: \ + *(SecStrBuf23 *)dest = *(const SecStrBuf23 *)src; \ + break; \ + case 24: \ + *(SecStrBuf24 *)dest = *(const SecStrBuf24 *)src; \ + break; \ + case 25: \ + *(SecStrBuf25 *)dest = *(const SecStrBuf25 *)src; \ + break; \ + case 26: \ + *(SecStrBuf26 *)dest = *(const SecStrBuf26 *)src; \ + break; \ + case 27: \ + *(SecStrBuf27 *)dest = *(const SecStrBuf27 *)src; \ + break; \ + case 28: \ + *(SecStrBuf28 *)dest = *(const SecStrBuf28 *)src; \ + break; \ + case 29: \ + *(SecStrBuf29 *)dest = *(const SecStrBuf29 *)src; \ + break; \ + case 30: \ + *(SecStrBuf30 *)dest = *(const SecStrBuf30 *)src; \ + break; \ + case 31: \ + *(SecStrBuf31 *)dest = *(const SecStrBuf31 *)src; \ + break; \ + case 32: \ + *(SecStrBuf32 *)dest = *(const SecStrBuf32 *)src; \ + break; \ + case 33: \ + *(SecStrBuf33 *)dest = *(const SecStrBuf33 *)src; \ + break; \ + case 34: \ + *(SecStrBuf34 *)dest = *(const SecStrBuf34 *)src; \ + break; \ + case 35: \ + *(SecStrBuf35 *)dest = *(const SecStrBuf35 *)src; \ + break; \ + case 36: \ + *(SecStrBuf36 *)dest = *(const SecStrBuf36 *)src; \ + break; \ + case 37: \ + *(SecStrBuf37 *)dest = *(const SecStrBuf37 *)src; \ + break; \ + case 38: \ + *(SecStrBuf38 *)dest = *(const SecStrBuf38 *)src; \ + break; \ + case 39: \ + *(SecStrBuf39 *)dest = *(const SecStrBuf39 *)src; \ + break; \ + case 40: \ + *(SecStrBuf40 *)dest = *(const SecStrBuf40 *)src; \ + break; \ + case 41: \ + *(SecStrBuf41 *)dest = *(const SecStrBuf41 *)src; \ + break; \ + case 42: \ + *(SecStrBuf42 *)dest = *(const SecStrBuf42 *)src; \ + break; \ + case 43: \ + *(SecStrBuf43 *)dest = *(const SecStrBuf43 *)src; \ + break; \ + case 44: \ + *(SecStrBuf44 *)dest = *(const SecStrBuf44 *)src; \ + break; \ + case 45: \ + *(SecStrBuf45 *)dest = *(const SecStrBuf45 *)src; \ + break; \ + case 46: \ + *(SecStrBuf46 *)dest = *(const SecStrBuf46 *)src; \ + break; \ + case 47: \ + *(SecStrBuf47 *)dest = *(const SecStrBuf47 *)src; \ + break; \ + case 48: \ + *(SecStrBuf48 *)dest = *(const SecStrBuf48 *)src; \ + break; \ + case 49: \ + *(SecStrBuf49 *)dest = *(const SecStrBuf49 *)src; \ + break; \ + case 50: \ + *(SecStrBuf50 *)dest = *(const SecStrBuf50 *)src; \ + break; \ + case 51: \ + *(SecStrBuf51 *)dest = *(const SecStrBuf51 *)src; \ + break; \ + case 52: \ + *(SecStrBuf52 *)dest = *(const SecStrBuf52 *)src; \ + break; \ + case 53: \ + *(SecStrBuf53 *)dest = *(const SecStrBuf53 *)src; \ + break; \ + case 54: \ + *(SecStrBuf54 *)dest = *(const SecStrBuf54 *)src; \ + break; \ + case 55: \ + *(SecStrBuf55 *)dest = *(const SecStrBuf55 *)src; \ + break; \ + case 56: \ + *(SecStrBuf56 *)dest = *(const SecStrBuf56 *)src; \ + break; \ + case 57: \ + *(SecStrBuf57 *)dest = *(const SecStrBuf57 *)src; \ + break; \ + case 58: \ + *(SecStrBuf58 *)dest = *(const SecStrBuf58 *)src; \ + break; \ + case 59: \ + *(SecStrBuf59 *)dest = *(const SecStrBuf59 *)src; \ + break; \ + case 60: \ + *(SecStrBuf60 *)dest = *(const SecStrBuf60 *)src; \ + break; \ + case 61: \ + *(SecStrBuf61 *)dest = *(const SecStrBuf61 *)src; \ + break; \ + case 62: \ + *(SecStrBuf62 *)dest = *(const SecStrBuf62 *)src; \ + break; \ + case 63: \ + *(SecStrBuf63 *)dest = *(const SecStrBuf63 *)src; \ + break; \ + case 64: \ + *(SecStrBuf64 *)dest = *(const SecStrBuf64 *)src; \ + break; \ + default: \ + break; \ + } /* END switch */ \ + } else { \ + char *tmpDest = (char *)dest; \ + const char *tmpSrc = (const char *)src; \ + switch (count) { \ + case 64: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 63: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 62: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 61: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 60: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 59: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 58: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 57: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 56: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 55: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 54: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 53: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 52: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 51: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 50: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 49: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 48: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 47: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 46: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 45: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 44: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 43: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 42: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 41: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 40: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 39: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 38: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 37: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 36: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 35: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 34: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 33: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 32: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 31: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 30: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 29: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 28: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 27: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 26: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 25: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 24: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 23: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 22: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 21: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 20: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 19: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 18: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 17: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 16: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 15: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 14: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 13: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 12: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 11: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 10: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 9: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 8: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 7: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 6: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 5: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 4: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 3: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 2: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 1: \ + *(tmpDest++) = *(tmpSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + default: \ + break; \ + } \ + } \ +} SECUREC_WHILE_ZERO +#endif + +/* + * Handling errors + */ +static errno_t SecMemcpyError(void *dest, size_t destMax, const void *src, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_MEM_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("memcpy_s"); + return ERANGE; + } + if (dest == NULL || src == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("memcpy_s"); + if (dest != NULL) { + (void)memset(dest, 0, destMax); + return EINVAL_AND_RESET; + } + return EINVAL; + } + if (count > destMax) { + (void)memset(dest, 0, destMax); + SECUREC_ERROR_INVALID_RANGE("memcpy_s"); + return ERANGE_AND_RESET; + } + if (dest == src) { + return EOK; + } + if ((dest > src && dest < (const void *)((const unsigned char *)src + count)) || \ + (src > dest && src < (void *)((unsigned char *)dest + count))) { + (void)memset(dest, 0, destMax); + SECUREC_ERROR_BUFFER_OVERLAP("memcpy_s"); + return EOVERLAP_AND_RESET; + } + /* count == 0 also return EOK */ + return EOK; +} + +#if SECUREC_WITH_PERFORMANCE_ADDONS || SECUREC_MEMCOPY_WITH_PERFORMANCE +/* + * Performance optimization + */ +static void SecDoMemcpyOpt(void *dest, const void *src, size_t count) +{ + if (count > SECUREC_MEMCOPY_THRESHOLD_SIZE) { + SecDoMemcpy(dest, src, count); + } else { + SECUREC_SMALL_MEM_COPY; + } + return; +} +#endif + +#if defined(SECUREC_COMPATIBLE_WIN_FORMAT) + /* fread API in windows will call memcpy_s and pass 0xffffffff to destMax. + * To avoid the failure of fread, we don't check desMax limit. + */ +#define SECUREC_MEMCPY_PARAM_OK(dest, destMax, src, count) (SECUREC_LIKELY((count) <= (destMax) && \ + (dest) != NULL && (src) != NULL && \ + (count) > 0 && SECUREC_MEMORY_NO_OVERLAP((dest), (src), (count)))) +#else +#define SECUREC_MEMCPY_PARAM_OK(dest, destMax, src, count) (SECUREC_LIKELY((count) <= (destMax) && \ + (dest) != NULL && (src) != NULL && \ + (destMax) <= SECUREC_MEM_MAX_LEN && \ + (count) > 0 && SECUREC_MEMORY_NO_OVERLAP((dest), (src), (count)))) +#endif + +/* + * + * The memcpy_s function copies n characters from the object pointed to by src into the object pointed to by dest + * + * + * dest Destination buffer. + * destMax Size of the destination buffer. + * src Buffer to copy from. + * count Number of characters to copy + * + * + * dest buffer is updated. + * + * + * EOK Success + * EINVAL dest is NULL and destMax != 0 and destMax <= SECUREC_MEM_MAX_LEN + * EINVAL_AND_RESET dest != NULL and src is NULLL and destMax != 0 and destMax <= SECUREC_MEM_MAX_LEN + * ERANGE destMax > SECUREC_MEM_MAX_LEN or destMax is 0 + * ERANGE_AND_RESET count > destMax and destMax != 0 and destMax <= SECUREC_MEM_MAX_LEN + * and dest != NULL and src != NULL + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and + * count <= destMax destMax != 0 and destMax <= SECUREC_MEM_MAX_LEN and dest != NULL + * and src != NULL and dest != src + * + * if an error occured, dest will be filled with 0. + * If the source and destination overlap, the behavior of memcpy_s is undefined. + * Use memmove_s to handle overlapping regions. + */ +errno_t memcpy_s(void *dest, size_t destMax, const void *src, size_t count) +{ + if (SECUREC_MEMCPY_PARAM_OK(dest, destMax, src, count)) { +#if SECUREC_MEMCOPY_WITH_PERFORMANCE + SecDoMemcpyOpt(dest, src, count); +#else + SecDoMemcpy(dest, src, count); +#endif + return EOK; + } + /* meet some runtime violation, return error code */ + return SecMemcpyError(dest, destMax, src, count); +} + +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(memcpy_s); +#endif + +#if SECUREC_WITH_PERFORMANCE_ADDONS +/* + * Performance optimization + */ +errno_t memcpy_sOptAsm(void *dest, size_t destMax, const void *src, size_t count) +{ + if (SECUREC_MEMCPY_PARAM_OK(dest, destMax, src, count)) { + SecDoMemcpyOpt(dest, src, count); + return EOK; + } + /* meet some runtime violation, return error code */ + return SecMemcpyError(dest, destMax, src, count); +} + +/* trim judgement on "destMax <= SECUREC_MEM_MAX_LEN" */ +errno_t memcpy_sOptTc(void *dest, size_t destMax, const void *src, size_t count) +{ + if (SECUREC_LIKELY(count <= destMax && dest != NULL && src != NULL && \ + count > 0 && \ + ((dest > src && (const void *)((const unsigned char *)src + count) <= dest) || \ + (src > dest && (void *)((unsigned char *)dest + count) <= src)))) { + SecDoMemcpyOpt(dest, src, count); + return EOK; + } + /* meet some runtime violation, return error code */ + return SecMemcpyError(dest, destMax, src, count); +} +#endif + diff --git a/third_party/securec/src/memmove_s.c b/third_party/securec/src/memmove_s.c new file mode 100644 index 0000000..ec6d04a --- /dev/null +++ b/third_party/securec/src/memmove_s.c @@ -0,0 +1,120 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securecutil.h" + +#ifdef SECUREC_NOT_CALL_LIBC_CORE_API +/* + * Implementing memory data movement + */ +static void SecUtilMemmove(void *dst, const void *src, size_t count) +{ + unsigned char *pDest = (unsigned char *)dst; + const unsigned char *pSrc = (const unsigned char *)src; + size_t maxCount = count; + + if (dst <= src || pDest >= (pSrc + maxCount)) { + /* + * Non-Overlapping Buffers + * copy from lower addresses to higher addresses + */ + while (maxCount--) { + *pDest = *pSrc; + ++pDest; + ++pSrc; + } + } else { + /* + * Overlapping Buffers + * copy from higher addresses to lower addresses + */ + pDest = pDest + maxCount - 1; + pSrc = pSrc + maxCount - 1; + + while (maxCount--) { + *pDest = *pSrc; + + --pDest; + --pSrc; + } + } +} +#endif + +/* + * + * The memmove_s function copies count bytes of characters from src to dest. + * This function can be assigned correctly when memory overlaps. + * + * dest Destination object. + * destMax Size of the destination buffer. + * src Source object. + * count Number of characters to copy. + * + * + * dest buffer is uptdated. + * + * + * EOK Success + * EINVAL dest is NULL and destMax != 0 and destMax <= SECUREC_MEM_MAX_LEN + * EINVAL_AND_RESET dest != NULL and src is NULLL and destMax != 0 and destMax <= SECUREC_MEM_MAX_LEN + * ERANGE destMax > SECUREC_MEM_MAX_LEN or destMax is 0 + * ERANGE_AND_RESET count > destMax and dest != NULL and src != NULL and destMax != 0 + * and destMax <= SECUREC_MEM_MAX_LEN + * + * If an error occured, dest will be filled with 0 when dest and destMax valid. + * If some regions of the source area and the destination overlap, memmove_s + * ensures that the original source bytes in the overlapping region are copied + * before being overwritten. + */ +errno_t memmove_s(void *dest, size_t destMax, const void *src, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_MEM_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("memmove_s"); + return ERANGE; + } + if (dest == NULL || src == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("memmove_s"); + if (dest != NULL) { + (void)memset(dest, 0, destMax); + return EINVAL_AND_RESET; + } + return EINVAL; + } + if (count > destMax) { + (void)memset(dest, 0, destMax); + SECUREC_ERROR_INVALID_RANGE("memmove_s"); + return ERANGE_AND_RESET; + } + if (dest == src) { + return EOK; + } + + if (count > 0) { +#ifdef SECUREC_NOT_CALL_LIBC_CORE_API + SecUtilMemmove(dest, src, count); +#else + /* use underlying memmove for performance consideration */ + (void)memmove(dest, src, count); +#endif + } + return EOK; +} + +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(memmove_s); +#endif + diff --git a/third_party/securec/src/memset_s.c b/third_party/securec/src/memset_s.c new file mode 100644 index 0000000..cd3f988 --- /dev/null +++ b/third_party/securec/src/memset_s.c @@ -0,0 +1,522 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_DO_MEMSET 1 + +#include "securecutil.h" + +#ifndef SECUREC_MEMSET_WITH_PERFORMANCE +#define SECUREC_MEMSET_WITH_PERFORMANCE 0 +#endif + +#define SECUREC_MEMSET_PARAM_OK(dest, destMax, count) (SECUREC_LIKELY((count) <= (destMax) && \ + (dest) != NULL && (destMax) <= SECUREC_MEM_MAX_LEN)) + + +#if SECUREC_WITH_PERFORMANCE_ADDONS || SECUREC_MEMSET_WITH_PERFORMANCE +/* + * Determine whether the address is 8-byte aligned, use static to increase performance + * return 0 is aligned + */ +static int SecIsAddrAligned8(const void *addr, const void *zeroAddr) +{ + return (int)(((size_t)((const char*)addr - (const char*)zeroAddr)) & 7); /* use 7 to check aligned 8 */ +} + +/* use union to clear strict-aliasing warning */ +typedef union { + SecStrBuf32 buf32; + SecStrBuf31 buf31; + SecStrBuf30 buf30; + SecStrBuf29 buf29; + SecStrBuf28 buf28; + SecStrBuf27 buf27; + SecStrBuf26 buf26; + SecStrBuf25 buf25; + SecStrBuf24 buf24; + SecStrBuf23 buf23; + SecStrBuf22 buf22; + SecStrBuf21 buf21; + SecStrBuf20 buf20; + SecStrBuf19 buf19; + SecStrBuf18 buf18; + SecStrBuf17 buf17; + SecStrBuf16 buf16; + SecStrBuf15 buf15; + SecStrBuf14 buf14; + SecStrBuf13 buf13; + SecStrBuf12 buf12; + SecStrBuf11 buf11; + SecStrBuf10 buf10; + SecStrBuf9 buf9; + SecStrBuf8 buf8; + SecStrBuf7 buf7; + SecStrBuf6 buf6; + SecStrBuf5 buf5; + SecStrBuf4 buf4; + SecStrBuf3 buf3; + SecStrBuf2 buf2; + SecStrBuf1 buf1; +} SecStrBuf32Union; +/* C standard initializes the first member of the consortium. */ +static const SecStrBuf32 g_allZero = {{ + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' +}}; +static const SecStrBuf32 g_allFF = {{ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}}; + +static const SecStrBuf32Union *SecStrictAliasingCast(const SecStrBuf32 *buf) +{ + return (const SecStrBuf32Union *)buf; +} + +#ifndef SECUREC_MEMSET_THRESHOLD_SIZE +#define SECUREC_MEMSET_THRESHOLD_SIZE 32UL +#endif + +#define SECUREC_UNALIGNED_SET do { \ + char *pcDest = (char *)dest; \ + switch (count) { \ + case 32: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 31: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 30: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 29: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 28: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 27: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 26: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 25: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 24: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 23: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 22: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 21: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 20: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 19: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 18: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 17: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 16: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 15: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 14: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 13: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 12: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 11: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 10: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 9: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 8: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 7: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 6: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 5: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 4: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 3: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 2: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + case 1: \ + *(pcDest++) = (char)c; \ + /* fall-through */ /* FALLTHRU */ \ + default: \ + break; \ + } \ +} SECUREC_WHILE_ZERO + +#define SECUREC_ALIGNED_SET_OPT_ZERO_FF do { \ + switch (c) { \ + case 0: \ + switch (count) { \ + case 1: \ + *(SecStrBuf1 *)dest = *(const SecStrBuf1 *)(&((SecStrictAliasingCast(&g_allZero))->buf1)); \ + break; \ + case 2: \ + *(SecStrBuf2 *)dest = *(const SecStrBuf2 *)(&((SecStrictAliasingCast(&g_allZero))->buf2)); \ + break; \ + case 3: \ + *(SecStrBuf3 *)dest = *(const SecStrBuf3 *)(&((SecStrictAliasingCast(&g_allZero))->buf3)); \ + break; \ + case 4: \ + *(SecStrBuf4 *)dest = *(const SecStrBuf4 *)(&((SecStrictAliasingCast(&g_allZero))->buf4)); \ + break; \ + case 5: \ + *(SecStrBuf5 *)dest = *(const SecStrBuf5 *)(&((SecStrictAliasingCast(&g_allZero))->buf5)); \ + break; \ + case 6: \ + *(SecStrBuf6 *)dest = *(const SecStrBuf6 *)(&((SecStrictAliasingCast(&g_allZero))->buf6)); \ + break; \ + case 7: \ + *(SecStrBuf7 *)dest = *(const SecStrBuf7 *)(&((SecStrictAliasingCast(&g_allZero))->buf7)); \ + break; \ + case 8: \ + *(SecStrBuf8 *)dest = *(const SecStrBuf8 *)(&((SecStrictAliasingCast(&g_allZero))->buf8)); \ + break; \ + case 9: \ + *(SecStrBuf9 *)dest = *(const SecStrBuf9 *)(&((SecStrictAliasingCast(&g_allZero))->buf9)); \ + break; \ + case 10: \ + *(SecStrBuf10 *)dest = *(const SecStrBuf10 *)(&((SecStrictAliasingCast(&g_allZero))->buf10)); \ + break; \ + case 11: \ + *(SecStrBuf11 *)dest = *(const SecStrBuf11 *)(&((SecStrictAliasingCast(&g_allZero))->buf11)); \ + break; \ + case 12: \ + *(SecStrBuf12 *)dest = *(const SecStrBuf12 *)(&((SecStrictAliasingCast(&g_allZero))->buf12)); \ + break; \ + case 13: \ + *(SecStrBuf13 *)dest = *(const SecStrBuf13 *)(&((SecStrictAliasingCast(&g_allZero))->buf13)); \ + break; \ + case 14: \ + *(SecStrBuf14 *)dest = *(const SecStrBuf14 *)(&((SecStrictAliasingCast(&g_allZero))->buf14)); \ + break; \ + case 15: \ + *(SecStrBuf15 *)dest = *(const SecStrBuf15 *)(&((SecStrictAliasingCast(&g_allZero))->buf15)); \ + break; \ + case 16: \ + *(SecStrBuf16 *)dest = *(const SecStrBuf16 *)(&((SecStrictAliasingCast(&g_allZero))->buf16)); \ + break; \ + case 17: \ + *(SecStrBuf17 *)dest = *(const SecStrBuf17 *)(&((SecStrictAliasingCast(&g_allZero))->buf17)); \ + break; \ + case 18: \ + *(SecStrBuf18 *)dest = *(const SecStrBuf18 *)(&((SecStrictAliasingCast(&g_allZero))->buf18)); \ + break; \ + case 19: \ + *(SecStrBuf19 *)dest = *(const SecStrBuf19 *)(&((SecStrictAliasingCast(&g_allZero))->buf19)); \ + break; \ + case 20: \ + *(SecStrBuf20 *)dest = *(const SecStrBuf20 *)(&((SecStrictAliasingCast(&g_allZero))->buf20)); \ + break; \ + case 21: \ + *(SecStrBuf21 *)dest = *(const SecStrBuf21 *)(&((SecStrictAliasingCast(&g_allZero))->buf21)); \ + break; \ + case 22: \ + *(SecStrBuf22 *)dest = *(const SecStrBuf22 *)(&((SecStrictAliasingCast(&g_allZero))->buf22)); \ + break; \ + case 23: \ + *(SecStrBuf23 *)dest = *(const SecStrBuf23 *)(&((SecStrictAliasingCast(&g_allZero))->buf23)); \ + break; \ + case 24: \ + *(SecStrBuf24 *)dest = *(const SecStrBuf24 *)(&((SecStrictAliasingCast(&g_allZero))->buf24)); \ + break; \ + case 25: \ + *(SecStrBuf25 *)dest = *(const SecStrBuf25 *)(&((SecStrictAliasingCast(&g_allZero))->buf25)); \ + break; \ + case 26: \ + *(SecStrBuf26 *)dest = *(const SecStrBuf26 *)(&((SecStrictAliasingCast(&g_allZero))->buf26)); \ + break; \ + case 27: \ + *(SecStrBuf27 *)dest = *(const SecStrBuf27 *)(&((SecStrictAliasingCast(&g_allZero))->buf27)); \ + break; \ + case 28: \ + *(SecStrBuf28 *)dest = *(const SecStrBuf28 *)(&((SecStrictAliasingCast(&g_allZero))->buf28)); \ + break; \ + case 29: \ + *(SecStrBuf29 *)dest = *(const SecStrBuf29 *)(&((SecStrictAliasingCast(&g_allZero))->buf29)); \ + break; \ + case 30: \ + *(SecStrBuf30 *)dest = *(const SecStrBuf30 *)(&((SecStrictAliasingCast(&g_allZero))->buf30)); \ + break; \ + case 31: \ + *(SecStrBuf31 *)dest = *(const SecStrBuf31 *)(&((SecStrictAliasingCast(&g_allZero))->buf31)); \ + break; \ + case 32: \ + *(SecStrBuf32 *)dest = *(const SecStrBuf32 *)(&((SecStrictAliasingCast(&g_allZero))->buf32)); \ + break; \ + default: \ + break; \ + } \ + break; \ + case 0xFF: \ + switch (count) { \ + case 1: \ + *(SecStrBuf1 *)dest = *(const SecStrBuf1 *)(&((SecStrictAliasingCast(&g_allFF))->buf1)); \ + break; \ + case 2: \ + *(SecStrBuf2 *)dest = *(const SecStrBuf2 *)(&((SecStrictAliasingCast(&g_allFF))->buf2)); \ + break; \ + case 3: \ + *(SecStrBuf3 *)dest = *(const SecStrBuf3 *)(&((SecStrictAliasingCast(&g_allFF))->buf3)); \ + break; \ + case 4: \ + *(SecStrBuf4 *)dest = *(const SecStrBuf4 *)(&((SecStrictAliasingCast(&g_allFF))->buf4)); \ + break; \ + case 5: \ + *(SecStrBuf5 *)dest = *(const SecStrBuf5 *)(&((SecStrictAliasingCast(&g_allFF))->buf5)); \ + break; \ + case 6: \ + *(SecStrBuf6 *)dest = *(const SecStrBuf6 *)(&((SecStrictAliasingCast(&g_allFF))->buf6)); \ + break; \ + case 7: \ + *(SecStrBuf7 *)dest = *(const SecStrBuf7 *)(&((SecStrictAliasingCast(&g_allFF))->buf7)); \ + break; \ + case 8: \ + *(SecStrBuf8 *)dest = *(const SecStrBuf8 *)(&((SecStrictAliasingCast(&g_allFF))->buf8)); \ + break; \ + case 9: \ + *(SecStrBuf9 *)dest = *(const SecStrBuf9 *)(&((SecStrictAliasingCast(&g_allFF))->buf9)); \ + break; \ + case 10: \ + *(SecStrBuf10 *)dest = *(const SecStrBuf10 *)(&((SecStrictAliasingCast(&g_allFF))->buf10)); \ + break; \ + case 11: \ + *(SecStrBuf11 *)dest = *(const SecStrBuf11 *)(&((SecStrictAliasingCast(&g_allFF))->buf11)); \ + break; \ + case 12: \ + *(SecStrBuf12 *)dest = *(const SecStrBuf12 *)(&((SecStrictAliasingCast(&g_allFF))->buf12)); \ + break; \ + case 13: \ + *(SecStrBuf13 *)dest = *(const SecStrBuf13 *)(&((SecStrictAliasingCast(&g_allFF))->buf13)); \ + break; \ + case 14: \ + *(SecStrBuf14 *)dest = *(const SecStrBuf14 *)(&((SecStrictAliasingCast(&g_allFF))->buf14)); \ + break; \ + case 15: \ + *(SecStrBuf15 *)dest = *(const SecStrBuf15 *)(&((SecStrictAliasingCast(&g_allFF))->buf15)); \ + break; \ + case 16: \ + *(SecStrBuf16 *)dest = *(const SecStrBuf16 *)(&((SecStrictAliasingCast(&g_allFF))->buf16)); \ + break; \ + case 17: \ + *(SecStrBuf17 *)dest = *(const SecStrBuf17 *)(&((SecStrictAliasingCast(&g_allFF))->buf17)); \ + break; \ + case 18: \ + *(SecStrBuf18 *)dest = *(const SecStrBuf18 *)(&((SecStrictAliasingCast(&g_allFF))->buf18)); \ + break; \ + case 19: \ + *(SecStrBuf19 *)dest = *(const SecStrBuf19 *)(&((SecStrictAliasingCast(&g_allFF))->buf19)); \ + break; \ + case 20: \ + *(SecStrBuf20 *)dest = *(const SecStrBuf20 *)(&((SecStrictAliasingCast(&g_allFF))->buf20)); \ + break; \ + case 21: \ + *(SecStrBuf21 *)dest = *(const SecStrBuf21 *)(&((SecStrictAliasingCast(&g_allFF))->buf21)); \ + break; \ + case 22: \ + *(SecStrBuf22 *)dest = *(const SecStrBuf22 *)(&((SecStrictAliasingCast(&g_allFF))->buf22)); \ + break; \ + case 23: \ + *(SecStrBuf23 *)dest = *(const SecStrBuf23 *)(&((SecStrictAliasingCast(&g_allFF))->buf23)); \ + break; \ + case 24: \ + *(SecStrBuf24 *)dest = *(const SecStrBuf24 *)(&((SecStrictAliasingCast(&g_allFF))->buf24)); \ + break; \ + case 25: \ + *(SecStrBuf25 *)dest = *(const SecStrBuf25 *)(&((SecStrictAliasingCast(&g_allFF))->buf25)); \ + break; \ + case 26: \ + *(SecStrBuf26 *)dest = *(const SecStrBuf26 *)(&((SecStrictAliasingCast(&g_allFF))->buf26)); \ + break; \ + case 27: \ + *(SecStrBuf27 *)dest = *(const SecStrBuf27 *)(&((SecStrictAliasingCast(&g_allFF))->buf27)); \ + break; \ + case 28: \ + *(SecStrBuf28 *)dest = *(const SecStrBuf28 *)(&((SecStrictAliasingCast(&g_allFF))->buf28)); \ + break; \ + case 29: \ + *(SecStrBuf29 *)dest = *(const SecStrBuf29 *)(&((SecStrictAliasingCast(&g_allFF))->buf29)); \ + break; \ + case 30: \ + *(SecStrBuf30 *)dest = *(const SecStrBuf30 *)(&((SecStrictAliasingCast(&g_allFF))->buf30)); \ + break; \ + case 31: \ + *(SecStrBuf31 *)dest = *(const SecStrBuf31 *)(&((SecStrictAliasingCast(&g_allFF))->buf31)); \ + break; \ + case 32: \ + *(SecStrBuf32 *)dest = *(const SecStrBuf32 *)(&((SecStrictAliasingCast(&g_allFF))->buf32)); \ + break; \ + default: \ + break; \ + } \ + break; \ + default: \ + SECUREC_UNALIGNED_SET; \ + } /* END switch */ \ +} SECUREC_WHILE_ZERO +#endif + +/* + * Handling errors + */ +static errno_t SecMemsetError(void *dest, size_t destMax, int c, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_MEM_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("memset_s"); + return ERANGE; + } + if (dest == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("memset_s"); + return EINVAL; + } + if (count > destMax) { + (void)memset(dest, c, destMax); /* set entire buffer to value c */ + SECUREC_ERROR_INVALID_RANGE("memset_s"); + return ERANGE_AND_RESET; + } + return EOK; +} + +#if SECUREC_WITH_PERFORMANCE_ADDONS || SECUREC_MEMSET_WITH_PERFORMANCE +/* + * Performance optimization + */ +static void SecDoMemsetOpt(void *dest, int c, size_t count) +{ + if (count > SECUREC_MEMSET_THRESHOLD_SIZE) { + SecDoMemset(dest, c, count); + } else { + if (SECUREC_ADDR_ALIGNED_8(dest)) { + /* use struct assignment */ + SECUREC_ALIGNED_SET_OPT_ZERO_FF; + } else { + SECUREC_UNALIGNED_SET; + } + } + return; +} +#endif + +/* + * + * The memset_s function copies the value of c (converted to an unsigned char) + * into each of the first count characters of the object pointed to by dest. + * + * + * dest Pointer to destination. + * destMax The size of the buffer. + * c Character to set. + * count Number of characters. + * + * + * dest buffer is uptdated. + * + * + * EOK Success + * EINVAL dest == NULL and destMax != 0 and destMax <= SECUREC_MEM_MAX_LEN + * ERANGE destMax is 0 or destMax > SECUREC_MEM_MAX_LEN + * ERANGE_AND_RESET count > destMax and destMax != 0 and destMax <= SECUREC_MEM_MAX_LEN and dest != NULL + * + * if return ERANGE_AND_RESET then fill dest to c ,fill length is destMax + */ +errno_t memset_s(void *dest, size_t destMax, int c, size_t count) +{ + if (SECUREC_MEMSET_PARAM_OK(dest, destMax, count)) { +#if SECUREC_MEMSET_WITH_PERFORMANCE + SecDoMemsetOpt(dest, c, count); +#else + SecDoMemset(dest, c, count); +#endif + return EOK; + } else { + /* meet some runtime violation, return error code */ + return SecMemsetError(dest, destMax, c, count); + } +} + +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(memset_s); +#endif + +#if SECUREC_WITH_PERFORMANCE_ADDONS +/* + * Performance optimization + */ +errno_t memset_sOptAsm(void *dest, size_t destMax, int c, size_t count) +{ + if (SECUREC_MEMSET_PARAM_OK(dest, destMax, count)) { + SecDoMemsetOpt(dest, c, count); + return EOK; + } + /* meet some runtime violation, return error code */ + return SecMemsetError(dest, destMax, c, count); +} + +/* + * Performance optimization + */ +errno_t memset_sOptTc(void *dest, size_t destMax, int c, size_t count) +{ + if (SECUREC_LIKELY(count <= destMax && dest != NULL)) { + SecDoMemsetOpt(dest, c, count); + return EOK; + } + /* meet some runtime violation, return error code */ + return SecMemsetError(dest, destMax, c, count); +} +#endif + diff --git a/third_party/securec/src/output.inl b/third_party/securec/src/output.inl new file mode 100644 index 0000000..d4e136c --- /dev/null +++ b/third_party/securec/src/output.inl @@ -0,0 +1,1401 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OUTPUT_INL_2B263E9C_43D8_44BB_B17A_6D2033DECEE5 +#define OUTPUT_INL_2B263E9C_43D8_44BB_B17A_6D2033DECEE5 + +#define SECUREC_NULL_STRING_SIZE 8 +#define SECUREC_STATE_TABLE_SIZE 337 +#define SECUREC_OFFSET_BITS_WORD 16 +#define SECUREC_OFFSET_BITS_DWORD 32 + +#define SECUREC_OFFSET_DIV_OCTAL 3 +#define SECUREC_OFFSET_DIV_HEX 4 +#define SECUREC_RADIX_OCTAL 8 +#define SECUREC_RADIX_DECIMAL 10 +#define SECUREC_RADIX_HEX 16 +/* Use two displacements to eliminate compilation warnings */ +#define SECUREC_SHR_DWORD(x) (((x) >> 16) >> 16) +#define SECUREC_PREFIX_LEN 2 +/* size include '+' and '\0' */ +#define SECUREC_FLOAT_BUF_EXT 2 + + +#ifdef SECUREC_STACK_SIZE_LESS_THAN_1K +#define SECUREC_FMT_STR_LEN 8 +#else +#define SECUREC_FMT_STR_LEN 16 +#endif + +typedef struct { + unsigned int flags; + int fldWidth; + int precision; + int bufferIsWide; /* flag for buffer contains wide chars ;0 is not wide char */ + int dynWidth; /* %* 1 width from variable parameter ;0 not */ + int dynPrecision; /* %.* 1 precision from variable parameter ;0 not */ +} SecFormatAttr; + +typedef union { + char *str; /* not a null terminated string */ +#if SECUREC_HAVE_WCHART + wchar_t *wStr; +#endif +} SecFormatBuf; + +typedef union { + char str[SECUREC_BUFFER_SIZE + 1]; +#ifdef SECUREC_FOR_WCHAR + wchar_t wStr[SECUREC_BUFFER_SIZE + 1]; +#endif +} SecBuffer; + +#if SECUREC_ENABLE_SPRINTF_FLOAT +/* call system sprintf to format float value */ +static int SecIndirectSprintf(char *strDest, const char *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + SECUREC_MASK_MSVC_CRT_WARNING + ret = vsprintf(strDest, format, argList); + SECUREC_END_MASK_MSVC_CRT_WARNING + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} + +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT +/* out put long double value to dest */ +static int SecFormatLongDboule(char *strDest,const SecFormatAttr *formatAttr, const char *fmt, long double ldValue) +{ + int fldWidth = ((formatAttr->flags & SECUREC_FLAG_LEFT) ? (-(formatAttr->fldWidth)) : formatAttr->fldWidth); + if (formatAttr->dynWidth && formatAttr->dynPrecision) { + return SecIndirectSprintf(strDest, fmt, fldWidth, formatAttr->precision, ldValue); + } else if (formatAttr->dynWidth) { + return SecIndirectSprintf(strDest, fmt, fldWidth, ldValue); + } else if (formatAttr->dynPrecision) { + return SecIndirectSprintf(strDest, fmt, formatAttr->precision, ldValue); + } + return SecIndirectSprintf(strDest, fmt, ldValue); +} +#endif + +/* out put double value to dest */ +static int SecFormatDboule(char *strDest, const SecFormatAttr *formatAttr, const char *fmt, double dValue) +{ + int fldWidth = ((formatAttr->flags & SECUREC_FLAG_LEFT) ? (-(formatAttr->fldWidth)) : formatAttr->fldWidth); + if (formatAttr->dynWidth && formatAttr->dynPrecision) { + return SecIndirectSprintf(strDest, fmt, fldWidth, formatAttr->precision, dValue); + } else if (formatAttr->dynWidth) { + return SecIndirectSprintf(strDest, fmt, fldWidth, dValue); + } else if (formatAttr->dynPrecision) { + return SecIndirectSprintf(strDest, fmt, formatAttr->precision, dValue); + } + return SecIndirectSprintf(strDest, fmt, dValue); +} +#endif + +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT +/* to clear e506 warning */ +static int SecIsSameSize(size_t sizeA, size_t sizeB) +{ + return sizeA == sizeB; +} +#endif + +#define SECUREC_SPECIAL_DWORD(val32, numBase) do { \ + --formatBuf.str; \ + *(formatBuf.str) = digits[(val32) % (numBase)]; \ +} while (((val32) /= (numBase)) != 0) + +#if defined(SECUREC_USE_SPECIAL_DIV64) || (defined(SECUREC_VXWORKS_VERSION_5_4) && !defined(SECUREC_ON_64BITS)) +/* + * Fast divide by 10 algorithm. + * Calculation divisor multiply 0xcccccccccccccccdULL, resultHi64 >> 3 as quotient + */ +static void SecU64Div10(SecUnsignedInt64 divisor, SecUnsignedInt64 *quotient, SecUnsignedInt32 *remainder) +{ + SecUnsignedInt64 mask = 0xffffffffULL; /* use 0xffffffffULL as 32 bit mask */ + SecUnsignedInt64 magicHi = 0xccccccccULL; /* fast divide 10 magic numbers high 32bit 0xccccccccULL */ + SecUnsignedInt64 magicLow = 0xcccccccdULL; /* fast divide 10 magic numbers low 32bit 0xcccccccdULL */ + SecUnsignedInt64 divisorHi = (SecUnsignedInt64)(SECUREC_SHR_DWORD(divisor)); /* hig 32 bit use */ + SecUnsignedInt64 divisorLow = (SecUnsignedInt64)(divisor & mask); /* low 32 bit mask */ + SecUnsignedInt64 factorHi = divisorHi * magicHi; + SecUnsignedInt64 factorLow1 = divisorHi * magicLow; + SecUnsignedInt64 factorLow2 = divisorLow * magicHi; + SecUnsignedInt64 factorLow3 = divisorLow * magicLow; + SecUnsignedInt64 carry = (factorLow1 & mask) + (factorLow2 & mask) + SECUREC_SHR_DWORD(factorLow3); + SecUnsignedInt64 resultHi64 = factorHi + SECUREC_SHR_DWORD(factorLow1) + \ + SECUREC_SHR_DWORD(factorLow2) + SECUREC_SHR_DWORD(carry); + + *quotient = resultHi64 >> 3; /* fast divide 10 magic numbers 3 */ + *remainder = (SecUnsignedInt32)(divisor - ((*quotient) * 10)); /* quotient mul 10 */ + return; +} +#if defined(SECUREC_VXWORKS_VERSION_5_4) && !defined(SECUREC_ON_64BITS) +/* + * Divide function for VXWORKS + */ +static int SecU64Div32(SecUnsignedInt64 divisor, SecUnsignedInt32 radix, + SecUnsignedInt64 *quotient, SecUnsignedInt32 *remainder) +{ + switch (radix) { + case SECUREC_RADIX_DECIMAL: + SecU64Div10(divisor, quotient, remainder); + break; + case SECUREC_RADIX_HEX: + *quotient = divisor >> SECUREC_OFFSET_DIV_HEX; + *remainder = divisor & 0xfULL; /* mask one hex number by 0xfULL */ + break; + case SECUREC_RADIX_OCTAL: + *quotient = divisor >> SECUREC_OFFSET_DIV_OCTAL; + *remainder = divisor & 0x7ULL; /* mask one hex number by 0x7ULL */ + break; + default: + return -1; + } + return 0; +} +#endif +#endif + +#if defined(SECUREC_USE_SPECIAL_DIV64) +/* The compiler does not provide 64 bit division problems */ +#define SECUREC_SPECIAL_QWORD_BASE10(val64) do { \ + SecUnsignedInt64 quotient = 0; \ + SecUnsignedInt32 digit = 0; \ + SecU64Div10((val64), &(quotient), &(digit)); \ + --formatBuf.str; \ + *(formatBuf.str) = digits[digit]; \ + (val64) = quotient; \ +} while ((val64) != 0) +#else +#define SECUREC_SPECIAL_QWORD_BASE10(val64) do { \ + --formatBuf.str; \ + *(formatBuf.str) = digits[(val64) % SECUREC_RADIX_DECIMAL]; \ +} while (((val64) /= SECUREC_RADIX_DECIMAL) != 0) +#endif +#define SECUREC_SPECIAL_QWORD(val64, numBase) do { \ + --formatBuf.str; \ + *(formatBuf.str) = digits[(val64) % (numBase)]; \ +} while (((val64) /= (numBase)) != 0) + + +#define SECUREC_SAFE_WRITE_STR_OPT(src, txtLen, outStream, outChars) do { \ + int ii_; \ + for (ii_ = 0; ii_ < (txtLen); ++ii_) { \ + *((SecChar *)(void *)((outStream)->cur)) = *(SecChar *)(src); \ + (outStream)->cur += sizeof(SecChar); \ + (src) = (src) + 1; \ + } \ + (outStream)->count -= (txtLen) * (int)(sizeof(SecChar)); \ + *(outChars) = *(outChars) + (txtLen); \ +} SECUREC_WHILE_ZERO + +#define SECUREC_SAFE_WRITE_STR(src, txtLen, outStream, outChars) do { \ + if ((txtLen) < 12) { /* performance optimization for mobile number length 12 */ \ + SECUREC_SAFE_WRITE_STR_OPT((src), (txtLen), (outStream), (outChars)); \ + } else { \ + SecDoMemcpy((outStream)->cur, (src), ((size_t)(unsigned int)(txtLen) * (sizeof(SecChar)))); \ + (outStream)->cur += (size_t)((size_t)(unsigned int)(txtLen) * (sizeof(SecChar))); \ + (outStream)->count -= (txtLen) * (int)(sizeof(SecChar)); \ + *(outChars) = *(outChars) + (txtLen); \ + } \ +} SECUREC_WHILE_ZERO + +#define SECUREC_SAFE_WRITE_CHAR(c, outStream, outChars) do { \ + *((SecChar *)(void *)((outStream)->cur)) = (SecChar)(c); \ + (outStream)->cur += sizeof(SecChar); \ + (outStream)->count -= (int)(sizeof(SecChar)); \ + *(outChars) = *(outChars) + 1; \ +} SECUREC_WHILE_ZERO + +#define SECUREC_SAFE_PADDING(padChar, padLen, outStream, outChars) do { \ + int ii_; \ + for (ii_ = 0; ii_ < (padLen); ++ii_) { \ + *((SecChar *)(void *)((outStream)->cur)) = (SecChar)(padChar); \ + (outStream)->cur += sizeof(SecChar); \ + } \ + (outStream)->count -= (padLen) * (int)(sizeof(SecChar)); \ + *(outChars) = *(outChars) + (padLen); \ +} SECUREC_WHILE_ZERO + +/* The count variable can be reduced to 0, and the external function complements the \0 terminator. */ +#define SECUREC_IS_REST_BUF_ENOUGH(stream, needLen) ((int)((stream)->count - \ + (int)(needLen) * (int)(sizeof(SecChar))) >= 0) + +#define SECUREC_FMT_STATE_OFFSET 256 +#ifdef SECUREC_FOR_WCHAR +#define SECUREC_FMT_TYPE(c, fmtTable) ((((unsigned int)(int)(c)) <= (unsigned int)(int)SECUREC_CHAR('~')) ? \ + ((fmtTable)[(unsigned char)(c)]) : 0) +#define SECUREC_DECODE_STATE(c, fmtTable, lastState) (SecFmtState)((((fmtTable)[(SECUREC_FMT_TYPE(c, (fmtTable))) * \ + ((unsigned char)STAT_INVALID + 1) + \ + (unsigned char)(lastState) + \ + SECUREC_FMT_STATE_OFFSET]))) +#else +#define SECUREC_DECODE_STATE(c, fmtTable, lastState) (SecFmtState)(((fmtTable)[((fmtTable)[(unsigned char)(c)]) * \ + ((unsigned char)STAT_INVALID + 1) + \ + (unsigned char)(lastState) + \ + SECUREC_FMT_STATE_OFFSET])) +#endif + +static void SecDecodeFlags(SecChar ch, SecFormatAttr *attr) +{ + switch (ch) { + case SECUREC_CHAR(' '): + attr->flags |= SECUREC_FLAG_SIGN_SPACE; + break; + case SECUREC_CHAR('+'): + attr->flags |= SECUREC_FLAG_SIGN; + break; + case SECUREC_CHAR('-'): + attr->flags |= SECUREC_FLAG_LEFT; + break; + case SECUREC_CHAR('0'): + attr->flags |= SECUREC_FLAG_LEADZERO; /* add zero th the front */ + break; + case SECUREC_CHAR('#'): + attr->flags |= SECUREC_FLAG_ALTERNATE; /* output %x with 0x */ + break; + default: + break; + } + return; +} + + +/* + * Decoded size identifier in format string to Reduce the number of lines of function code + */ +static int SecDecodeSizeI(SecFormatAttr *attr, const SecChar **format) +{ +#ifdef SECUREC_ON_64BITS + attr->flags |= SECUREC_FLAG_I64; /* %I to INT64 */ +#endif + if ((**format == SECUREC_CHAR('6')) && (*((*format) + 1) == SECUREC_CHAR('4'))) { + (*format) += 2; /* add 2 to skip I64 */ + attr->flags |= SECUREC_FLAG_I64; /* %I64 to INT64 */ + } else if ((**format == SECUREC_CHAR('3')) && (*((*format) + 1) == SECUREC_CHAR('2'))) { + (*format) += 2; /* add 2 to skip I32 */ + attr->flags &= ~SECUREC_FLAG_I64; /* %I64 to INT32 */ + } else if ((**format == SECUREC_CHAR('d')) || (**format == SECUREC_CHAR('i')) || + (**format == SECUREC_CHAR('o')) || (**format == SECUREC_CHAR('u')) || + (**format == SECUREC_CHAR('x')) || (**format == SECUREC_CHAR('X'))) { + /* do nothing */ + } else { + /* Compatibility code for "%I" just print I */ + return -1; + } + return 0; +} +/* + * Decoded size identifier in format string + */ +static int SecDecodeSize(SecChar ch, SecFormatAttr *attr, const SecChar **format) +{ + switch (ch) { +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT + case SECUREC_CHAR('j'): + attr->flags |= SECUREC_FLAG_INTMAX; + break; +#endif + case SECUREC_CHAR('q'): + /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('L'): + attr->flags |= SECUREC_FLAG_LONGLONG | SECUREC_FLAG_LONG_DOUBLE; + break; + case SECUREC_CHAR('l'): + if (**format == SECUREC_CHAR('l')) { + *format = *format + 1; + attr->flags |= SECUREC_FLAG_LONGLONG; /* long long */ + } else { + attr->flags |= SECUREC_FLAG_LONG; /* long int or wchar_t */ + } + break; + case SECUREC_CHAR('t'): + attr->flags |= SECUREC_FLAG_PTRDIFF; + break; +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT + case SECUREC_CHAR('z'): + /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('Z'): + attr->flags |= SECUREC_FLAG_SIZE; + break; +#endif + case SECUREC_CHAR('I'): + if (SecDecodeSizeI(attr, format) != 0) { + /* Compatibility code for "%I" just print I */ + return -1; + } + break; + case SECUREC_CHAR('h'): + if (**format == SECUREC_CHAR('h')) { + attr->flags |= SECUREC_FLAG_CHAR; /* char */ + } else { + attr->flags |= SECUREC_FLAG_SHORT; /* short int */ + } + break; + case SECUREC_CHAR('w'): + attr->flags |= SECUREC_FLAG_WIDECHAR; /* wide char */ + break; + default: + break; + } + return 0; +} + +/* + * Decoded char type identifier + */ +static int SecDecodeTypeC(SecFormatAttr *attr, unsigned int cValue, SecFormatBuf *formatBuf, SecBuffer *buffer) +{ +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT)) && !(defined(__hpux)) && !(defined(SECUREC_ON_SOLARIS)) + attr->flags &= ~SECUREC_FLAG_LEADZERO; +#endif + +#ifdef SECUREC_FOR_WCHAR + attr->bufferIsWide = 1; + if (attr->flags & SECUREC_FLAG_SHORT) { +#if SECUREC_HAVE_MBTOWC + /* multibyte character to wide character */ + char tmpChar[2]; /* One character string, length is 2 */ + tmpChar[0] = (char)(cValue & 0x00ff); + tmpChar[1] = '\0'; + + if (mbtowc(buffer->wStr, tmpChar, sizeof(tmpChar)) < 0) { + return -1; + } +#else + return -1; +#endif + } else { + buffer->wStr[0] = (wchar_t)cValue; + } + formatBuf->wStr = buffer->wStr; + return 1; /* only 1 wide character */ +#else /* SECUREC_FOR_WCHAR */ + attr->bufferIsWide = 0; + if (attr->flags & (SECUREC_FLAG_LONG | SECUREC_FLAG_WIDECHAR)) { +#if SECUREC_HAVE_WCTOMB + wchar_t wChar = (wchar_t)cValue; + int textLen; + /* wide character to multibyte character */ + SECUREC_MASK_MSVC_CRT_WARNING + textLen = wctomb(buffer->str, wChar); + SECUREC_END_MASK_MSVC_CRT_WARNING + if (textLen < 0) { + return -1; + } + formatBuf->str = buffer->str; + return textLen; +#else + return -1; +#endif + } else { + /* get multibyte character from argument */ + unsigned short temp; + temp = (unsigned short)cValue; + buffer->str[0] = (char)temp; + formatBuf->str = buffer->str; + return 1; /* only 1 character */ + } +#endif + +} + +/* literal string to print null ptr, define it as array rather than const text area + * is to avoid gcc warning with pointing const text with variable + */ +#if SECUREC_HAVE_WCHART +static wchar_t g_wStrNullString[SECUREC_NULL_STRING_SIZE] = { L'(', L'n', L'u', L'l', L'l', L')', L'\0', L'\0' }; +#endif +static char g_strNullString[SECUREC_NULL_STRING_SIZE] = "(null)"; + +static int SecDecodeTypeSchar(const SecFormatAttr *attr, SecFormatBuf *formatBuf) +{ + int finalPrecision = (attr->precision == -1) ? SECUREC_INT_MAX : attr->precision; + int textLen; + + if (formatBuf->str == NULL) { /* NULL passed, use special string */ + formatBuf->str = g_strNullString; + } + if (finalPrecision == SECUREC_INT_MAX) { + /* precision NOT assigned */ + /* The strlen performance is high when the string length is greater than 32 */ + textLen = (int)strlen(formatBuf->str); + } else { + /* precision assigned */ + size_t tmpLen; + SECUREC_CALC_STR_LEN(formatBuf->str, (size_t)(unsigned int)finalPrecision, &tmpLen); + textLen = (int)tmpLen; + } + return textLen; +} + +#if SECUREC_HAVE_WCHART +static int SecDecodeTypeSwchar(SecFormatAttr *attr, SecFormatBuf *formatBuf) +{ + int finalPrecision = (attr->precision == -1) ? SECUREC_INT_MAX : attr->precision; + int textLen; + + attr->bufferIsWide = 1; + if (formatBuf->wStr == NULL) { /* NULL passed, use special string */ + formatBuf->wStr = g_wStrNullString; + } + /* textLen in wchar_t */ + SECUREC_CALC_WSTR_LEN(formatBuf->wStr, finalPrecision, &textLen); + + return textLen; +} +#endif + +/* + * Decoded string identifier + */ +static int SecDecodeTypeS(SecFormatAttr *attr, char *argPtr, SecFormatBuf *formatBuf) +{ + int textLen; +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT)) && (!defined(SECUREC_ON_UNIX)) + attr->flags &= ~SECUREC_FLAG_LEADZERO; +#endif + formatBuf->str = argPtr; +#ifdef SECUREC_FOR_WCHAR +#if defined(SECUREC_COMPATIBLE_LINUX_FORMAT) + if (!(attr->flags & SECUREC_FLAG_LONG)) { + attr->flags |= SECUREC_FLAG_SHORT; + } +#endif + if (attr->flags & SECUREC_FLAG_SHORT) { + /* textLen now contains length in multibyte chars */ + textLen = SecDecodeTypeSchar(attr, formatBuf); + } else { + /* textLen now contains length in wide chars */ + textLen = SecDecodeTypeSwchar(attr, formatBuf); + } +#else /* SECUREC_FOR_WCHAR */ + if (attr->flags & (SECUREC_FLAG_LONG | SECUREC_FLAG_WIDECHAR)) { + /* textLen now contains length in wide chars */ +#if SECUREC_HAVE_WCHART + textLen = SecDecodeTypeSwchar(attr, formatBuf); +#else + textLen = 0; +#endif + } else { + /* textLen now contains length in multibyte chars */ + textLen = SecDecodeTypeSchar(attr, formatBuf); + } +#endif /* SECUREC_FOR_WCHAR */ + return textLen; +} + +/* + * Write one character to dest buffer + */ +static void SecOutputOneChar(SecChar ch, SecPrintfStream *stream, int *counter) +{ + /* normal state, write character */ + if (SECUREC_IS_REST_BUF_ENOUGH(stream, 1)) { /* only one char */ + SECUREC_SAFE_WRITE_CHAR(ch, stream, counter); /* char * cast to wchar * */ + } else { +#ifdef SECUREC_FOR_WCHAR + SecWriteCharW(ch, stream, counter); +#else + /* optimize function call to code */ + *counter = -1; + stream->count = -1; +#endif + } +} + +/* + * Check precison in format + */ +static int SecDecodePrecision(SecChar ch, SecFormatAttr *formatAttr) +{ + if (formatAttr->dynPrecision == 0) { + /* add digit to current precision */ + if (SECUREC_MUL_TEN_ADD_BEYOND_MAX(formatAttr->precision)) { + return -1; + } + formatAttr->precision = (int)SECUREC_MUL_TEN((unsigned int)formatAttr->precision) + + (unsigned char)(ch - SECUREC_CHAR('0')); + } else { + if (formatAttr->precision < 0) { + formatAttr->precision = -1; + } + if (formatAttr->precision > SECUREC_MAX_WIDTH_LEN) { + return -1; + } + } + return 0; +} + + +/* + * Check width in format + */ +static int SecDecodeWidth(SecChar ch, SecFormatAttr *formatAttr, SecFmtState lastState) +{ + if (formatAttr->dynWidth == 0) { + if (lastState != STAT_WIDTH) { + formatAttr->fldWidth = 0; + } + if (SECUREC_MUL_TEN_ADD_BEYOND_MAX(formatAttr->fldWidth)) { + return -1; + } + formatAttr->fldWidth = (int)SECUREC_MUL_TEN((unsigned int)formatAttr->fldWidth) + + (unsigned char)(ch - SECUREC_CHAR('0')); + } else { + if (formatAttr->fldWidth < 0) { + formatAttr->flags |= SECUREC_FLAG_LEFT; + formatAttr->fldWidth = (-formatAttr->fldWidth); + if (formatAttr->fldWidth > SECUREC_MAX_WIDTH_LEN) { + return -1; + } + } + } + return 0; +} +#ifdef SECUREC_FOR_WCHAR +/* + * Formatting output core functions for wchar version.Called by a function such as vswprintf_s + * argList must not be declare as const + */ +static int SecOutputSW(SecPrintfStream *stream, const wchar_t *cFormat, va_list argList) +#else +/* + * Formatting output core functions for char version.Called by a function such as vsnprintf_s + */ +static int SecOutputS(SecPrintfStream *stream, const char *cFormat, va_list argList) +#endif +{ + const SecChar *format = cFormat; +#if SECUREC_ENABLE_SPRINTF_FLOAT + char *floatBuf = NULL; +#endif + SecFormatBuf formatBuf; + static const char *itoaUpperDigits = "0123456789ABCDEFX"; + static const char *itoaLowerDigits = "0123456789abcdefx"; + const char *digits = itoaUpperDigits; + unsigned int radix = SECUREC_RADIX_DECIMAL; + int charsOut; /* characters written */ + int prefixLen = 0; /* Must be initialized or compiler alerts */ + int padding = 0; + int textLen; /* length of the text */ + int noOutput = 0; /* Must be initialized or compiler alerts */ + SecFmtState state; + SecFmtState lastState; + SecChar prefix[SECUREC_PREFIX_LEN] = { 0 }; + SecChar ch; /* currently read character */ + static const unsigned char stateTable[SECUREC_STATE_TABLE_SIZE] = { + /* type 0: nospecial meanin; + * 1: '%'; + * 2: '.' + * 3: '*' + * 4: '0' + * 5: '1' ... '9' + * 6: ' ', '+', '-', '#' + * 7: 'h', 'l', 'L', 'F', 'w' , 'N','z','q','t','j' + * 8: 'd','o','u','i','x','X','e','f','g' + */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x06, 0x00, 0x06, 0x02, 0x00, + 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x08, 0x00, 0x07, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x08, 0x07, 0x00, 0x07, 0x00, 0x00, 0x08, + 0x08, 0x07, 0x00, 0x08, 0x07, 0x08, 0x00, 0x07, 0x08, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, + /* fill zero for normal char 128 byte for 0x80 - 0xff */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* state 0: normal + * 1: percent + * 2: flag + * 3: width + * 4: dot + * 5: precis + * 6: size + * 7: type + * 8: invalid + */ + 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x01, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x01, 0x00, 0x00, 0x04, 0x04, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x03, 0x03, 0x08, 0x05, + 0x08, 0x08, 0x00, 0x00, 0x00, 0x02, 0x02, 0x03, 0x05, 0x05, 0x08, 0x00, 0x00, 0x00, 0x03, 0x03, + 0x03, 0x05, 0x05, 0x08, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x00, + 0x00 + }; + + SecFormatAttr formatAttr; + SecBuffer buffer; + formatAttr.flags = 0; + formatAttr.bufferIsWide = 0; /* flag for buffer contains wide chars */ + formatAttr.fldWidth = 0; + formatAttr.precision = 0; + formatAttr.dynWidth = 0; + formatAttr.dynPrecision = 0; + charsOut = 0; + textLen = 0; + state = STAT_NORMAL; /* starting state */ + formatBuf.str = NULL; + + /* loop each format character */ + /* remove format != NULL */ + while ((ch = *format) != SECUREC_CHAR('\0') && charsOut >= 0) { + ++format; + lastState = state; + state = SECUREC_DECODE_STATE(ch, stateTable, lastState); + switch (state) { + case STAT_NORMAL: + SecOutputOneChar(ch, stream, &charsOut); + continue; + case STAT_PERCENT: + /* set default values */ + prefixLen = 0; + noOutput = 0; + formatAttr.flags = 0; + formatAttr.fldWidth = 0; + formatAttr.precision = -1; + formatAttr.bufferIsWide = 0; + formatAttr.dynWidth = 0; + formatAttr.dynPrecision = 0; + break; + case STAT_FLAG: + /* set flag based on which flag character */ + SecDecodeFlags(ch, &formatAttr); + break; + case STAT_WIDTH: + /* update width value */ + if (ch == SECUREC_CHAR('*')) { + /* get width */ + formatAttr.fldWidth = (int)va_arg(argList, int); + formatAttr.dynWidth = 1; + } else { + formatAttr.dynWidth = 0; + } + if (SecDecodeWidth(ch, &formatAttr, lastState) != 0) { + return -1; + } + break; + case STAT_DOT: + formatAttr.precision = 0; + break; + case STAT_PRECIS: + /* update precison value */ + if (ch == SECUREC_CHAR('*')) { + /* get precision from arg list */ + formatAttr.precision = (int)va_arg(argList, int); + formatAttr.dynPrecision = 1; + } else { + formatAttr.dynPrecision = 0; + } + if (SecDecodePrecision(ch, &formatAttr) != 0) { + return -1; + } + break; + case STAT_SIZE: + /* read a size specifier, set the formatAttr.flags based on it */ + if (SecDecodeSize(ch, &formatAttr, &format) != 0) { + /* Compatibility code for "%I" just print I */ + SecOutputOneChar(ch, stream, &charsOut); + state = STAT_NORMAL; + continue; + } + break; + case STAT_TYPE: + switch (ch) { + case SECUREC_CHAR('C'): + /* wide char */ + if (!(formatAttr.flags & (SECUREC_FLAG_SHORT | SECUREC_FLAG_LONG | SECUREC_FLAG_WIDECHAR))) { +#ifdef SECUREC_FOR_WCHAR + formatAttr.flags |= SECUREC_FLAG_SHORT; +#else + formatAttr.flags |= SECUREC_FLAG_WIDECHAR; +#endif + } + /* fall-through */ + /* FALLTHRU */ + case SECUREC_CHAR('c'): + do { + unsigned int cValue = (unsigned int)va_arg(argList, int); + textLen = SecDecodeTypeC(&formatAttr, cValue, &formatBuf, &buffer); + if (textLen < 0) { + noOutput = 1; + } + } SECUREC_WHILE_ZERO; + break; + case SECUREC_CHAR('S'): /* wide char string */ + if (!(formatAttr.flags & (SECUREC_FLAG_SHORT | SECUREC_FLAG_LONG | SECUREC_FLAG_WIDECHAR))) { +#ifndef SECUREC_FOR_WCHAR + formatAttr.flags |= SECUREC_FLAG_WIDECHAR; +#else + formatAttr.flags |= SECUREC_FLAG_SHORT; +#endif + } + /* fall-through */ + /* FALLTHRU */ + case SECUREC_CHAR('s'): + do { + char *argPtr = (char *)va_arg(argList, char *); + textLen = SecDecodeTypeS(&formatAttr, argPtr, &formatBuf); + } SECUREC_WHILE_ZERO; + break; + case SECUREC_CHAR('n'): + /* higher risk disable it */ + return -1; + case SECUREC_CHAR('E'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('F'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('G'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('A'): /* fall-through */ /* FALLTHRU */ + /* convert format char to lower , use Explicit conversion to clean up compilation warning */ + ch = (SecChar)(ch + ((SecChar)(SECUREC_CHAR('a')) - (SECUREC_CHAR('A')))); + /* fall-through */ + /* FALLTHRU */ + case SECUREC_CHAR('e'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('f'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('g'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('a'): +#if SECUREC_ENABLE_SPRINTF_FLOAT + do { + int bufferSize = 0; /* size of formatBuf.str */ + /* floating point conversion */ + formatBuf.str = buffer.str; /* output buffer for float string with default size */ + + /* compute the precision value */ + if (formatAttr.precision < 0) { + formatAttr.precision = SECUREC_FLOAT_DEFAULT_PRECISION; + } else if (formatAttr.precision == 0 && ch == SECUREC_CHAR('g')) { + formatAttr.precision = 1; + } + + /* calc buffer size to store double value + * The maximum length of SECUREC_MAX_WIDTH_LEN is enough + */ + if (formatAttr.flags & SECUREC_FLAG_LONG_DOUBLE) { + if (formatAttr.precision > (SECUREC_MAX_WIDTH_LEN - SECUREC_FLOAT_BUFSIZE_LB)) { + noOutput = 1; + break; + } + /* Long double needs to meet the basic print length */ + bufferSize = SECUREC_FLOAT_BUFSIZE_LB + formatAttr.precision + SECUREC_FLOAT_BUF_EXT; + } else { + if (formatAttr.precision > (SECUREC_MAX_WIDTH_LEN - SECUREC_FLOAT_BUFSIZE)) { + noOutput = 1; + break; + } + /* Double needs to meet the basic print length */ + bufferSize = SECUREC_FLOAT_BUFSIZE + formatAttr.precision + SECUREC_FLOAT_BUF_EXT; + } + if (formatAttr.fldWidth > bufferSize) { + bufferSize = formatAttr.fldWidth + SECUREC_FLOAT_BUF_EXT; + } + + if (bufferSize > SECUREC_BUFFER_SIZE) { + /* the current vlaue of SECUREC_BUFFER_SIZE could NOT store the + * formatted float string + */ + floatBuf = (char *)SECUREC_MALLOC(((size_t)(unsigned int)bufferSize)); + if (floatBuf != NULL) { + formatBuf.str = floatBuf; + } else { + noOutput = 1; + break; + } + } + + do { + /* add following code to call system sprintf API for float number */ + const SecChar *pFloatFmt = format - 2; /* sub 2 to the position before 'f' or 'g' */ + int k; + int fFmtStrLen; + char fFmtBuf[SECUREC_FMT_STR_LEN]; + char *fFmtStr = fFmtBuf; + char *fFmtHeap = NULL; /* to clear warning */ + + while (SECUREC_CHAR('%') != *pFloatFmt) { /* must meet '%' */ + --pFloatFmt; + } + fFmtStrLen = (int)((format - pFloatFmt) + 1); /* with ending terminator */ + if (fFmtStrLen > SECUREC_FMT_STR_LEN) { + /* if SECUREC_FMT_STR_LEN is NOT enough, alloc a new buffer */ + fFmtHeap = (char *)SECUREC_MALLOC((size_t)((unsigned int)fFmtStrLen)); + if (fFmtHeap == NULL) { + noOutput = 1; + break; + } else { + for (k = 0; k < fFmtStrLen - 1; ++k) { + /* convert wchar to char */ + fFmtHeap[k] = (char)(pFloatFmt[k]); /* copy the format string */ + } + fFmtHeap[k] = '\0'; + + fFmtStr = fFmtHeap; + } + } else { + /* purpose of the repeat code is to solve the tool alarm Redundant_Null_Check */ + for (k = 0; k < fFmtStrLen - 1; ++k) { + /* convert wchar to char */ + fFmtBuf[k] = (char)(pFloatFmt[k]); /* copy the format string */ + } + fFmtBuf[k] = '\0'; + } + + if (formatAttr.flags & SECUREC_FLAG_LONG_DOUBLE) { +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT + long double tmp = (long double)va_arg(argList, long double); + textLen = SecFormatLongDboule(formatBuf.str, &formatAttr, fFmtStr, tmp); +#else + double tmp = (double)va_arg(argList, double); + textLen = SecFormatDboule(formatBuf.str, &formatAttr, fFmtStr, tmp); +#endif + } else { + double tmp = (double)va_arg(argList, double); + textLen = SecFormatDboule(formatBuf.str, &formatAttr, fFmtStr, tmp); + } + + if (fFmtHeap != NULL) { + /* if buffer is alloced on heap, free it */ + SECUREC_FREE(fFmtHeap); + fFmtHeap = NULL; + /* to clear e438 last value assigned not used , the compiler will + * optimize this code + */ + (void)fFmtHeap; + } + if (textLen < 0 || textLen >= bufferSize) { + /* bufferSize is large enough, just validation the return value */ + noOutput = 1; + break; + } + + /* no padding ,this variable to calculate amount of padding */ + formatAttr.fldWidth = textLen; + prefixLen = 0; /* no padding ,this variable to calculate amount of padding */ + formatAttr.flags = 0; /* clear all internal formatAttr.flags */ + break; + } SECUREC_WHILE_ZERO; + } SECUREC_WHILE_ZERO; + break; +#else + return -1; +#endif + case SECUREC_CHAR('p'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('X'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('x'): + /* unsigned lower hex output */ + digits = itoaLowerDigits; + radix = SECUREC_RADIX_HEX; + switch (ch) { + case SECUREC_CHAR('p'): + /* print a pointer */ +#if defined(SECUREC_COMPATIBLE_WIN_FORMAT) + formatAttr.flags &= ~SECUREC_FLAG_LEADZERO; +#else + formatAttr.flags |= SECUREC_FLAG_POINTER; +#endif +#ifdef SECUREC_ON_64BITS + formatAttr.flags |= SECUREC_FLAG_I64; /* converting an int64 */ +#else + formatAttr.flags |= SECUREC_FLAG_LONG; /* converting a long */ +#endif + +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) || defined(SECUREC_VXWORKS_PLATFORM)) && (!defined(SECUREC_ON_UNIX)) +#if defined(SECUREC_VXWORKS_PLATFORM) + formatAttr.precision = 1; +#else + formatAttr.precision = 0; +#endif + formatAttr.flags |= SECUREC_FLAG_ALTERNATE; /* "0x" is not default prefix in UNIX */ + break; +#else + /* not linux vxwoks */ +#if defined(_AIX) || defined(SECUREC_ON_SOLARIS) + formatAttr.precision = 1; +#else + formatAttr.precision = 2 * sizeof(void *); /* 2 precision of different systems */ +#endif +#endif + +#if defined(SECUREC_ON_UNIX) + break; +#endif + /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('X'): /* fall-through */ /* FALLTHRU */ + /* unsigned upper hex output */ + digits = itoaUpperDigits; + break; + default: + break; + } + + if (formatAttr.flags & SECUREC_FLAG_ALTERNATE) { + /* alternate form means '0x' prefix */ + prefix[0] = SECUREC_CHAR('0'); + prefix[1] = (SecChar)(digits[16]); /* 16 for 'x' or 'X' */ + +#if (defined(SECUREC_COMPATIBLE_LINUX_FORMAT) || defined(SECUREC_VXWORKS_PLATFORM)) + if (ch == 'p') { + prefix[1] = SECUREC_CHAR('x'); + } +#endif +#if defined(_AIX) || defined(SECUREC_ON_SOLARIS) + if (ch == 'p') { + prefixLen = 0; + } else { + prefixLen = SECUREC_PREFIX_LEN; + } +#else + prefixLen = SECUREC_PREFIX_LEN; +#endif + + } + /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('i'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('d'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('u'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('o'): /* fall-through */ /* FALLTHRU */ + switch (ch) { + case SECUREC_CHAR('i'): /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('d'): /* fall-through */ /* FALLTHRU */ + /* signed decimal output */ + formatAttr.flags |= SECUREC_FLAG_SIGNED; + /* fall-through */ /* FALLTHRU */ + case SECUREC_CHAR('u'): + radix = SECUREC_RADIX_DECIMAL; + break; + case SECUREC_CHAR('o'): + /* unsigned octal output */ + radix = SECUREC_RADIX_OCTAL; + if (formatAttr.flags & SECUREC_FLAG_ALTERNATE) { + /* alternate form means force a leading 0 */ + formatAttr.flags |= SECUREC_FLAG_FORCE_OCTAL; + } + break; + default: + break; + } + + do { + + SecUnsignedInt64 number = 0; /* number to convert */ + SecInt64 l; /* temp long value */ + + /* read argument into variable l */ + if (formatAttr.flags & SECUREC_FLAG_I64) { + l = (SecInt64)va_arg(argList, SecInt64); + } else if (formatAttr.flags & SECUREC_FLAG_LONGLONG) { + l = (SecInt64)va_arg(argList, SecInt64); + } else +#ifdef SECUREC_ON_64BITS + if (formatAttr.flags & SECUREC_FLAG_LONG) { + l = (long)va_arg(argList, long); + } else +#endif /* SECUREC_ON_64BITS */ + if (formatAttr.flags & SECUREC_FLAG_CHAR) { + if (formatAttr.flags & SECUREC_FLAG_SIGNED) { + l = (char)va_arg(argList, int); /* sign extend */ + if (l >= 128) { /* 128 on some platform, char is always unsigned */ + SecUnsignedInt64 tmpL = (SecUnsignedInt64)l; + unsigned char tmpCh = (unsigned char)(~(tmpL)); + l = tmpCh + 1; + formatAttr.flags |= SECUREC_FLAG_NEGATIVE; + } + } else { + l = (unsigned char)va_arg(argList, int); /* zero-extend */ + } + + } else if (formatAttr.flags & SECUREC_FLAG_SHORT) { + if (formatAttr.flags & SECUREC_FLAG_SIGNED) { + l = (short)va_arg(argList, int); /* sign extend */ + } else { + l = (unsigned short)va_arg(argList, int); /* zero-extend */ + } + + } +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT + else if (formatAttr.flags & SECUREC_FLAG_PTRDIFF) { + l = (ptrdiff_t)va_arg(argList, ptrdiff_t); /* sign extend */ + } else if (formatAttr.flags & SECUREC_FLAG_SIZE) { + if (formatAttr.flags & SECUREC_FLAG_SIGNED) { + /* No suitable macros were found to handle the branch */ + if (SecIsSameSize(sizeof(size_t), sizeof(long))) { + l = va_arg(argList, long); /* sign extend */ + } else if (SecIsSameSize(sizeof(size_t), sizeof(long long))) { + l = va_arg(argList, long long); /* sign extend */ + } else { + l = va_arg(argList, int); /* sign extend */ + } + } else { + l = (SecInt64)(size_t)va_arg(argList, size_t); /* sign extend */ + } + } else if (formatAttr.flags & SECUREC_FLAG_INTMAX) { + if (formatAttr.flags & SECUREC_FLAG_SIGNED) { + l = va_arg(argList, SecInt64); /* sign extend */ + } else { + /* sign extend */ + l = (SecInt64)(SecUnsignedInt64)va_arg(argList, SecUnsignedInt64); + } + } +#endif + else { + if (formatAttr.flags & SECUREC_FLAG_SIGNED) { + l = va_arg(argList, int); /* sign extend */ + } else { + l = (unsigned int)va_arg(argList, int); /* zero-extend */ + } + + } + + /* check for negative; copy into number */ + if ((formatAttr.flags & SECUREC_FLAG_SIGNED) && l < 0) { + number = (SecUnsignedInt64)(-l); + formatAttr.flags |= SECUREC_FLAG_NEGATIVE; + } else { + number = (SecUnsignedInt64)l; + } + + if (((formatAttr.flags & SECUREC_FLAG_I64) == 0) && +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT + ((formatAttr.flags & SECUREC_FLAG_INTMAX) == 0) && +#endif +#ifdef SECUREC_ON_64BITS + ((formatAttr.flags & SECUREC_FLAG_PTRDIFF) == 0) && + ((formatAttr.flags & SECUREC_FLAG_SIZE) == 0) && +#if !defined(SECUREC_COMPATIBLE_WIN_FORMAT) /* on window 64 system sizeof long is 32bit */ + ((formatAttr.flags & SECUREC_FLAG_LONG) == 0) && +#endif +#endif + ((formatAttr.flags & SECUREC_FLAG_LONGLONG) == 0)) { + + number &= 0xffffffff; /* use 0xffffffff as 32 bit mask */ + } + + /* check precision value for default */ + if (formatAttr.precision < 0) { + formatAttr.precision = 1; /* default precision */ + } else { +#if defined(SECUREC_COMPATIBLE_WIN_FORMAT) + formatAttr.flags &= ~SECUREC_FLAG_LEADZERO; +#else + if (!(formatAttr.flags & SECUREC_FLAG_POINTER)) { + formatAttr.flags &= ~SECUREC_FLAG_LEADZERO; + } +#endif + if (formatAttr.precision > SECUREC_MAX_PRECISION) { + formatAttr.precision = SECUREC_MAX_PRECISION; + } + } + + /* Check if data is 0; if so, turn off hex prefix, + * 'p' add 0x prefix, otherwise not add prefix + */ + if (number == 0) { +#if !(defined(SECUREC_VXWORKS_PLATFORM) || defined(__hpux)) + prefixLen = 0; +#else + if ((ch == 'p') && (formatAttr.flags & SECUREC_FLAG_ALTERNATE)) { + prefixLen = SECUREC_PREFIX_LEN; + } else { + prefixLen = 0; + } +#endif + } + + /* Convert data to ASCII */ + formatBuf.str = &buffer.str[SECUREC_BUFFER_SIZE]; + + if (number > 0) { +#ifdef SECUREC_ON_64BITS + switch (radix) { + /* the compiler will optimize each one */ + case SECUREC_RADIX_DECIMAL: + SECUREC_SPECIAL_QWORD_BASE10(number); + break; + case SECUREC_RADIX_HEX: + SECUREC_SPECIAL_QWORD(number, SECUREC_RADIX_HEX); + break; + case SECUREC_RADIX_OCTAL: + SECUREC_SPECIAL_QWORD(number, SECUREC_RADIX_OCTAL); + break; + default: + break; + } +#else /* for 32 bits system */ + if (number <= 0xFFFFFFFFUL) { + /* in most case, the value to be converted is small value */ + SecUnsignedInt32 n32Tmp = (SecUnsignedInt32)number; + switch (radix) { + case SECUREC_RADIX_HEX: + SECUREC_SPECIAL_DWORD(n32Tmp, SECUREC_RADIX_HEX); + break; + case SECUREC_RADIX_OCTAL: + SECUREC_SPECIAL_DWORD(n32Tmp, SECUREC_RADIX_OCTAL); + break; + +#ifdef _AIX + /* the compiler will optimize div 10 */ + case SECUREC_RADIX_DECIMAL: + SECUREC_SPECIAL_DWORD(n32Tmp, SECUREC_RADIX_DECIMAL); + break; +#else + case SECUREC_RADIX_DECIMAL: + do { + /* fast div 10 */ + SecUnsignedInt32 q; + SecUnsignedInt32 r; + do { + *--formatBuf.str = digits[n32Tmp % SECUREC_RADIX_DECIMAL]; + q = (n32Tmp >> 1) + (n32Tmp >> 2); /* fast div magic 2 */ + q = q + (q >> 4); /* fast div magic 4 */ + q = q + (q >> 8); /* fast div magic 8 */ + q = q + (q >> 16); /* fast div magic 16 */ + q = q >> 3; /* fast div magic 3 */ + r = n32Tmp - SECUREC_MUL_TEN(q); + n32Tmp = (r > 9) ? (q + 1) : q; /* fast div magic 9 */ + } while (n32Tmp != 0); + } SECUREC_WHILE_ZERO; + break; +#endif + default: + break; + } /* end switch */ + } else { + /* the value to be converted is greater than 4G */ +#if defined(SECUREC_VXWORKS_VERSION_5_4) + do { + SecUnsignedInt32 digit = 0; /* ascii value of digit */ + SecUnsignedInt64 quotient = 0; + if (SecU64Div32(number,(SecUnsignedInt32)radix, "ient, &digit) != 0) { + noOutput = 1; + break; + } + *--formatBuf.str = digits[digit]; + number = quotient; + } while (number != 0); +#else + switch (radix) { + /* the compiler will optimize div 10 */ + case SECUREC_RADIX_DECIMAL: + SECUREC_SPECIAL_QWORD_BASE10(number); + break; + case SECUREC_RADIX_OCTAL: + SECUREC_SPECIAL_QWORD(number, SECUREC_RADIX_OCTAL); + break; + case SECUREC_RADIX_HEX: + SECUREC_SPECIAL_QWORD(number, SECUREC_RADIX_HEX); + break; + default: + break; + } +#endif + } +#endif + + } + /* compute length of number,.if textLen > 0, then formatBuf.str must be in buffer.str */ + textLen = (int)(size_t)((char *)&buffer.str[SECUREC_BUFFER_SIZE] - formatBuf.str); + if (formatAttr.precision > textLen) { + int ii; + for (ii = 0; ii < formatAttr.precision - textLen; ++ii) { + *--formatBuf.str = '0'; + } + textLen = formatAttr.precision; + } + + /* Force a leading zero if FORCEOCTAL flag set */ + if ((formatAttr.flags & SECUREC_FLAG_FORCE_OCTAL) && + (textLen == 0 || formatBuf.str[0] != '0')) { + *--formatBuf.str = '0'; + ++textLen; /* add a zero */ + } + } SECUREC_WHILE_ZERO; + break; + default: + break; + } + + while (noOutput < 1) { + if (formatAttr.flags & SECUREC_FLAG_SIGNED) { + if (formatAttr.flags & SECUREC_FLAG_NEGATIVE) { + /* prefix is a '-' */ + prefix[0] = SECUREC_CHAR('-'); + prefixLen = 1; + } else if (formatAttr.flags & SECUREC_FLAG_SIGN) { + /* prefix is '+' */ + prefix[0] = SECUREC_CHAR('+'); + prefixLen = 1; + } else if (formatAttr.flags & SECUREC_FLAG_SIGN_SPACE) { + /* prefix is ' ' */ + prefix[0] = SECUREC_CHAR(' '); + prefixLen = 1; + } + } + +#if defined(SECUREC_COMPATIBLE_LINUX_FORMAT) && (!defined(SECUREC_ON_UNIX)) + if ((formatAttr.flags & SECUREC_FLAG_POINTER) && (textLen == 0)) { + formatAttr.flags &= ~SECUREC_FLAG_LEADZERO; + formatBuf.str = &buffer.str[SECUREC_BUFFER_SIZE - 1]; + *formatBuf.str-- = '\0'; + *formatBuf.str-- = ')'; + *formatBuf.str-- = 'l'; + *formatBuf.str-- = 'i'; + *formatBuf.str-- = 'n'; + *formatBuf.str = '('; + textLen = 5; /* length of (nil) is 5 */ + } +#endif + + /* calculate amount of padding */ + padding = (formatAttr.fldWidth - textLen) - prefixLen; + + /* put out the padding, prefix, and text, in the correct order */ + + if (!(formatAttr.flags & (SECUREC_FLAG_LEFT | SECUREC_FLAG_LEADZERO)) && padding > 0) { + /* pad on left with blanks */ + if (SECUREC_IS_REST_BUF_ENOUGH(stream, padding)) { + /* char * cast to wchar * */ + SECUREC_SAFE_PADDING(SECUREC_CHAR(' '), padding, stream, &charsOut); + } else { + SECUREC_WRITE_MULTI_CHAR(SECUREC_CHAR(' '), padding, stream, &charsOut); + } + } + + /* write prefix */ + if (prefixLen > 0) { + SecChar *pPrefix = prefix; + if (SECUREC_IS_REST_BUF_ENOUGH(stream, prefixLen)) { + /* max prefix len is 2, use loop copy */ /* char * cast to wchar * in WCHAR version */ + SECUREC_SAFE_WRITE_STR_OPT(pPrefix, prefixLen, stream, &charsOut); + } else { + SECUREC_WRITE_STRING(prefix, prefixLen, stream, &charsOut); + } + } + + if ((formatAttr.flags & SECUREC_FLAG_LEADZERO) && !(formatAttr.flags & SECUREC_FLAG_LEFT) + && padding > 0) { + /* write leading zeros */ + if (SECUREC_IS_REST_BUF_ENOUGH(stream, padding)) { + /* char * cast to wchar * */ + SECUREC_SAFE_PADDING(SECUREC_CHAR('0'), padding, stream, &charsOut); + } else { + SECUREC_WRITE_MULTI_CHAR(SECUREC_CHAR('0'), padding, stream, &charsOut); + } + } + + /* write text */ +#ifndef SECUREC_FOR_WCHAR + if (formatAttr.bufferIsWide != 0 && (textLen > 0)) { +#if SECUREC_HAVE_WCTOMB + wchar_t *p = formatBuf.wStr; + int count = textLen; + while (count > 0) { + char tmpBuf[SECUREC_MB_LEN + 1]; + SECUREC_MASK_MSVC_CRT_WARNING + int retVal = wctomb(tmpBuf, *p); + SECUREC_END_MASK_MSVC_CRT_WARNING + if (retVal <= 0) { + charsOut = -1; + break; + } + SECUREC_WRITE_STRING(tmpBuf, retVal, stream, &charsOut); + --count; + ++p; + } +#else + charsOut = -1; + break; +#endif + } else { + if (SECUREC_IS_REST_BUF_ENOUGH(stream, textLen)) { + SECUREC_SAFE_WRITE_STR(formatBuf.str, textLen, stream, &charsOut); + } else { + SECUREC_WRITE_STRING(formatBuf.str, textLen, stream, &charsOut); + } + } +#else /* SECUREC_FOR_WCHAR */ + if (formatAttr.bufferIsWide == 0 && textLen > 0) { +#if SECUREC_HAVE_MBTOWC + int count = textLen; + char *p = formatBuf.str; + + while (count > 0) { + wchar_t wChar = L'\0'; + int retVal = mbtowc(&wChar, p, (size_t)MB_CUR_MAX); + if (retVal <= 0) { + charsOut = -1; + break; + } + SecWriteCharW(wChar, stream, &charsOut); + p += retVal; + count -= retVal; + } +#else + charsOut = -1; + break; +#endif + } else { + if (SECUREC_IS_REST_BUF_ENOUGH(stream, textLen)) { + /* char * cast to wchar * */ + SECUREC_SAFE_WRITE_STR(formatBuf.wStr, textLen, stream, &charsOut); + } else { + SECUREC_WRITE_STRING(formatBuf.wStr, textLen, stream, &charsOut); + } + } +#endif /* SECUREC_FOR_WCHAR */ + + if (charsOut >= 0 && (formatAttr.flags & SECUREC_FLAG_LEFT) && padding > 0) { + /* pad on right with blanks */ + if (SECUREC_IS_REST_BUF_ENOUGH(stream, padding)) { + /* char * cast to wchar * */ + SECUREC_SAFE_PADDING(SECUREC_CHAR(' '), padding, stream, &charsOut); + } else { + SECUREC_WRITE_MULTI_CHAR(SECUREC_CHAR(' '), padding, stream, &charsOut); + } + } + break; + } +#if SECUREC_ENABLE_SPRINTF_FLOAT + if (floatBuf != NULL) { + SECUREC_FREE(floatBuf); + floatBuf = NULL; + } +#endif + break; + case STAT_INVALID: + return -1; + default: + return -1; /* input format is wrong, directly return */ + } + } + + if (state != STAT_NORMAL && state != STAT_TYPE) { + return -1; + } + + return charsOut; /* the number of characters written */ +} +#endif /* OUTPUT_INL_2B263E9C_43D8_44BB_B17A_6D2033DECEE5 */ + diff --git a/third_party/securec/src/scanf_s.c b/third_party/securec/src/scanf_s.c new file mode 100644 index 0000000..e4b0e60 --- /dev/null +++ b/third_party/securec/src/scanf_s.c @@ -0,0 +1,55 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * + * The scanf_s function is equivalent to fscanf_s with the argument stdin interposed before the arguments to scanf_s + * The scanf_s function reads data from the standard input stream stdin and + * writes the data into the location that's given by argument. Each argument + * must be a pointer to a variable of a type that corresponds to a type specifier + * in format. If copying occurs between strings that overlap, the behavior is + * undefined. + * + * + * format Format control string. + * ... Optional arguments. + * + * + * ... The converted value stored in user assigned address + * + * + * Returns the number of fields successfully converted and assigned; + * the return value does not include fields that were read but not assigned. + * A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ + +int scanf_s(const char *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vscanf_s(format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} + + diff --git a/third_party/securec/src/secinput.h b/third_party/securec/src/secinput.h new file mode 100644 index 0000000..8cd9284 --- /dev/null +++ b/third_party/securec/src/secinput.h @@ -0,0 +1,156 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SEC_INPUT_H_E950DA2C_902F_4B15_BECD_948E99090D9C +#define SEC_INPUT_H_E950DA2C_902F_4B15_BECD_948E99090D9C +#include "securecutil.h" + +#define SECUREC_SCANF_EINVAL (-1) +#define SECUREC_SCANF_ERROR_PARA (-2) + +/* for internal stream flag */ +#define SECUREC_MEM_STR_FLAG 0X01 +#define SECUREC_FILE_STREAM_FLAG 0X02 +#define SECUREC_FROM_STDIN_FLAG 0X04 +#define SECUREC_LOAD_FILE_TO_MEM_FLAG 0X08 + +#define SECUREC_UNINITIALIZED_FILE_POS (-1) +#define SECUREC_BOM_HEADER_SIZE 2 +#define SECUREC_BOM_HEADER_BE_1ST 0xFEU +#define SECUREC_BOM_HEADER_BE_2ST 0xFFU +#define SECUREC_BOM_HEADER_LE_1ST 0xFFU +#define SECUREC_BOM_HEADER_LE_2ST 0xFEU +#define SECUREC_UTF8_BOM_HEADER_SIZE 3 +#define SECUREC_UTF8_BOM_HEADER_1ST 0xEFU +#define SECUREC_UTF8_BOM_HEADER_2ND 0xBBU +#define SECUREC_UTF8_BOM_HEADER_3RD 0xBFU +#define SECUREC_UTF8_LEAD_1ST 0xE0 +#define SECUREC_UTF8_LEAD_2ND 0x80 + +typedef struct { + unsigned int flag; /* mark the properties of input stream */ + int count; /* the size of buffered string in bytes */ + const char *cur; /* the pointer to next read position */ + char *base; /* the pointer to the header of buffered string */ +#if SECUREC_ENABLE_SCANF_FILE + FILE *pf; /* the file pointer */ + long oriFilePos; /* the original position of file offset when fscanf is called */ + int fileRealRead; +#if defined(SECUREC_NO_STD_UNGETC) + unsigned int lastChar; /* the char code of last input */ + int fUnget; /* the boolean flag of pushing a char back to read stream */ +#endif +#endif +} SecFileStream; + + +#define SECUREC_INIT_SEC_FILE_STREAM_COMMON(fileStream, streamFlag, curPtr, strCount) do { \ + (fileStream).flag = (streamFlag); \ + (fileStream).count = (strCount); \ + (fileStream).cur = (curPtr); \ + (fileStream).base = NULL; \ +} SECUREC_WHILE_ZERO + +#if SECUREC_ENABLE_SCANF_FILE +#if defined(SECUREC_NO_STD_UNGETC) +/* This initialization for eliminating redundant initialization. + * Compared with the previous version initialization 0, + * the current code causes the binary size to increase by some bytes + */ +#define SECUREC_INIT_SEC_FILE_STREAM(fileStream, streamFlag, stream, filePos, curPtr, strCount) do { \ + SECUREC_INIT_SEC_FILE_STREAM_COMMON((fileStream), (streamFlag), (curPtr), (strCount)); \ + (fileStream).pf = (stream); \ + (fileStream).oriFilePos = (filePos); \ + (fileStream).fileRealRead = 0; \ + (fileStream).lastChar = 0; \ + (fileStream).fUnget = 0; \ +} SECUREC_WHILE_ZERO +#else +#define SECUREC_INIT_SEC_FILE_STREAM(fileStream, streamFlag, stream, filePos, curPtr, strCount) do { \ + SECUREC_INIT_SEC_FILE_STREAM_COMMON((fileStream), (streamFlag), (curPtr), (strCount)); \ + (fileStream).pf = (stream); \ + (fileStream).oriFilePos = (filePos); \ + (fileStream).fileRealRead = 0; \ +} SECUREC_WHILE_ZERO +#endif +#else /* No SECUREC_ENABLE_SCANF_FILE */ +#define SECUREC_INIT_SEC_FILE_STREAM(fileStream, streamFlag, stream, filePos, curPtr, strCount) do { \ + SECUREC_INIT_SEC_FILE_STREAM_COMMON((fileStream), (streamFlag), (curPtr), (strCount)); \ +} SECUREC_WHILE_ZERO +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + extern int SecInputS(SecFileStream *stream, const char *cFormat, va_list argList); + extern void SecClearDestBuf(const char *buffer, const char *format, va_list argList); +#if SECUREC_IN_KERNEL == 0 + extern int SecInputSW(SecFileStream *stream, const wchar_t *cFormat, va_list argList); + extern void SecClearDestBufW(const wchar_t *buffer, const wchar_t *format, va_list argList); +#endif +/* 20150105 For software and hardware decoupling,such as UMG */ +#if defined(SECUREC_SYSAPI4VXWORKS) +#ifdef feof +#undef feof +#endif + extern int feof(FILE *stream); +#endif + +#if defined(SECUREC_SYSAPI4VXWORKS) || defined(SECUREC_CTYPE_MACRO_ADAPT) +#ifndef isspace +#define isspace(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\r') || ((c) == '\n')) +#endif +#ifndef iswspace +#define iswspace(c) (((c) == L' ') || ((c) == L'\t') || ((c) == L'\r') || ((c) == L'\n')) +#endif +#ifndef isascii +#define isascii(c) (((unsigned char)(c)) <= 0x7f) +#endif +#ifndef isupper +#define isupper(c) ((c) >= 'A' && (c) <= 'Z') +#endif +#ifndef islower +#define islower(c) ((c) >= 'a' && (c) <= 'z') +#endif +#ifndef isalpha +#define isalpha(c) (isupper(c) || (islower(c))) +#endif +#ifndef isdigit +#define isdigit(c) ((c) >= '0' && (c) <= '9') +#endif +#ifndef isxupper +#define isxupper(c) ((c) >= 'A' && (c) <= 'F') +#endif +#ifndef isxlower +#define isxlower(c) ((c) >= 'a' && (c) <= 'f') +#endif +#ifndef isxdigit +#define isxdigit(c) (isdigit(c) || isxupper(c) || isxlower(c)) +#endif +#endif + +#ifdef __cplusplus +} +#endif +/* Reserved file operation macro interface */ +#define SECUREC_LOCK_FILE(s) +#define SECUREC_UNLOCK_FILE(s) +#define SECUREC_LOCK_STDIN(i, s) +#define SECUREC_UNLOCK_STDIN(i, s) +#endif + + diff --git a/third_party/securec/src/securecutil.c b/third_party/securec/src/securecutil.c new file mode 100644 index 0000000..1a44cfb --- /dev/null +++ b/third_party/securec/src/securecutil.c @@ -0,0 +1,74 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Avoid duplicate header files,not include securecutil.h */ +#include "securecutil.h" + + +#if defined(ANDROID) && (SECUREC_HAVE_WCTOMB || SECUREC_HAVE_MBTOWC) +#include +#if SECUREC_HAVE_WCTOMB +/* + * Convert wide characters to narrow multi-bytes + */ +int wctomb(char *s, wchar_t wc) +{ + return wcrtomb(s, wc, NULL); +} +#endif + +#if SECUREC_HAVE_MBTOWC +/* + * Converting narrow multi-byte characters to wide characters + */ +int mbtowc(wchar_t *pwc, const char *s, size_t n) +{ + return mbrtowc(pwc, s, n, NULL); +} +#endif +#endif + +/* high Num << 8 | num of SPC Ver */ +#define SECUREC_C_VERSION (0x5 << 8) +#define SECUREC_SPC_VERSION 7 +#define SECUREC_VERSION_STR "Huawei Secure C V100R001C01SPC007B002" + +/* SPC verNumber<->verStr like: + * 0X201<->C01 + * 0X202<->SPC001 Redefine numbers after this version + * 0X502<->SPC002 + * 0X503<->SPC003 + * ... + * 0X50a<->SPC010 + * 0X50b<->SPC011 + * ... + */ +/* CP verNumber<->verStr like: + * 0X601<->CP0001 + * 0X602<->CP0002 + * ... + */ +const char *GetHwSecureCVersion(unsigned short *verNumber) +{ + if (verNumber != NULL) { + *verNumber = (unsigned short)(SECUREC_C_VERSION | SECUREC_SPC_VERSION); + } + return SECUREC_VERSION_STR; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(GetHwSecureCVersion); +#endif + diff --git a/third_party/securec/src/securecutil.h b/third_party/securec/src/securecutil.h new file mode 100644 index 0000000..98c9aad --- /dev/null +++ b/third_party/securec/src/securecutil.h @@ -0,0 +1,541 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SECURECUTIL_H_46C86578_F8FF_4E49_8E64_9B175241761F +#define SECURECUTIL_H_46C86578_F8FF_4E49_8E64_9B175241761F +#include "securec.h" + +#if (defined(_MSC_VER)) && (_MSC_VER >= 1400) +#define SECUREC_MASK_MSVC_CRT_WARNING __pragma(warning(push)) \ + __pragma(warning(disable:4996 4127)) +#define SECUREC_END_MASK_MSVC_CRT_WARNING __pragma(warning(pop)) +#else +#define SECUREC_MASK_MSVC_CRT_WARNING +#define SECUREC_END_MASK_MSVC_CRT_WARNING +#endif +#define SECUREC_WHILE_ZERO SECUREC_MASK_MSVC_CRT_WARNING while (0) SECUREC_END_MASK_MSVC_CRT_WARNING + +#ifndef SECUREC_HAVE_STRNLEN +#if (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L) +#if SECUREC_IN_KERNEL +#define SECUREC_HAVE_STRNLEN 0 +#else +#if defined(__GLIBC__) && __GLIBC__ >= 2 && defined(__GLIBC_MINOR__) && __GLIBC_MINOR__ >= 10 +#define SECUREC_HAVE_STRNLEN 1 +#else +#define SECUREC_HAVE_STRNLEN 0 +#endif +#endif +#else +#define SECUREC_HAVE_STRNLEN 0 +#endif +#endif + +#if SECUREC_IN_KERNEL +/* in kernel disbale functions */ +#ifndef SECUREC_ENABLE_SCANF_FILE +#define SECUREC_ENABLE_SCANF_FILE 0 +#endif +#ifndef SECUREC_ENABLE_SCANF_FLOAT +#define SECUREC_ENABLE_SCANF_FLOAT 0 +#endif +#ifndef SECUREC_ENABLE_SPRINTF_FLOAT +#define SECUREC_ENABLE_SPRINTF_FLOAT 0 +#endif +#ifndef SECUREC_HAVE_MBTOWC +#define SECUREC_HAVE_MBTOWC 0 +#endif +#ifndef SECUREC_HAVE_WCTOMB +#define SECUREC_HAVE_WCTOMB 0 +#endif +#ifndef SECUREC_HAVE_WCHART +#define SECUREC_HAVE_WCHART 0 +#endif +#else /* no in kernel */ +/* Systems that do not support file, can define this macro to 0. */ +#ifndef SECUREC_ENABLE_SCANF_FILE +#define SECUREC_ENABLE_SCANF_FILE 1 +#endif +#ifndef SECUREC_ENABLE_SCANF_FLOAT +#define SECUREC_ENABLE_SCANF_FLOAT 1 +#endif +/* Systems that do not support float, can define this macro to 0. */ +#ifndef SECUREC_ENABLE_SPRINTF_FLOAT +#define SECUREC_ENABLE_SPRINTF_FLOAT 1 +#endif +#ifndef SECUREC_HAVE_MBTOWC +#define SECUREC_HAVE_MBTOWC 1 +#endif +#ifndef SECUREC_HAVE_WCTOMB +#define SECUREC_HAVE_WCTOMB 1 +#endif +#ifndef SECUREC_HAVE_WCHART +#define SECUREC_HAVE_WCHART 1 +#endif +#endif + + +#define SECUREC_INT_MAX 2147483647 +#define SECUREC_MUL_SIXTEEN(x) ((x) << 4) +#define SECUREC_MUL_EIGHT(x) ((x) << 3) +#define SECUREC_MUL_TEN(x) ((((x) << 2) + (x)) << 1) +/* Limited format input and output width */ +#define SECUREC_MAX_WIDTH_LEN_DIV_TEN 21474836 +#define SECUREC_MAX_WIDTH_LEN SECUREC_MUL_TEN(SECUREC_MAX_WIDTH_LEN_DIV_TEN) +/* Is the x multiplied by 10 greater than */ +#define SECUREC_MUL_TEN_ADD_BEYOND_MAX(x) (((x) > SECUREC_MAX_WIDTH_LEN_DIV_TEN)) + +#define SECUREC_FLOAT_BUFSIZE (309 + 40) /* Max length of double value */ +#define SECUREC_FLOAT_BUFSIZE_LB (4932 + 40) /* Max length of long double value */ +#define SECUREC_FLOAT_DEFAULT_PRECISION 6 + +/* This macro does not handle pointer equality or integer overflow */ +#define SECUREC_MEMORY_NO_OVERLAP(dest, src, count) \ + (((src) < (dest) && ((const char *)(src) + (count)) <= (char *)(dest)) || \ + ((dest) < (src) && ((char *)(dest) + (count)) <= (const char *)(src))) + +#define SECUREC_MEMORY_IS_OVERLAP(dest, src, count) \ + (((src) < (dest) && ((const char *)(src) + (count)) > (char *)(dest)) || \ + ((dest) < (src) && ((char *)(dest) + (count)) > (const char *)(src))) + +/* + * Check whether the strings overlap, len is the length of the string not include terminator + * Length is related to data type char or wchar , do not force conversion of types + */ +#define SECUREC_STRING_NO_OVERLAP(dest, src, len) \ + (((src) < (dest) && ((src) + (len)) < (dest)) || \ + ((dest) < (src) && ((dest) + (len)) < (src))) + +/* + * Check whether the strings overlap for strcpy wcscpy function, dest len and src Len are not include terminator + * Length is related to data type char or wchar , do not force conversion of types + */ +#define SECUREC_STRING_IS_OVERLAP(dest, src, len) \ + (((src) < (dest) && ((src) + (len)) >= (dest)) || \ + ((dest) < (src) && ((dest) + (len)) >= (src))) + +/* + * Check whether the strings overlap for strcat wcscat function, dest len and src Len are not include terminator + * Length is related to data type char or wchar , do not force conversion of types + */ +#define SECUREC_CAT_STRING_IS_OVERLAP(dest, destLen, src, srcLen) \ + (((dest) < (src) && ((dest) + (destLen) + (srcLen)) >= (src)) || \ + ((src) < (dest) && ((src) + (srcLen)) >= (dest))) + + +#if SECUREC_HAVE_STRNLEN +#define SECUREC_CALC_STR_LEN(str, maxLen, outLen) do { \ + *(outLen) = strnlen((str), (maxLen)); \ +} SECUREC_WHILE_ZERO +#define SECUREC_CALC_STR_LEN_OPT(str, maxLen, outLen) do { \ + if ((maxLen) > 8) { \ + /* Optimization or len less then 8 */ \ + if (*((str) + 0) == '\0') { \ + *(outLen) = 0; \ + } else if (*((str) + 1) == '\0') { \ + *(outLen) = 1; \ + } else if (*((str) + 2) == '\0') { \ + *(outLen) = 2; \ + } else if (*((str) + 3) == '\0') { \ + *(outLen) = 3; \ + } else if (*((str) + 4) == '\0') { \ + *(outLen) = 4; \ + } else if (*((str) + 5) == '\0') { \ + *(outLen) = 5; \ + } else if (*((str) + 6) == '\0') { \ + *(outLen) = 6; \ + } else if (*((str) + 7) == '\0') { \ + *(outLen) = 7; \ + } else if (*((str) + 8) == '\0') { \ + /* Optimization with a length of 8 */ \ + *(outLen) = 8; \ + } else { \ + /* The offset is 8 because the performance of 8 byte alignment is high */ \ + *(outLen) = 8 + strnlen((str) + 8, (maxLen) - 8); \ + } \ + } else { \ + SECUREC_CALC_STR_LEN((str), (maxLen), (outLen)); \ + } \ +} SECUREC_WHILE_ZERO +#else +#define SECUREC_CALC_STR_LEN(str, maxLen, outLen) do { \ + const char *strEnd = (const char *)(str); \ + size_t availableSize = (size_t)(maxLen); \ + while (availableSize > 0 && *strEnd != '\0') { \ + --availableSize; \ + ++strEnd; \ + } \ + *(outLen) = (size_t)(strEnd - (str)); \ +} SECUREC_WHILE_ZERO +#define SECUREC_CALC_STR_LEN_OPT SECUREC_CALC_STR_LEN +#endif + +#define SECUREC_CALC_WSTR_LEN(str, maxLen, outLen) do { \ + const wchar_t *strEnd = (const wchar_t *)(str); \ + *(outLen) = 0; \ + while (*(outLen) < (maxLen) && *strEnd != L'\0') { \ + *(outLen) = *(outLen) + 1; \ + ++strEnd; \ + } \ +} SECUREC_WHILE_ZERO + + +#ifdef SECUREC_FORMAT_OUTPUT_INPUT +#if defined(SECUREC_COMPATIBLE_WIN_FORMAT) || defined(__ARMCC_VERSION) +typedef __int64 SecInt64; +typedef unsigned __int64 SecUnsignedInt64; +#if defined(__ARMCC_VERSION) +typedef unsigned int SecUnsignedInt32; +#else +typedef unsigned __int32 SecUnsignedInt32; +#endif +#else +typedef unsigned int SecUnsignedInt32; +typedef long long SecInt64; +typedef unsigned long long SecUnsignedInt64; +#endif + +#ifdef SECUREC_FOR_WCHAR +#if defined(SECUREC_VXWORKS_PLATFORM) && !defined(__WINT_TYPE__) +typedef wchar_t wint_t; +#endif +typedef wchar_t SecChar; +typedef wchar_t SecUnsignedChar; +typedef wint_t SecInt; +typedef wint_t SecUnsignedInt; +#else /* no SECUREC_FOR_WCHAR */ +typedef char SecChar; +typedef unsigned char SecUnsignedChar; +typedef int SecInt; +typedef unsigned int SecUnsignedInt; +#endif +#endif + +/* Determine whether the address is 8-byte aligned + * Some systems do not have uintptr_t type, so use NULL to clear tool alarm 507 + */ +#define SECUREC_ADDR_ALIGNED_8(addr) (SecIsAddrAligned8((addr), NULL) == 0) + +/* If you define the memory allocation function, + * you need to define the function prototype. You can define this macro as a header file. + */ +#if defined(SECUREC_MALLOC_PROTOTYPE) +SECUREC_MALLOC_PROTOTYPE +#endif + +#ifndef SECUREC_MALLOC +#define SECUREC_MALLOC(x) malloc((size_t)(x)) +#endif + +#ifndef SECUREC_FREE +#define SECUREC_FREE(x) free((void *)(x)) +#endif + +/* struct for performance */ +typedef struct { + unsigned char buf[1]; /* Performance optimization code structure assignment length 1 bytes */ +} SecStrBuf1; +typedef struct { + unsigned char buf[2]; /* Performance optimization code structure assignment length 2 bytes */ +} SecStrBuf2; +typedef struct { + unsigned char buf[3]; /* Performance optimization code structure assignment length 3 bytes */ +} SecStrBuf3; +typedef struct { + unsigned char buf[4]; /* Performance optimization code structure assignment length 4 bytes */ +} SecStrBuf4; +typedef struct { + unsigned char buf[5]; /* Performance optimization code structure assignment length 5 bytes */ +} SecStrBuf5; +typedef struct { + unsigned char buf[6]; /* Performance optimization code structure assignment length 6 bytes */ +} SecStrBuf6; +typedef struct { + unsigned char buf[7]; /* Performance optimization code structure assignment length 7 bytes */ +} SecStrBuf7; +typedef struct { + unsigned char buf[8]; /* Performance optimization code structure assignment length 8 bytes */ +} SecStrBuf8; +typedef struct { + unsigned char buf[9]; /* Performance optimization code structure assignment length 9 bytes */ +} SecStrBuf9; +typedef struct { + unsigned char buf[10]; /* Performance optimization code structure assignment length 10 bytes */ +} SecStrBuf10; +typedef struct { + unsigned char buf[11]; /* Performance optimization code structure assignment length 11 bytes */ +} SecStrBuf11; +typedef struct { + unsigned char buf[12]; /* Performance optimization code structure assignment length 12 bytes */ +} SecStrBuf12; +typedef struct { + unsigned char buf[13]; /* Performance optimization code structure assignment length 13 bytes */ +} SecStrBuf13; +typedef struct { + unsigned char buf[14]; /* Performance optimization code structure assignment length 14 bytes */ +} SecStrBuf14; +typedef struct { + unsigned char buf[15]; /* Performance optimization code structure assignment length 15 bytes */ +} SecStrBuf15; +typedef struct { + unsigned char buf[16]; /* Performance optimization code structure assignment length 16 bytes */ +} SecStrBuf16; +typedef struct { + unsigned char buf[17]; /* Performance optimization code structure assignment length 17 bytes */ +} SecStrBuf17; +typedef struct { + unsigned char buf[18]; /* Performance optimization code structure assignment length 18 bytes */ +} SecStrBuf18; +typedef struct { + unsigned char buf[19]; /* Performance optimization code structure assignment length 19 bytes */ +} SecStrBuf19; +typedef struct { + unsigned char buf[20]; /* Performance optimization code structure assignment length 20 bytes */ +} SecStrBuf20; +typedef struct { + unsigned char buf[21]; /* Performance optimization code structure assignment length 21 bytes */ +} SecStrBuf21; +typedef struct { + unsigned char buf[22]; /* Performance optimization code structure assignment length 22 bytes */ +} SecStrBuf22; +typedef struct { + unsigned char buf[23]; /* Performance optimization code structure assignment length 23 bytes */ +} SecStrBuf23; +typedef struct { + unsigned char buf[24]; /* Performance optimization code structure assignment length 24 bytes */ +} SecStrBuf24; +typedef struct { + unsigned char buf[25]; /* Performance optimization code structure assignment length 25 bytes */ +} SecStrBuf25; +typedef struct { + unsigned char buf[26]; /* Performance optimization code structure assignment length 26 bytes */ +} SecStrBuf26; +typedef struct { + unsigned char buf[27]; /* Performance optimization code structure assignment length 27 bytes */ +} SecStrBuf27; +typedef struct { + unsigned char buf[28]; /* Performance optimization code structure assignment length 28 bytes */ +} SecStrBuf28; +typedef struct { + unsigned char buf[29]; /* Performance optimization code structure assignment length 29 bytes */ +} SecStrBuf29; +typedef struct { + unsigned char buf[30]; /* Performance optimization code structure assignment length 30 bytes */ +} SecStrBuf30; +typedef struct { + unsigned char buf[31]; /* Performance optimization code structure assignment length 31 bytes */ +} SecStrBuf31; +typedef struct { + unsigned char buf[32]; /* Performance optimization code structure assignment length 32 bytes */ +} SecStrBuf32; +typedef struct { + unsigned char buf[33]; /* Performance optimization code structure assignment length 33 bytes */ +} SecStrBuf33; +typedef struct { + unsigned char buf[34]; /* Performance optimization code structure assignment length 34 bytes */ +} SecStrBuf34; +typedef struct { + unsigned char buf[35]; /* Performance optimization code structure assignment length 35 bytes */ +} SecStrBuf35; +typedef struct { + unsigned char buf[36]; /* Performance optimization code structure assignment length 36 bytes */ +} SecStrBuf36; +typedef struct { + unsigned char buf[37]; /* Performance optimization code structure assignment length 37 bytes */ +} SecStrBuf37; +typedef struct { + unsigned char buf[38]; /* Performance optimization code structure assignment length 38 bytes */ +} SecStrBuf38; +typedef struct { + unsigned char buf[39]; /* Performance optimization code structure assignment length 39 bytes */ +} SecStrBuf39; +typedef struct { + unsigned char buf[40]; /* Performance optimization code structure assignment length 40 bytes */ +} SecStrBuf40; +typedef struct { + unsigned char buf[41]; /* Performance optimization code structure assignment length 41 bytes */ +} SecStrBuf41; +typedef struct { + unsigned char buf[42]; /* Performance optimization code structure assignment length 42 bytes */ +} SecStrBuf42; +typedef struct { + unsigned char buf[43]; /* Performance optimization code structure assignment length 43 bytes */ +} SecStrBuf43; +typedef struct { + unsigned char buf[44]; /* Performance optimization code structure assignment length 44 bytes */ +} SecStrBuf44; +typedef struct { + unsigned char buf[45]; /* Performance optimization code structure assignment length 45 bytes */ +} SecStrBuf45; +typedef struct { + unsigned char buf[46]; /* Performance optimization code structure assignment length 46 bytes */ +} SecStrBuf46; +typedef struct { + unsigned char buf[47]; /* Performance optimization code structure assignment length 47 bytes */ +} SecStrBuf47; +typedef struct { + unsigned char buf[48]; /* Performance optimization code structure assignment length 48 bytes */ +} SecStrBuf48; +typedef struct { + unsigned char buf[49]; /* Performance optimization code structure assignment length 49 bytes */ +} SecStrBuf49; +typedef struct { + unsigned char buf[50]; /* Performance optimization code structure assignment length 50 bytes */ +} SecStrBuf50; +typedef struct { + unsigned char buf[51]; /* Performance optimization code structure assignment length 51 bytes */ +} SecStrBuf51; +typedef struct { + unsigned char buf[52]; /* Performance optimization code structure assignment length 52 bytes */ +} SecStrBuf52; +typedef struct { + unsigned char buf[53]; /* Performance optimization code structure assignment length 53 bytes */ +} SecStrBuf53; +typedef struct { + unsigned char buf[54]; /* Performance optimization code structure assignment length 54 bytes */ +} SecStrBuf54; +typedef struct { + unsigned char buf[55]; /* Performance optimization code structure assignment length 55 bytes */ +} SecStrBuf55; +typedef struct { + unsigned char buf[56]; /* Performance optimization code structure assignment length 56 bytes */ +} SecStrBuf56; +typedef struct { + unsigned char buf[57]; /* Performance optimization code structure assignment length 57 bytes */ +} SecStrBuf57; +typedef struct { + unsigned char buf[58]; /* Performance optimization code structure assignment length 58 bytes */ +} SecStrBuf58; +typedef struct { + unsigned char buf[59]; /* Performance optimization code structure assignment length 59 bytes */ +} SecStrBuf59; +typedef struct { + unsigned char buf[60]; /* Performance optimization code structure assignment length 60 bytes */ +} SecStrBuf60; +typedef struct { + unsigned char buf[61]; /* Performance optimization code structure assignment length 61 bytes */ +} SecStrBuf61; +typedef struct { + unsigned char buf[62]; /* Performance optimization code structure assignment length 62 bytes */ +} SecStrBuf62; +typedef struct { + unsigned char buf[63]; /* Performance optimization code structure assignment length 63 bytes */ +} SecStrBuf63; +typedef struct { + unsigned char buf[64]; /* Performance optimization code structure assignment length 64 bytes */ +} SecStrBuf64; + + + + +/* User can change the error handler by modify the following definition, + * such as logging the detail error in file. + */ +#if defined(_DEBUG) || defined(DEBUG) +#if defined(SECUREC_ERROR_HANDLER_BY_ASSERT) +#define SECUREC_ERROR_INVALID_PARAMTER(msg) assert(msg "invalid argument" == NULL) +#define SECUREC_ERROR_INVALID_RANGE(msg) assert(msg "invalid dest buffer size" == NULL) +#define SECUREC_ERROR_BUFFER_OVERLAP(msg) assert(msg "buffer overlap" == NULL) +#elif defined(SECUREC_ERROR_HANDLER_BY_PRINTF) +#if SECUREC_IN_KERNEL +#define SECUREC_ERROR_INVALID_PARAMTER(msg) printk("%s invalid argument\n", msg) +#define SECUREC_ERROR_INVALID_RANGE(msg) printk("%s invalid dest buffer size\n", msg) +#define SECUREC_ERROR_BUFFER_OVERLAP(msg) printk("%s buffer overlap\n", msg) +#else +#define SECUREC_ERROR_INVALID_PARAMTER(msg) printf("%s invalid argument\n", msg) +#define SECUREC_ERROR_INVALID_RANGE(msg) printf("%s invalid dest buffer size\n", msg) +#define SECUREC_ERROR_BUFFER_OVERLAP(msg) printf("%s buffer overlap\n", msg) +#endif +#elif defined(SECUREC_ERROR_HANDLER_BY_FILE_LOG) +#define SECUREC_ERROR_INVALID_PARAMTER(msg) LogSecureCRuntimeError(msg " EINVAL\n") +#define SECUREC_ERROR_INVALID_RANGE(msg) LogSecureCRuntimeError(msg " ERANGE\n") +#define SECUREC_ERROR_BUFFER_OVERLAP(msg) LogSecureCRuntimeError(msg " EOVERLAP\n") +#else /* no HANDLER is defined */ +#define SECUREC_ERROR_INVALID_PARAMTER(msg) ((void)0) +#define SECUREC_ERROR_INVALID_RANGE(msg) ((void)0) +#define SECUREC_ERROR_BUFFER_OVERLAP(msg) ((void)0) +#endif +#else /* no DEBUG */ +#define SECUREC_ERROR_INVALID_PARAMTER(msg) ((void)0) +#define SECUREC_ERROR_INVALID_RANGE(msg) ((void)0) +#define SECUREC_ERROR_BUFFER_OVERLAP(msg) ((void)0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* assembly language memory copy and memory set for X86 or MIPS ... */ +#ifdef SECUREC_USE_ASM + extern void *memcpy_opt(void *, const void *, size_t); + extern void *memset_opt(void *, int, size_t); +#endif + +#if defined(SECUREC_ERROR_HANDLER_BY_FILE_LOG) + extern void LogSecureCRuntimeError(const char *errDetail); +#endif + +#ifdef SECUREC_INLINE_DO_MEMCPY +static void SecDoMemcpy(void *dest, const void *src, size_t count) +{ + /* + * if SECUREC_USE_ASM macro is enabled, it will call assembly language function to improve performance. + */ +#ifdef SECUREC_USE_ASM + (void)memcpy_opt(dest, src, count); +#else + /* large enough, let system API do it */ + (void)memcpy(dest, src, count); +#endif +} +#endif + +#ifdef SECUREC_INLINE_DO_MEMSET +static void SecDoMemset(void *dest, int c, size_t count) +{ +#ifdef SECUREC_USE_ASM + (void)memset_opt(dest, c, count); +#else + (void)memset(dest, c, count); +#endif +} +#endif + +#ifdef SECUREC_INLINE_STR_LEN +/* The function compiler will be inlined and not placed in other files */ +static size_t SecStrMinLen(const char *str, size_t maxLen) +{ + size_t len; + SECUREC_CALC_STR_LEN(str, maxLen, &len); + return len; +} +#endif + +#ifdef SECUREC_INLINE_STR_LEN_OPT +/* The function compiler will be inlined and not placed in other files */ +static size_t SecStrMinLenOpt(const char *str, size_t maxLen) +{ + size_t len; + SECUREC_CALC_STR_LEN_OPT(str, maxLen, &len); + return len; +} +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif + diff --git a/third_party/securec/src/secureinput_a.c b/third_party/securec/src/secureinput_a.c new file mode 100644 index 0000000..4f9bae8 --- /dev/null +++ b/third_party/securec/src/secureinput_a.c @@ -0,0 +1,25 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_FORMAT_OUTPUT_INPUT 1 +#ifdef SECUREC_FOR_WCHAR +#undef SECUREC_FOR_WCHAR +#endif + +#include "secinput.h" + +#include "input.inl" + diff --git a/third_party/securec/src/secureinput_w.c b/third_party/securec/src/secureinput_w.c new file mode 100644 index 0000000..7a4bef4 --- /dev/null +++ b/third_party/securec/src/secureinput_w.c @@ -0,0 +1,46 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* if some platforms don't have wchar.h, dont't include it */ +#if !(defined(SECUREC_VXWORKS_PLATFORM)) +/* This header file is placed below secinput.h, which will cause tool alarm, + * but If there is no macro above, it will cause vs2010 compiling alarm + */ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#ifndef __STDC_WANT_SECURE_LIB__ +/* The order of adjustment is to eliminate alarm of Duplicate Block */ +#define __STDC_WANT_SECURE_LIB__ 0 +#endif +#ifndef _CRTIMP_ALTERNATIVE +#define _CRTIMP_ALTERNATIVE /* comment microsoft *_s function */ +#endif +#endif +#include +#endif +#define SECUREC_ENABLE_WCHAR_FUNC 0 +#define SECUREC_FORMAT_OUTPUT_INPUT 1 +#ifndef SECUREC_FOR_WCHAR +#define SECUREC_FOR_WCHAR +#endif + +#include "secinput.h" + +#ifndef WEOF +#define WEOF ((wchar_t)(-1)) +#endif + +#include "input.inl" + diff --git a/third_party/securec/src/secureprintoutput.h b/third_party/securec/src/secureprintoutput.h new file mode 100644 index 0000000..b690ec9 --- /dev/null +++ b/third_party/securec/src/secureprintoutput.h @@ -0,0 +1,98 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SECUREPRINTOUTPUT_H_E950DA2C_902F_4B15_BECD_948E99090D9C +#define SECUREPRINTOUTPUT_H_E950DA2C_902F_4B15_BECD_948E99090D9C +#include "securecutil.h" + +/* flag definitions */ +/* Using macros instead of enumerations is because some of the enumerated types under the compiler are 16bit. */ +#define SECUREC_FLAG_SIGN 0x00001U +#define SECUREC_FLAG_SIGN_SPACE 0x00002U +#define SECUREC_FLAG_LEFT 0x00004U +#define SECUREC_FLAG_LEADZERO 0x00008U +#define SECUREC_FLAG_LONG 0x00010U +#define SECUREC_FLAG_SHORT 0x00020U +#define SECUREC_FLAG_SIGNED 0x00040U +#define SECUREC_FLAG_ALTERNATE 0x00080U +#define SECUREC_FLAG_NEGATIVE 0x00100U +#define SECUREC_FLAG_FORCE_OCTAL 0x00200U +#define SECUREC_FLAG_LONG_DOUBLE 0x00400U +#define SECUREC_FLAG_WIDECHAR 0x00800U +#define SECUREC_FLAG_LONGLONG 0x01000U +#define SECUREC_FLAG_CHAR 0x02000U +#define SECUREC_FLAG_POINTER 0x04000U +#define SECUREC_FLAG_I64 0x08000U +#define SECUREC_FLAG_PTRDIFF 0x10000U +#define SECUREC_FLAG_SIZE 0x20000U +#ifdef SECUREC_COMPATIBLE_LINUX_FORMAT +#define SECUREC_FLAG_INTMAX 0x40000U +#endif + +/* state definitions. Identify the status of the current format */ +typedef enum { + STAT_NORMAL, + STAT_PERCENT, + STAT_FLAG, + STAT_WIDTH, + STAT_DOT, + STAT_PRECIS, + STAT_SIZE, + STAT_TYPE, + STAT_INVALID +} SecFmtState; + +/* Format output buffer pointer and available size */ +typedef struct { + int count; + char *cur; +} SecPrintfStream; + + +#ifndef SECUREC_BUFFER_SIZE +#ifdef SECUREC_STACK_SIZE_LESS_THAN_1K +/* SECUREC_BUFFER_SIZE Can not be less than 23 , + * the length of the octal representation of 64-bit integers with zero lead + */ +#define SECUREC_BUFFER_SIZE 256 +#else +#define SECUREC_BUFFER_SIZE 512 +#endif +#endif +#if SECUREC_BUFFER_SIZE < 23 +#error SECUREC_BUFFER_SIZE Can not be less than 23 +#endif + +#define SECUREC_MAX_PRECISION SECUREC_BUFFER_SIZE +/* max. # bytes in multibyte char ,see MB_LEN_MAX */ +#define SECUREC_MB_LEN 16 +/* The return value of the internal function, which is returned when truncated */ +#define SECUREC_PRINTF_TRUNCATE (-2) + +#ifdef __cplusplus +extern "C" { +#endif + extern int SecVsnprintfImpl(char *string, size_t count, const char *format, va_list argList); +#if SECUREC_IN_KERNEL == 0 + extern int SecVswprintfImpl(wchar_t *string, size_t sizeInWchar, const wchar_t *format, va_list argList); +#endif +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/third_party/securec/src/secureprintoutput_a.c b/third_party/securec/src/secureprintoutput_a.c new file mode 100644 index 0000000..746878a --- /dev/null +++ b/third_party/securec/src/secureprintoutput_a.c @@ -0,0 +1,101 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_DO_MEMCPY 1 +#define SECUREC_FORMAT_OUTPUT_INPUT 1 +#ifdef SECUREC_FOR_WCHAR +#undef SECUREC_FOR_WCHAR +#endif + +#include "secureprintoutput.h" + +#define SECUREC_CHAR(x) x +#define SECUREC_WRITE_MULTI_CHAR SecWriteMultiChar +#define SECUREC_WRITE_STRING SecWriteString + +#ifndef EOF +#define EOF (-1) +#endif + +/* put a char to output */ +#define SECUREC_PUTC(c, outStream) ((--(outStream)->count >= 0) ? \ + (int)((unsigned int)(unsigned char)(*((outStream)->cur++) = (char)(c)) & 0xff) : EOF) +/* to clear e835 */ +#define SECUREC_PUTC_ZERO(outStream) ((--(outStream)->count >= 0) ? \ + ((*((outStream)->cur++) = (char)('\0'))) : EOF) + +static void SecWriteMultiChar(char ch, int num, SecPrintfStream *f, int *pnumwritten); +static void SecWriteString(const char *string, int len, SecPrintfStream *f, int *pnumwritten); + +#include "output.inl" + +/* + * Wide character formatted output implementation + */ +int SecVsnprintfImpl(char *string, size_t count, const char *format, va_list argList) +{ + SecPrintfStream str; + int retVal; + + str.count = (int)count; /* this count include \0 character, Must be greater than zero */ + str.cur = string; + + retVal = SecOutputS(&str, format, argList); + if ((retVal >= 0) && (SECUREC_PUTC_ZERO(&str) != EOF)) { + return retVal; + } else if (str.count < 0) { + /* the buffer was too small; we return truncation */ + string[count - 1] = '\0'; + return SECUREC_PRINTF_TRUNCATE; + } + string[0] = '\0'; /* empty the dest strDest */ + return -1; +} + +/* + * Sec write Wide character + */ +static void SecWriteMultiChar(char ch, int num, SecPrintfStream *f, int *pnumwritten) +{ + int count = num; + while (count-- > 0) { + if (SECUREC_PUTC(ch, f) == EOF) { + *pnumwritten = -1; + break; + } else { + *pnumwritten = *pnumwritten + 1; + } + } +} + +/* + * Sec write string function + */ +static void SecWriteString(const char *string, int len, SecPrintfStream *f, int *pnumwritten) +{ + const char *str = string; + int count = len; + while (count-- > 0) { + if (SECUREC_PUTC(*str, f) == EOF) { + *pnumwritten = -1; + break; + } else { + *pnumwritten = *pnumwritten + 1; + ++str; + } + } +} + diff --git a/third_party/securec/src/secureprintoutput_w.c b/third_party/securec/src/secureprintoutput_w.c new file mode 100644 index 0000000..9063ab4 --- /dev/null +++ b/third_party/securec/src/secureprintoutput_w.c @@ -0,0 +1,170 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* if some platforms don't have wchar.h, dont't include it */ +#if !(defined(SECUREC_VXWORKS_PLATFORM)) +/* This header file is placed below secinput.h, which will cause tool alarm, + * but if there is no macro above, it will cause compiling alarm + */ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#ifndef _CRTIMP_ALTERNATIVE +#define _CRTIMP_ALTERNATIVE /* comment microsoft *_s function */ +#endif +#ifndef __STDC_WANT_SECURE_LIB__ +#define __STDC_WANT_SECURE_LIB__ 0 +#endif +#endif +#include +#endif + +#define SECUREC_ENABLE_WCHAR_FUNC 0 +#define SECUREC_INLINE_DO_MEMCPY 1 +#define SECUREC_FORMAT_OUTPUT_INPUT 1 +#ifndef SECUREC_FOR_WCHAR +#define SECUREC_FOR_WCHAR +#endif + +#include "secureprintoutput.h" + +#ifndef WEOF +#define WEOF ((wchar_t)(-1)) +#endif + +#define SECUREC_CHAR(x) L ## x +#define SECUREC_WRITE_MULTI_CHAR SecWriteMultiCharW +#define SECUREC_WRITE_STRING SecWriteStringW + +static void SecWriteCharW(wchar_t ch, SecPrintfStream *f, int *pnumwritten); +static void SecWriteMultiCharW(wchar_t ch, int num, SecPrintfStream *f, int *pnumwritten); +static void SecWriteStringW(const wchar_t *string, int len, SecPrintfStream *f, int *pnumwritten); +static int SecPutWcharStrEndingZero(SecPrintfStream *str, int zeroCount); + + +#include "output.inl" + +/* + * Wide character formatted output implementation + */ +int SecVswprintfImpl(wchar_t *string, size_t sizeInWchar, const wchar_t *format, va_list argList) +{ + SecPrintfStream str; + int retVal; /* If initialization causes e838 */ + + str.cur = (char *)string; + /* this count include \0 character, Must be greater than zero */ + str.count = (int)(sizeInWchar * sizeof(wchar_t)); + + retVal = SecOutputSW(&str, format, argList); + if ((retVal >= 0) && SecPutWcharStrEndingZero(&str, (int)sizeof(wchar_t))) { + return (retVal); + } else if (str.count < 0) { + /* the buffer was too small; we return truncation */ + string[sizeInWchar - 1] = L'\0'; + return SECUREC_PRINTF_TRUNCATE; + } + string[0] = L'\0'; + return -1; +} + +/* + * Output one zero character zero into the SecPrintfStream structure + */ +static int SecPutZeroChar(SecPrintfStream *str) +{ + if (str->count > 0) { + *(str->cur) = (char)('\0'); + str->count = str->count - 1; + str->cur = str->cur + 1; + return 0; + } + return -1; +} + +/* + * Output a wide character zero end into the SecPrintfStream structure + */ +static int SecPutWcharStrEndingZero(SecPrintfStream *str, int zeroCount) +{ + int succeed = 0; + int i = 0; + + while (i < zeroCount && (SecPutZeroChar(str) == 0)) { + ++i; + } + if (i == zeroCount) { + succeed = 1; + } + return succeed; +} + + +/* + * Output a wide character into the SecPrintfStream structure + */ +static wchar_t SecPutCharW(wchar_t ch, SecPrintfStream *f) +{ + wchar_t wcRet = 0; + if (((f)->count -= (int)sizeof(wchar_t)) >= 0) { + *(wchar_t *)(void *)(f->cur) = ch; + f->cur += sizeof(wchar_t); + wcRet = ch; + } else { + wcRet = (wchar_t)WEOF; + } + return wcRet; +} + +/* + * Output a wide character into the SecPrintfStream structure, returns the number of characters written + */ +static void SecWriteCharW(wchar_t ch, SecPrintfStream *f, int *pnumwritten) +{ + if (SecPutCharW(ch, f) == (wchar_t)WEOF) { + *pnumwritten = -1; + } else { + *pnumwritten = *pnumwritten + 1; + } +} + +/* + * Output multiple wide character into the SecPrintfStream structure, returns the number of characters written + */ +static void SecWriteMultiCharW(wchar_t ch, int num, SecPrintfStream *f, int *pnumwritten) +{ + int count = num; + while (count-- > 0) { + SecWriteCharW(ch, f, pnumwritten); + if (*pnumwritten == -1) { + break; + } + } +} + +/* + * Output a wide string into the SecPrintfStream structure, returns the number of characters written + */ +static void SecWriteStringW(const wchar_t *string, int len, SecPrintfStream *f, int *pnumwritten) +{ + const wchar_t *str = string; + int count = len; + while (count-- > 0) { + SecWriteCharW(*str++, f, pnumwritten); + if (*pnumwritten == -1) { + break; + } + } +} + diff --git a/third_party/securec/src/snprintf_s.c b/third_party/securec/src/snprintf_s.c new file mode 100644 index 0000000..0bd7ed1 --- /dev/null +++ b/third_party/securec/src/snprintf_s.c @@ -0,0 +1,113 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +#if SECUREC_ENABLE_SNPRINTF +/* + * + * The snprintf_s function is equivalent to the snprintf function + * except for the parameter destMax/count and the explicit runtime-constraints violation + * The snprintf_s function formats and stores count or fewer characters in + * strDest and appends a terminating null. Each argument (if any) is converted + * and output according to the corresponding format specification in format. + * The formatting is consistent with the printf family of functions; If copying + * occurs between strings that overlap, the behavior is undefined. + * + * + * strDest Storage location for the output. + * destMax The size of the storage location for output. Size + * in bytes for snprintf_s or size in words for snwprintf_s. + * count Maximum number of character to store. + * format Format-control string. + * ... Optional arguments. + * + * + * strDest is updated + * + * + * return the number of characters written, not including the terminating null + * return -1 if an error occurs. + * return -1 if count < destMax and the output string has been truncated + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + * + */ +int snprintf_s(char *strDest, size_t destMax, size_t count, const char *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vsnprintf_s(strDest, destMax, count, format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(snprintf_s); +#endif +#endif + +#if SECUREC_SNPRINTF_TRUNCATED +/* + * + * The snprintf_truncated_s function is equivalent to the snprintf function + * except for the parameter destMax/count and the explicit runtime-constraints violation + * The snprintf_truncated_s function formats and stores count or fewer characters in + * strDest and appends a terminating null. Each argument (if any) is converted + * and output according to the corresponding format specification in format. + * The formatting is consistent with the printf family of functions; If copying + * occurs between strings that overlap, the behavior is undefined. + * + * + * strDest Storage location for the output. + * destMax The size of the storage location for output. Size + * in bytes for snprintf_truncated_s or size in words for snwprintf_s. + * format Format-control string. + * ... Optional arguments. + * + * + * strDest is updated + * + * + * return the number of characters written, not including the terminating null + * return -1 if an error occurs. + * return destMax-1 if output string has been truncated + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + * + */ +int snprintf_truncated_s(char *strDest, size_t destMax, const char *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vsnprintf_truncated_s(strDest, destMax, format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(snprintf_truncated_s); +#endif + +#endif + + diff --git a/third_party/securec/src/sprintf_s.c b/third_party/securec/src/sprintf_s.c new file mode 100644 index 0000000..54a7960 --- /dev/null +++ b/third_party/securec/src/sprintf_s.c @@ -0,0 +1,61 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * + * The sprintf_s function is equivalent to the sprintf function + * except for the parameter destMax and the explicit runtime-constraints violation + * The sprintf_s function formats and stores a series of characters and values + * in strDest. Each argument (if any) is converted and output according to + * the corresponding format specification in format. The format consists of + * ordinary characters and has the same form and function as the format argument + * for printf. A null character is appended after the last character written. + * If copying occurs between strings that overlap, the behavior is undefined. + * + * + * strDest Storage location for output. + * destMax Maximum number of characters to store. + * format Format-control string. + * ... Optional arguments + * + * + * strDest is updated + * + * + * return the number of bytes stored in strDest, not counting the terminating null character. + * return -1 if an error occurred. + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +int sprintf_s(char *strDest, size_t destMax, const char *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vsprintf_s(strDest, destMax, format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(sprintf_s); +#endif + + diff --git a/third_party/securec/src/sscanf_s.c b/third_party/securec/src/sscanf_s.c new file mode 100644 index 0000000..c8f097e --- /dev/null +++ b/third_party/securec/src/sscanf_s.c @@ -0,0 +1,61 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * + * The sscanf_s function is equivalent to fscanf_s, + * except that input is obtained from a string (specified by the argument buffer) rather than from a stream + * The sscanf function reads data from buffer into the location given by each + * argument. Every argument must be a pointer to a variable with a type that + * corresponds to a type specifier in format. The format argument controls the + * interpretation of the input fields and has the same form and function as + * the format argument for the scanf function. + * If copying takes place between strings that overlap, the behavior is undefined. + * + * + * buffer Stored data. + * format Format control string, see Format Specifications. + * ... Optional arguments. + * + * + * ... The converted value stored in user assigned address + * + * + * Each of these functions returns the number of fields successfully converted + * and assigned; the return value does not include fields that were read but + * not assigned. + * A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int sscanf_s(const char *buffer, const char *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vsscanf_s(buffer, format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(sscanf_s); +#endif + + diff --git a/third_party/securec/src/strcat_s.c b/third_party/securec/src/strcat_s.c new file mode 100644 index 0000000..6bf1379 --- /dev/null +++ b/third_party/securec/src/strcat_s.c @@ -0,0 +1,102 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_STR_LEN 1 +#define SECUREC_INLINE_STR_LEN_OPT 1 +#define SECUREC_INLINE_DO_MEMCPY 1 +#include "securecutil.h" + +/* + * Befor this function, the basic parameter checking has been done + */ +static errno_t SecDoStrcat(char *strDest, size_t destMax, const char *strSrc) +{ + size_t destLen = SecStrMinLen(strDest, destMax); + /* Only optimize strSrc, do not apply this function to strDest */ + size_t srcLen = SecStrMinLenOpt(strSrc, destMax - destLen); + + if (SECUREC_CAT_STRING_IS_OVERLAP(strDest, destLen, strSrc, srcLen)) { + strDest[0] = '\0'; + if (strDest + destLen <= strSrc && destLen == destMax) { + SECUREC_ERROR_INVALID_PARAMTER("strcat_s"); + return EINVAL_AND_RESET; + } + SECUREC_ERROR_BUFFER_OVERLAP("strcat_s"); + return EOVERLAP_AND_RESET; + } + if (srcLen + destLen >= destMax || strDest == strSrc) { + strDest[0] = '\0'; + if (destLen == destMax) { + SECUREC_ERROR_INVALID_PARAMTER("strcat_s"); + return EINVAL_AND_RESET; + } + SECUREC_ERROR_INVALID_RANGE("strcat_s"); + return ERANGE_AND_RESET; + } + SecDoMemcpy(strDest + destLen, strSrc, srcLen + 1); /* single character length include \0 */ + return EOK; +} + +/* + * + * The strcat_s function appends a copy of the string pointed to by strSrc (including the terminating null character) + * to the end of the string pointed to by strDest. + * The initial character of strSrc overwrites the terminating null character of strDest. + * strcat_s will return EOVERLAP_AND_RESET if the source and destination strings overlap. + * + * Note that the second parameter is the total size of the buffer, not the + * remaining size. + * + * + * strDest Null-terminated destination string buffer. + * destMax Size of the destination string buffer. + * strSrc Null-terminated source string buffer. + * + * + * strDest is updated + * + * + * EOK Success + * EINVAL strDest is NULL and destMax != 0 and destMax <= SECUREC_STRING_MAX_LEN + * EINVAL_AND_RESET (strDest unterminated and all other parameters are valid)or + * (strDest != NULL and strSrc is NULL and destMax != 0 and destMax <= SECUREC_STRING_MAX_LEN) + * ERANGE destMax is 0 and destMax > SECUREC_STRING_MAX_LEN + * ERANGE_AND_RESET strDest have not enough space and all other parameters are valid and not overlap + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and all parameters are valid + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +errno_t strcat_s(char *strDest, size_t destMax, const char *strSrc) +{ + if (destMax == 0 || destMax > SECUREC_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("strcat_s"); + return ERANGE; + } + if (strDest == NULL || strSrc == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("strcat_s"); + if (strDest != NULL) { + strDest[0] = '\0'; + return EINVAL_AND_RESET; + } + return EINVAL; + } + return SecDoStrcat(strDest, destMax, strSrc); +} + +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(strcat_s); +#endif + diff --git a/third_party/securec/src/strcpy_s.c b/third_party/securec/src/strcpy_s.c new file mode 100644 index 0000000..e248da7 --- /dev/null +++ b/third_party/securec/src/strcpy_s.c @@ -0,0 +1,351 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_STR_LEN 1 +#define SECUREC_INLINE_DO_MEMCPY 1 + +#include "securecutil.h" + +#if SECUREC_IN_KERNEL== 0 +#ifndef SECUREC_STRCOPY_THRESHOLD_SIZE +#define SECUREC_STRCOPY_THRESHOLD_SIZE 32UL +#endif + +/* + * Determine whether the address is 8-byte aligned, use static to increase performance + * return 0 is aligned + */ +static int SecIsAddrAligned8(const void *addr, const void *zeroAddr) +{ + return (int)(((size_t)((const char*)addr - (const char*)zeroAddr)) & 7); /* use 7 to check aligned 8 */ +} + +/* The purpose of converting to void is to clean up the alarm */ +#define SECUREC_SMALL_STR_COPY do { \ + if (SECUREC_ADDR_ALIGNED_8(strDest) && SECUREC_ADDR_ALIGNED_8(strSrc)) { \ + /* use struct assignment */ \ + switch (srcStrLen) { \ + case 1: \ + *(SecStrBuf1 *)(void *)strDest = *(const SecStrBuf1 *)(const void *)strSrc; \ + break; \ + case 2: \ + *(SecStrBuf2 *)(void *)strDest = *(const SecStrBuf2 *)(const void *)strSrc; \ + break; \ + case 3: \ + *(SecStrBuf3 *)(void *)strDest = *(const SecStrBuf3 *)(const void *)strSrc; \ + break; \ + case 4: \ + *(SecStrBuf4 *)(void *)strDest = *(const SecStrBuf4 *)(const void *)strSrc; \ + break; \ + case 5: \ + *(SecStrBuf5 *)(void *)strDest = *(const SecStrBuf5 *)(const void *)strSrc; \ + break; \ + case 6: \ + *(SecStrBuf6 *)(void *)strDest = *(const SecStrBuf6 *)(const void *)strSrc; \ + break; \ + case 7: \ + *(SecStrBuf7 *)(void *)strDest = *(const SecStrBuf7 *)(const void *)strSrc; \ + break; \ + case 8: \ + *(SecStrBuf8 *)(void *)strDest = *(const SecStrBuf8 *)(const void *)strSrc; \ + break; \ + case 9: \ + *(SecStrBuf9 *)(void *)strDest = *(const SecStrBuf9 *)(const void *)strSrc; \ + break; \ + case 10: \ + *(SecStrBuf10 *)(void *)strDest = *(const SecStrBuf10 *)(const void *)strSrc; \ + break; \ + case 11: \ + *(SecStrBuf11 *)(void *)strDest = *(const SecStrBuf11 *)(const void *)strSrc; \ + break; \ + case 12: \ + *(SecStrBuf12 *)(void *)strDest = *(const SecStrBuf12 *)(const void *)strSrc; \ + break; \ + case 13: \ + *(SecStrBuf13 *)(void *)strDest = *(const SecStrBuf13 *)(const void *)strSrc; \ + break; \ + case 14: \ + *(SecStrBuf14 *)(void *)strDest = *(const SecStrBuf14 *)(const void *)strSrc; \ + break; \ + case 15: \ + *(SecStrBuf15 *)(void *)strDest = *(const SecStrBuf15 *)(const void *)strSrc; \ + break; \ + case 16: \ + *(SecStrBuf16 *)(void *)strDest = *(const SecStrBuf16 *)(const void *)strSrc; \ + break; \ + case 17: \ + *(SecStrBuf17 *)(void *)strDest = *(const SecStrBuf17 *)(const void *)strSrc; \ + break; \ + case 18: \ + *(SecStrBuf18 *)(void *)strDest = *(const SecStrBuf18 *)(const void *)strSrc; \ + break; \ + case 19: \ + *(SecStrBuf19 *)(void *)strDest = *(const SecStrBuf19 *)(const void *)strSrc; \ + break; \ + case 20: \ + *(SecStrBuf20 *)(void *)strDest = *(const SecStrBuf20 *)(const void *)strSrc; \ + break; \ + case 21: \ + *(SecStrBuf21 *)(void *)strDest = *(const SecStrBuf21 *)(const void *)strSrc; \ + break; \ + case 22: \ + *(SecStrBuf22 *)(void *)strDest = *(const SecStrBuf22 *)(const void *)strSrc; \ + break; \ + case 23: \ + *(SecStrBuf23 *)(void *)strDest = *(const SecStrBuf23 *)(const void *)strSrc; \ + break; \ + case 24: \ + *(SecStrBuf24 *)(void *)strDest = *(const SecStrBuf24 *)(const void *)strSrc; \ + break; \ + case 25: \ + *(SecStrBuf25 *)(void *)strDest = *(const SecStrBuf25 *)(const void *)strSrc; \ + break; \ + case 26: \ + *(SecStrBuf26 *)(void *)strDest = *(const SecStrBuf26 *)(const void *)strSrc; \ + break; \ + case 27: \ + *(SecStrBuf27 *)(void *)strDest = *(const SecStrBuf27 *)(const void *)strSrc; \ + break; \ + case 28: \ + *(SecStrBuf28 *)(void *)strDest = *(const SecStrBuf28 *)(const void *)strSrc; \ + break; \ + case 29: \ + *(SecStrBuf29 *)(void *)strDest = *(const SecStrBuf29 *)(const void *)strSrc; \ + break; \ + case 30: \ + *(SecStrBuf30 *)(void *)strDest = *(const SecStrBuf30 *)(const void *)strSrc; \ + break; \ + case 31: \ + *(SecStrBuf31 *)(void *)strDest = *(const SecStrBuf31 *)(const void *)strSrc; \ + break; \ + case 32: \ + *(SecStrBuf32 *)(void *)strDest = *(const SecStrBuf32 *)(const void *)strSrc; \ + break; \ + default: \ + break; \ + } /* END switch */ \ + } else { \ + char *tmpStrDest = (char *)strDest; \ + const char *tmpStrSrc = (const char *)strSrc; \ + switch (srcStrLen) { \ + case 32: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 31: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 30: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 29: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 28: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 27: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 26: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 25: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 24: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 23: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 22: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 21: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 20: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 19: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 18: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 17: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 16: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 15: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 14: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 13: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 12: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 11: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 10: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 9: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 8: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 7: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 6: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 5: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 4: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 3: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 2: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + case 1: \ + *(tmpStrDest++) = *(tmpStrSrc++); \ + /* fall-through */ /* FALLTHRU */ \ + default: \ + break; \ + } \ + } \ +} SECUREC_WHILE_ZERO +#endif + +/* + * Check Src Range + */ +static errno_t CheckSrcRange(char *strDest, size_t destMax, const char *strSrc) +{ + size_t tmpDestMax = destMax; + const char *tmpSrc = strSrc; + /* use destMax as boundary checker and destMax must be greater than zero */ + while (*(tmpSrc) != '\0' && tmpDestMax > 0) { + ++tmpSrc; + --tmpDestMax; + } + if (tmpDestMax == 0) { + strDest[0] = '\0'; + SECUREC_ERROR_INVALID_RANGE("strcpy_s"); + return ERANGE_AND_RESET; + } + return EOK; +} + +/* + * Handling errors + */ +errno_t strcpy_error(char *strDest, size_t destMax, const char *strSrc) +{ + if (destMax == 0 || destMax > SECUREC_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("strcpy_s"); + return ERANGE; + } else if (strDest == NULL || strSrc == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("strcpy_s"); + if (strDest != NULL) { + strDest[0] = '\0'; + return EINVAL_AND_RESET; + } + return EINVAL; + } + return CheckSrcRange(strDest, destMax, strSrc); +} + +/* + * Performance optimization. srcStrLen include '\0' + */ +static void SecDoStrcpyOpt(char *strDest, const char *strSrc, size_t srcStrLen) +{ +#if SECUREC_IN_KERNEL + SecDoMemcpy(strDest, strSrc, srcStrLen); +#else + if (srcStrLen > SECUREC_STRCOPY_THRESHOLD_SIZE) { + SecDoMemcpy(strDest, strSrc, srcStrLen); + } else { + SECUREC_SMALL_STR_COPY; + } +#endif +} + +/* + * + * The strcpy_s function copies the string pointed to strSrc + * (including the terminating null character) into the array pointed to by strDest + * The destination string must be large enough to hold the source string, + * including the terminating null character. strcpy_s will return EOVERLAP_AND_RESET + * if the source and destination strings overlap. + * + * + * strDest Location of destination string buffer + * destMax Size of the destination string buffer. + * strSrc Null-terminated source string buffer. + * + * + * strDest is updated. + * + * + * EOK Success + * EINVAL strDest is NULL and destMax != 0 and destMax <= SECUREC_STRING_MAX_LEN + * EINVAL_AND_RESET strDest != NULL and strSrc is NULL and destMax != 0 and destMax <= SECUREC_STRING_MAX_LEN + * ERANGE destMax is 0 and destMax > SECUREC_STRING_MAX_LEN + * ERANGE_AND_RESET strDest have not enough space and all other parameters are valid and not overlap + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and all parameters are valid + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +errno_t strcpy_s(char *strDest, size_t destMax, const char *strSrc) +{ + if ((destMax > 0 && destMax <= SECUREC_STRING_MAX_LEN && strDest != NULL && strSrc != NULL && strDest != strSrc)) { + size_t srcStrLen = SecStrMinLen(strSrc, destMax) + 1; /* len include \0 */ + if (srcStrLen <= destMax) { + /* use mem overlap check include \0 */ + if (SECUREC_MEMORY_NO_OVERLAP(strDest, strSrc, srcStrLen)) { + /* performance optimization srcStrLen include '\0' */ + SecDoStrcpyOpt(strDest, strSrc, srcStrLen); + return EOK; + } else { + strDest[0] = '\0'; + SECUREC_ERROR_BUFFER_OVERLAP("strcpy_s"); + return EOVERLAP_AND_RESET; + } + } + } + return strcpy_error(strDest, destMax, strSrc); +} + +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(strcpy_s); +#endif + diff --git a/third_party/securec/src/strncat_s.c b/third_party/securec/src/strncat_s.c new file mode 100644 index 0000000..78234fd --- /dev/null +++ b/third_party/securec/src/strncat_s.c @@ -0,0 +1,121 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_STR_LEN 1 +#define SECUREC_INLINE_DO_MEMCPY 1 + +#include "securecutil.h" + +/* + * Befor this function, the basic parameter checking has been done + */ +static errno_t SecDoStrncat(char *strDest, size_t destMax, const char *strSrc, size_t count) +{ + size_t destLen = SecStrMinLen(strDest, destMax); + /* The strSrc is no longer optimized. The reason is that when count is small, + * the efficiency of strnlen is higher than that of self realization. + */ + size_t srcLen = SecStrMinLen(strSrc, count); + + if (SECUREC_CAT_STRING_IS_OVERLAP(strDest, destLen, strSrc, srcLen)) { + strDest[0] = '\0'; + if (strDest + destLen <= strSrc && destLen == destMax) { + SECUREC_ERROR_INVALID_PARAMTER("strncat_s"); + return EINVAL_AND_RESET; + } + SECUREC_ERROR_BUFFER_OVERLAP("strncat_s"); + return EOVERLAP_AND_RESET; + } + if (srcLen + destLen >= destMax || strDest == strSrc) { + strDest[0] = '\0'; + if (destLen == destMax) { + SECUREC_ERROR_INVALID_PARAMTER("strncat_s"); + return EINVAL_AND_RESET; + } + SECUREC_ERROR_INVALID_RANGE("strncat_s"); + return ERANGE_AND_RESET; + } + SecDoMemcpy(strDest + destLen, strSrc, srcLen); /* no terminator */ + *(strDest + destLen + srcLen) = '\0'; + return EOK; +} + +/* + * + * The strncat_s function appends not more than n successive characters + * (not including the terminating null character) + * from the array pointed to by strSrc to the end of the string pointed to by strDest + * The strncat_s function try to append the first D characters of strSrc to + * the end of strDest, where D is the lesser of count and the length of strSrc. + * If appending those D characters will fit within strDest (whose size is given + * as destMax) and still leave room for a null terminator, then those characters + * are appended, starting at the original terminating null of strDest, and a + * new terminating null is appended; otherwise, strDest[0] is set to the null + * character. + * + * + * strDest Null-terminated destination string. + * destMax Size of the destination buffer. + * strSrc Null-terminated source string. + * count Number of character to append, or truncate. + * + * + * strDest is updated + * + * + * EOK Success + * EINVAL strDest is NULL and destMax != 0 and destMax <= SECUREC_STRING_MAX_LEN + * EINVAL_AND_RESET (strDest unterminated and all other parameters are valid)or + * (strDest != NULL and strSrc is NULL and destMax != 0 and destMax <= SECUREC_STRING_MAX_LEN) + * ERANGE destMax is 0 and destMax > SECUREC_STRING_MAX_LEN + * ERANGE_AND_RESET strDest have not enough space and all other parameters are valid and not overlap + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and all parameters are valid + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +errno_t strncat_s(char *strDest, size_t destMax, const char *strSrc, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("strncat_s"); + return ERANGE; + } + + if (strDest == NULL || strSrc == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("strncat_s"); + if (strDest != NULL) { + strDest[0] = '\0'; + return EINVAL_AND_RESET; + } + return EINVAL; + } + if (count > SECUREC_STRING_MAX_LEN) { +#ifdef SECUREC_COMPATIBLE_WIN_FORMAT + if (count == (size_t)(-1)) { + /* Windows internal functions may pass in -1 when calling this function */ + return SecDoStrncat(strDest, destMax, strSrc, destMax); + } +#endif + strDest[0] = '\0'; + SECUREC_ERROR_INVALID_RANGE("strncat_s"); + return ERANGE_AND_RESET; + } + return SecDoStrncat(strDest, destMax, strSrc, count); +} + +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(strncat_s); +#endif + diff --git a/third_party/securec/src/strncpy_s.c b/third_party/securec/src/strncpy_s.c new file mode 100644 index 0000000..493d1f7 --- /dev/null +++ b/third_party/securec/src/strncpy_s.c @@ -0,0 +1,143 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_STR_LEN 1 +#define SECUREC_INLINE_DO_MEMCPY 1 + +#include "securecutil.h" + +#if defined(SECUREC_COMPATIBLE_WIN_FORMAT) +#define SECUREC_STRNCPY_PARAM_OK(strDest, destMax, strSrc, count) \ + (((destMax) > 0 && (destMax) <= SECUREC_STRING_MAX_LEN && (strDest) != NULL && (strSrc) != NULL && \ + ((count) <= SECUREC_STRING_MAX_LEN || (count) == ((size_t)(-1))) && (count) > 0)) +#else +#define SECUREC_STRNCPY_PARAM_OK(strDest, destMax, strSrc, count) \ + (((destMax) > 0 && (destMax) <= SECUREC_STRING_MAX_LEN && (strDest) != NULL && (strSrc) != NULL && \ + (count) <= SECUREC_STRING_MAX_LEN && (count) > 0)) +#endif + +/* + * Check Src Count Range + */ +static errno_t CheckSrcCountRange(char *strDest, size_t destMax, const char *strSrc, size_t count) +{ + size_t tmpDestMax = destMax; + size_t tmpCount = count; + const char *endPos = strSrc; + + /* use destMax and count as boundary checker and destMax must be greater than zero */ + while (*(endPos) != '\0' && tmpDestMax > 0 && tmpCount > 0) { + ++endPos; + --tmpCount; + --tmpDestMax; + } + if (tmpDestMax == 0) { + strDest[0] = '\0'; + SECUREC_ERROR_INVALID_RANGE("strncpy_s"); + return ERANGE_AND_RESET; + } + return EOK; +} + +/* + * Handling errors, when dest euqal src return EOK + */ +errno_t strncpy_error(char *strDest, size_t destMax, const char *strSrc, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("strncpy_s"); + return ERANGE; + } else if (strDest == NULL || strSrc == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("strncpy_s"); + if (strDest != NULL) { + strDest[0] = '\0'; + return EINVAL_AND_RESET; + } + return EINVAL; + } else if (count > SECUREC_STRING_MAX_LEN) { + strDest[0] = '\0'; /* clear dest string */ + SECUREC_ERROR_INVALID_RANGE("strncpy_s"); + return ERANGE_AND_RESET; + } else if (count == 0) { + strDest[0] = '\0'; + return EOK; + } + + return CheckSrcCountRange(strDest, destMax, strSrc, count); +} + +/* + * + * The strncpy_s function copies not more than n successive characters (not including the terminating null character) + * from the array pointed to by strSrc to the array pointed to by strDest. + * + * + * strDest Destination string. + * destMax The size of the destination string, in characters. + * strSrc Source string. + * count Number of characters to be copied. + * + * + * strDest is updated + * + * + * EOK Success + * EINVAL strDest is NULL and destMax != 0 and destMax <= SECUREC_STRING_MAX_LEN + * EINVAL_AND_RESET strDest != NULL and strSrc is NULL and destMax != 0 and destMax <= SECUREC_STRING_MAX_LEN + * ERANGE destMax is 0 and destMax > SECUREC_STRING_MAX_LEN + * ERANGE_AND_RESET strDest have not enough space and all other parameters are valid and not overlap + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and all parameters are valid + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +errno_t strncpy_s(char *strDest, size_t destMax, const char *strSrc, size_t count) +{ + if (SECUREC_STRNCPY_PARAM_OK(strDest, destMax, strSrc, count)) { + size_t minCpLen; /* use it to store the maxi length limit */ + if (count < destMax) { + minCpLen = SecStrMinLen(strSrc, count); /* no ending terminator */ + } else { + size_t tmpCount = destMax; +#ifdef SECUREC_COMPATIBLE_WIN_FORMAT + if (count == ((size_t)(-1))) { + tmpCount = destMax - 1; + } +#endif + minCpLen = SecStrMinLen(strSrc, tmpCount); + if (minCpLen == destMax) { + strDest[0] = '\0'; + SECUREC_ERROR_INVALID_RANGE("strncpy_s"); + return ERANGE_AND_RESET; + } + } + if (SECUREC_STRING_NO_OVERLAP(strDest, strSrc, minCpLen) || strDest == strSrc) { + /* Not overlap */ + SecDoMemcpy(strDest, strSrc, minCpLen); /* copy string without terminator */ + strDest[minCpLen] = '\0'; + return EOK; + } else { + strDest[0] = '\0'; + SECUREC_ERROR_BUFFER_OVERLAP("strncpy_s"); + return EOVERLAP_AND_RESET; + } + } + return strncpy_error(strDest, destMax, strSrc, count); +} + +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(strncpy_s); +#endif + diff --git a/third_party/securec/src/strtok_s.c b/third_party/securec/src/strtok_s.c new file mode 100644 index 0000000..18f977a --- /dev/null +++ b/third_party/securec/src/strtok_s.c @@ -0,0 +1,117 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * Find beginning of token (skip over leading delimiters).Note that + * there is no token if this loop sets string to point to the terminal null. + */ +static char *SecFindBegin(char *strToken, const char *strDelimit) +{ + char *token = strToken; + while (*token != '\0') { + const char *ctl = strDelimit; + while (*ctl != '\0' && *ctl != *token) { + ++ctl; + } + if (*ctl == '\0') { /* don't find any delimiter in string header, break the loop */ + break; + } + ++token; + } + return token; +} + +/* + * Find rest of token + */ +static char *SecFindRest(char *strToken, const char *strDelimit) +{ + /* Find the rest of the token. If it is not the end of the string, + * put a null there. + */ + char *token = strToken; + while (*token != '\0') { + const char *ctl = strDelimit; + while (*ctl != '\0' && *ctl != *token) { + ++ctl; + } + if (*ctl != '\0') { /* find a delimiter */ + *token++ = '\0'; /* set string termintor */ + break; + } + ++token; + } + return token; +} + +/* + * Find the final position pointer + */ +static char *SecUpdateToken(char *strToken, const char *strDelimit, char **context) +{ + /* point to updated position */ + char *token = SecFindRest(strToken, strDelimit); + /* record string position for next search in the context */ + *context = token; + /* Determine if a token has been found. */ + if (token == strToken) { + return NULL; + } + return strToken; +} + +/* + * + * The strtok_s function parses a string into a sequence of strToken, + * replace all characters in strToken string that match to strDelimit set with 0. + * On the first call to strtok_s the string to be parsed should be specified in strToken. + * In each subsequent call that should parse the same string, strToken should be NULL + * + * strToken String containing token or tokens. + * strDelimit Set of delimiter characters. + * context Used to store position information between calls + * to strtok_s + * + * context is updated + * + * On the first call returns the address of the first non \0 character, otherwise NULL is returned. + * In subsequent calls, the strtoken is set to NULL, and the context set is the same as the previous call, + * return NULL if the *context string length is equal 0, otherwise return *context. + */ +char *strtok_s(char *strToken, const char *strDelimit, char **context) +{ + char *orgToken = strToken; + /* validate delimiter and string context */ + if (context == NULL || strDelimit == NULL) { + return NULL; + } + /* valid input string and string pointer from where to search */ + if (orgToken == NULL && (*context) == NULL) { + return NULL; + } + /* If string is null, continue searching from previous string position stored in context */ + if (orgToken == NULL) { + orgToken = *context; + } + orgToken = SecFindBegin(orgToken, strDelimit); + return SecUpdateToken(orgToken, strDelimit, context); +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(strtok_s); +#endif + diff --git a/third_party/securec/src/swprintf_s.c b/third_party/securec/src/swprintf_s.c new file mode 100644 index 0000000..1fb0f6c --- /dev/null +++ b/third_party/securec/src/swprintf_s.c @@ -0,0 +1,51 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * + * The swprintf_s function is the wide-character equivalent of the sprintf_s function + * + * + * strDest Storage location for the output. + * destMax Maximum number of characters to store. + * format Format-control string. + * ... Optional arguments + * + * + * strDest is updated + * + * + * return the number of wide characters stored in strDest, not counting the terminating null wide character. + * return -1 if an error occurred. + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +int swprintf_s(wchar_t *strDest, size_t destMax, const wchar_t *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vswprintf_s(strDest, destMax, format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} + + diff --git a/third_party/securec/src/swscanf_s.c b/third_party/securec/src/swscanf_s.c new file mode 100644 index 0000000..c16045f --- /dev/null +++ b/third_party/securec/src/swscanf_s.c @@ -0,0 +1,57 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * + * The swscanf_s function is the wide-character equivalent of the sscanf_s function + * The swscanf_s function reads data from buffer into the location given by + * each argument. Every argument must be a pointer to a variable with a type + * that corresponds to a type specifier in format. The format argument controls + * the interpretation of the input fields and has the same form and function + * as the format argument for the scanf function. If copying takes place between + * strings that overlap, the behavior is undefined. + * + * + * buffer Stored data. + * format Format control string, see Format Specifications. + * ... Optional arguments. + * + * + * ... the converted value stored in user assigned address + * + * + * Each of these functions returns the number of fields successfully converted + * and assigned; The return value does not include fields that were read but not + * assigned. + * A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int swscanf_s(const wchar_t *buffer, const wchar_t *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vswscanf_s(buffer, format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} + + diff --git a/third_party/securec/src/vfscanf_s.c b/third_party/securec/src/vfscanf_s.c new file mode 100644 index 0000000..78444e4 --- /dev/null +++ b/third_party/securec/src/vfscanf_s.c @@ -0,0 +1,67 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secinput.h" + +/* + * + * The vfscanf_s function is equivalent to fscanf_s, with the variable argument list replaced by argList + * The vfscanf_s function reads data from the current position of stream into + * the locations given by argument (if any). Each argument must be a pointer + * to a variable of a type that corresponds to a type specifier in format. + * format controls the interpretation of the input fields and has the same + * form and function as the format argument for scanf. + * + * + * stream Pointer to FILE structure. + * format Format control string, see Format Specifications. + * argList pointer to list of arguments + * + * + * argList the converted value stored in user assigned address + * + * + * Each of these functions returns the number of fields successfully converted + * and assigned; the return value does not include fields that were read but + * not assigned. A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int vfscanf_s(FILE *stream, const char *format, va_list argList) +{ + int retVal; /* If initialization causes e838 */ + SecFileStream fStr; + + if ((stream == NULL) || (format == NULL)) { + SECUREC_ERROR_INVALID_PARAMTER("vfscanf_s"); + return SECUREC_SCANF_EINVAL; + } + if (stream == stdin) { + return vscanf_s(format, argList); + } + + SECUREC_LOCK_FILE(stream); + SECUREC_INIT_SEC_FILE_STREAM(fStr, SECUREC_FILE_STREAM_FLAG, stream, SECUREC_UNINITIALIZED_FILE_POS, NULL, 0); + retVal = SecInputS(&fStr, format, argList); + SECUREC_UNLOCK_FILE(stream); + if (retVal < 0) { + SECUREC_ERROR_INVALID_PARAMTER("vfscanf_s"); + return SECUREC_SCANF_EINVAL; + } + + return retVal; +} + + diff --git a/third_party/securec/src/vfwscanf_s.c b/third_party/securec/src/vfwscanf_s.c new file mode 100644 index 0000000..3ae62ee --- /dev/null +++ b/third_party/securec/src/vfwscanf_s.c @@ -0,0 +1,66 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secinput.h" + +/* + * + * The vfwscanf_s function is the wide-character equivalent of the vfscanf_s function + * The vfwscanf_s function reads data from the current position of stream into + * the locations given by argument (if any). Each argument must be a pointer + * to a variable of a type that corresponds to a type specifier in format. + * format controls the interpretation of the input fields and has the same form + * and function as the format argument for scanf. + * + * + * stream Pointer to FILE structure. + * format Format control string, see Format Specifications. + * argList pointer to list of arguments + * + * + * argList the converted value stored in user assigned address + * + * + * Each of these functions returns the number of fields successfully converted + * and assigned; the return value does not include fields that were read but + * not assigned. A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int vfwscanf_s(FILE *stream, const wchar_t *format, va_list argList) +{ + int retVal; /* If initialization causes e838 */ + SecFileStream fStr; + + if ((stream == NULL) || (format == NULL)) { + SECUREC_ERROR_INVALID_PARAMTER("vfwscanf_s"); + return SECUREC_SCANF_EINVAL; + } + if (stream == stdin) { + return vwscanf_s(format, argList); + } + + SECUREC_LOCK_FILE(stream); + SECUREC_INIT_SEC_FILE_STREAM(fStr, SECUREC_FILE_STREAM_FLAG, stream, SECUREC_UNINITIALIZED_FILE_POS, NULL, 0); + retVal = SecInputSW(&fStr, format, argList); + SECUREC_UNLOCK_FILE(stream); + if (retVal < 0) { + SECUREC_ERROR_INVALID_PARAMTER("vfwscanf_s"); + return SECUREC_SCANF_EINVAL; + } + return retVal; +} + + diff --git a/third_party/securec/src/vscanf_s.c b/third_party/securec/src/vscanf_s.c new file mode 100644 index 0000000..6666976 --- /dev/null +++ b/third_party/securec/src/vscanf_s.c @@ -0,0 +1,68 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secinput.h" + +/* + * + * The vscanf_s function is equivalent to scanf_s, with the variable argument list replaced by argList, + * The vscanf_s function reads data from the standard input stream stdin and + * writes the data into the location that's given by argument. Each argument + * must be a pointer to a variable of a type that corresponds to a type specifier + * in format. If copying occurs between strings that overlap, the behavior is + * undefined. + * + * + * format Format control string. + * argList pointer to list of arguments + * + * + * argList the converted value stored in user assigned address + * + * + * Returns the number of fields successfully converted and assigned; + * the return value does not include fields that were read but not assigned. + * A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int vscanf_s(const char *format, va_list argList) +{ + int retVal; /* If initialization causes e838 */ + SecFileStream fStr; + SECUREC_INIT_SEC_FILE_STREAM(fStr, SECUREC_FROM_STDIN_FLAG, stdin, 0, NULL, 0); + /* + * "va_list" has different definition on different platform, so we can't use argList == NULL + * to determine it's invalid. If you has fixed platform, you can check some fields to validate it, + * such as "argList == NULL" or argList.xxx != NULL or *(size_t *)&argList != 0. + */ + if (format == NULL || fStr.pf == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("vscanf_s"); + return SECUREC_SCANF_EINVAL; + } + + SECUREC_LOCK_STDIN(0, fStr.pf); + + retVal = SecInputS(&fStr, format, argList); + + SECUREC_UNLOCK_STDIN(0, fStr.pf); + if (retVal < 0) { + SECUREC_ERROR_INVALID_PARAMTER("vscanf_s"); + return SECUREC_SCANF_EINVAL; + } + return retVal; +} + + diff --git a/third_party/securec/src/vsnprintf_s.c b/third_party/securec/src/vsnprintf_s.c new file mode 100644 index 0000000..dfa55ba --- /dev/null +++ b/third_party/securec/src/vsnprintf_s.c @@ -0,0 +1,149 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secureprintoutput.h" + +#if SECUREC_ENABLE_VSNPRINTF +/* + * + * The vsnprintf_s function is equivalent to the vsnprintf function + * except for the parameter destMax/count and the explicit runtime-constraints violation + * The vsnprintf_s function takes a pointer to an argument list, then formats + * and writes up to count characters of the given data to the memory pointed + * to by strDest and appends a terminating null. + * + * + * strDest Storage location for the output. + * destMax The size of the strDest for output. + * count Maximum number of character to write(not including + * the terminating NULL) + * format Format-control string. + * argList pointer to list of arguments. + * + * + * strDest is updated + * + * + * return the number of characters written, not including the terminating null + * return -1 if an error occurs. + * return -1 if count < destMax and the output string has been truncated + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +int vsnprintf_s(char *strDest, size_t destMax, size_t count, const char *format, va_list argList) +{ + int retVal; + + if (format == NULL || strDest == NULL || destMax == 0 || destMax > SECUREC_STRING_MAX_LEN || + (count > (SECUREC_STRING_MAX_LEN - 1) && count != (size_t)(-1))) { + if (strDest != NULL && destMax > 0 && destMax <= SECUREC_STRING_MAX_LEN) { + strDest[0] = '\0'; + } + SECUREC_ERROR_INVALID_PARAMTER("vsnprintf_s"); + return -1; + } + + if (destMax > count) { + retVal = SecVsnprintfImpl(strDest, count + 1, format, argList); + if (retVal == SECUREC_PRINTF_TRUNCATE) { /* lsd add to keep dest buffer not destroyed 2014.2.18 */ + /* the string has been truncated, return -1 */ + return -1; /* to skip error handler, return strlen(strDest) or -1 */ + } + } else { + retVal = SecVsnprintfImpl(strDest, destMax, format, argList); +#ifdef SECUREC_COMPATIBLE_WIN_FORMAT + if (retVal == SECUREC_PRINTF_TRUNCATE && count == (size_t)(-1)) { + return -1; + } +#endif + } + + if (retVal < 0) { + strDest[0] = '\0'; /* empty the dest strDest */ + + if (retVal == SECUREC_PRINTF_TRUNCATE) { + /* Buffer too small */ + SECUREC_ERROR_INVALID_RANGE("vsnprintf_s"); + } + + SECUREC_ERROR_INVALID_PARAMTER("vsnprintf_s"); + return -1; + } + + return retVal; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(vsnprintf_s); +#endif +#endif + +#if SECUREC_SNPRINTF_TRUNCATED +/* + * + * The vsnprintf_truncated_s function is equivalent to the vsnprintf function + * except for the parameter destMax/count and the explicit runtime-constraints violation + * The vsnprintf_truncated_s function takes a pointer to an argument list, then formats + * and writes up to count characters of the given data to the memory pointed + * to by strDest and appends a terminating null. + * + * + * strDest Storage location for the output. + * destMax The size of the strDest for output. + * the terminating NULL) + * format Format-control string. + * argList pointer to list of arguments. + * + * + * strDest is updated + * + * + * return the number of characters written, not including the terminating null + * return -1 if an error occurs. + * return destMax-1 if output string has been truncated + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +int vsnprintf_truncated_s(char *strDest, size_t destMax, const char *format, va_list argList) +{ + int retVal; + + if (format == NULL || strDest == NULL || destMax == 0 || destMax > SECUREC_STRING_MAX_LEN) { + if (strDest != NULL && destMax > 0 && destMax <= SECUREC_STRING_MAX_LEN) { + strDest[0] = '\0'; + } + SECUREC_ERROR_INVALID_PARAMTER("vsnprintf_truncated_s"); + return -1; + } + + retVal = SecVsnprintfImpl(strDest, destMax, format, argList); + + if (retVal < 0) { + if (retVal == SECUREC_PRINTF_TRUNCATE) { + return (int)(destMax - 1); /* to skip error handler, return strlen(strDest) */ + } + strDest[0] = '\0'; /* empty the dest strDest */ + SECUREC_ERROR_INVALID_PARAMTER("vsnprintf_truncated_s"); + return -1; + } + + return retVal; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(vsnprintf_truncated_s); +#endif +#endif + + diff --git a/third_party/securec/src/vsprintf_s.c b/third_party/securec/src/vsprintf_s.c new file mode 100644 index 0000000..e74c774 --- /dev/null +++ b/third_party/securec/src/vsprintf_s.c @@ -0,0 +1,73 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secureprintoutput.h" + +/* + * + * The vsprintf_s function is equivalent to the vsprintf function + * except for the parameter destMax and the explicit runtime-constraints violation + * The vsprintf_s function takes a pointer to an argument list, and then formats + * and writes the given data to the memory pointed to by strDest. + * The function differ from the non-secure versions only in that the secure + * versions support positional parameters. + * + * + * strDest Storage location for the output. + * destMax Size of strDest + * format Format specification. + * argList pointer to list of arguments + * + * + * strDest is updated + * + * + * return the number of characters written, not including the terminating null character, + * return -1 if an error occurs. + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +int vsprintf_s(char *strDest, size_t destMax, const char *format, va_list argList) +{ + int retVal; /* If initialization causes e838 */ + + if (format == NULL || strDest == NULL || destMax == 0 || destMax > SECUREC_STRING_MAX_LEN) { + if (strDest != NULL && destMax > 0 && destMax <= SECUREC_STRING_MAX_LEN) { + strDest[0] = '\0'; + } + SECUREC_ERROR_INVALID_PARAMTER("vsprintf_s"); + return -1; + } + + retVal = SecVsnprintfImpl(strDest, destMax, format, argList); + + if (retVal < 0) { + strDest[0] = '\0'; + if (retVal == SECUREC_PRINTF_TRUNCATE) { + /* Buffer is too small */ + SECUREC_ERROR_INVALID_RANGE("vsprintf_s"); + } + SECUREC_ERROR_INVALID_PARAMTER("vsprintf_s"); + return -1; + } + + return retVal; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(vsprintf_s); +#endif + + diff --git a/third_party/securec/src/vsscanf_s.c b/third_party/securec/src/vsscanf_s.c new file mode 100644 index 0000000..e0a5ecd --- /dev/null +++ b/third_party/securec/src/vsscanf_s.c @@ -0,0 +1,88 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secinput.h" +#if defined(SECUREC_VXWORKS_PLATFORM) && (!defined(SECUREC_SYSAPI4VXWORKS) && !defined(SECUREC_CTYPE_MACRO_ADAPT)) +#include +#endif + +/* + * + * vsscanf_s + * + * + * + * The vsscanf_s function is equivalent to sscanf_s, with the variable argument list replaced by argList + * The vsscanf_s function reads data from buffer into the location given by + * each argument. Every argument must be a pointer to a variable with a type + * that corresponds to a type specifier in format. The format argument controls + * the interpretation of the input fields and has the same form and function + * as the format argument for the scanf function. + * If copying takes place between strings that overlap, the behavior is undefined. + * + * + * buffer Stored data + * format Format control string, see Format Specifications. + * argList pointer to list of arguments + * + * + * argList the converted value stored in user assigned address + * + * + * Each of these functions returns the number of fields successfully converted + * and assigned; the return value does not include fields that were read but + * not assigned. A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int vsscanf_s(const char *buffer, const char *format, va_list argList) +{ + size_t count; /* If initialization causes e838 */ + int retVal; + SecFileStream fStr; + + /* validation section */ + if (buffer == NULL || format == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("vsscanf_s"); + return SECUREC_SCANF_EINVAL; + } + count = strlen(buffer); + if (count == 0 || count > SECUREC_STRING_MAX_LEN) { + SecClearDestBuf(buffer, format, argList); + SECUREC_ERROR_INVALID_PARAMTER("vsscanf_s"); + return SECUREC_SCANF_EINVAL; + } +#ifdef SECUREC_VXWORKS_PLATFORM + /* + * in vxworks platform when buffer is white string, will set first %s argument tu zero.like following useage: + * " \v\f\t\r\n", "%s", str, strSize + * do not check all character, just first and last character then consider it is white string + */ + if (isspace((int)buffer[0]) && isspace((int)buffer[count - 1])) { + SecClearDestBuf(buffer, format, argList); + } +#endif + SECUREC_INIT_SEC_FILE_STREAM(fStr, SECUREC_MEM_STR_FLAG, NULL, 0, buffer, (int)count); + retVal = SecInputS(&fStr, format, argList); + if (retVal < 0) { + SECUREC_ERROR_INVALID_PARAMTER("vsscanf_s"); + return SECUREC_SCANF_EINVAL; + } + return retVal; +} +#if SECUREC_IN_KERNEL +EXPORT_SYMBOL(vsscanf_s); +#endif + diff --git a/third_party/securec/src/vswprintf_s.c b/third_party/securec/src/vswprintf_s.c new file mode 100644 index 0000000..3403a6b --- /dev/null +++ b/third_party/securec/src/vswprintf_s.c @@ -0,0 +1,66 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secureprintoutput.h" + + +/* + * + * The vswprintf_s function is the wide-character equivalent of the vsprintf_s function + * + * + * strDest Storage location for the output. + * destMax Size of strDest + * format Format specification. + * argList pointer to list of arguments + * + * + * strDest is updated + * + * + * return the number of wide characters stored in strDest, not counting the terminating null wide character. + * return -1 if an error occurred. + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +int vswprintf_s(wchar_t *strDest, size_t destMax, const wchar_t *format, va_list argList) +{ + int retVal; /* If initialization causes e838 */ + + if (format == NULL || strDest == NULL || destMax == 0 || destMax > (SECUREC_WCHAR_STRING_MAX_LEN)) { + if (strDest != NULL && destMax > 0) { + strDest[0] = '\0'; + } + SECUREC_ERROR_INVALID_PARAMTER("vswprintf_s"); + return -1; + } + + retVal = SecVswprintfImpl(strDest, destMax, format, argList); + + if (retVal < 0) { + strDest[0] = '\0'; + if (retVal == SECUREC_PRINTF_TRUNCATE) { + /* Buffer too small */ + SECUREC_ERROR_INVALID_RANGE("vswprintf_s"); + } + SECUREC_ERROR_INVALID_PARAMTER("vswprintf_s"); + return -1; + } + + return retVal; +} + + diff --git a/third_party/securec/src/vswscanf_s.c b/third_party/securec/src/vswscanf_s.c new file mode 100644 index 0000000..269e105 --- /dev/null +++ b/third_party/securec/src/vswscanf_s.c @@ -0,0 +1,79 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secinput.h" + +static size_t SecWcslen(const wchar_t *s) +{ + const wchar_t *end = s; + while (*end != L'\0') { + ++end; + } + return ((size_t)((end - s))); +} + +/* + * + * The vswscanf_s function is the wide-character equivalent of the vsscanf_s function + * The vsscanf_s function reads data from buffer into the location given by + * each argument. Every argument must be a pointer to a variable with a type + * that corresponds to a type specifier in format. + * The format argument controls the interpretation of the input fields and + * has the same form and function as the format argument for the scanf function. + * If copying takes place between strings that overlap, the behavior is undefined. + * + * + * buffer Stored data + * format Format control string, see Format Specifications. + * argList pointer to list of arguments + * + * + * argList the converted value stored in user assigned address + * + * + * Each of these functions returns the number of fields successfully converted + * and assigned; the return value does not include fields that were read but + * not assigned. A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int vswscanf_s(const wchar_t *buffer, const wchar_t *format, va_list argList) +{ + size_t count; /* If initialization causes e838 */ + SecFileStream fStr; + int retVal; + + /* validation section */ + if (buffer == NULL || format == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("vswscanf_s"); + return SECUREC_SCANF_EINVAL; + } + count = SecWcslen(buffer); + if (count == 0 || count > SECUREC_WCHAR_STRING_MAX_LEN) { + SecClearDestBufW(buffer, format, argList); + SECUREC_ERROR_INVALID_PARAMTER("vswscanf_s"); + return SECUREC_SCANF_EINVAL; + } + SECUREC_INIT_SEC_FILE_STREAM(fStr, SECUREC_MEM_STR_FLAG, NULL, 0,\ + (const char *)buffer, (int)count * ((int)sizeof(wchar_t))); + retVal = SecInputSW(&fStr, format, argList); + if (retVal < 0) { + SECUREC_ERROR_INVALID_PARAMTER("vswscanf_s"); + return SECUREC_SCANF_EINVAL; + } + return retVal; +} + + diff --git a/third_party/securec/src/vwscanf_s.c b/third_party/securec/src/vwscanf_s.c new file mode 100644 index 0000000..56e0f6b --- /dev/null +++ b/third_party/securec/src/vwscanf_s.c @@ -0,0 +1,67 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "secinput.h" + +/* + * + * The vwscanf_s function is the wide-character equivalent of the vscanf_s function + * The vwscanf_s function is the wide-character version of vscanf_s. The + * function reads data from the standard input stream stdin and writes the + * data into the location that's given by argument. Each argument must be a + * pointer to a variable of a type that corresponds to a type specifier in + * format. If copying occurs between strings that overlap, the behavior is + * undefined. + * + * + * format Format control string. + * argList pointer to list of arguments + * + * + * argList the converted value stored in user assigned address + * + * + * Returns the number of fields successfully converted and assigned; + * the return value does not include fields that were read but not assigned. + * A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ +int vwscanf_s(const wchar_t *format, va_list argList) +{ + int retVal; /* If initialization causes e838 */ + SecFileStream fStr; + + SECUREC_INIT_SEC_FILE_STREAM(fStr, SECUREC_FROM_STDIN_FLAG, stdin, 0, NULL, 0); + if (format == NULL || fStr.pf == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("vwscanf_s"); + return SECUREC_SCANF_EINVAL; + } + + SECUREC_LOCK_STDIN(0, fStr.pf); + + retVal = SecInputSW(&fStr, format, argList); + + SECUREC_UNLOCK_STDIN(0, fStr.pf); + + if (retVal < 0) { + SECUREC_ERROR_INVALID_PARAMTER("vwscanf_s"); + return SECUREC_SCANF_EINVAL; + } + + return retVal; +} + + diff --git a/third_party/securec/src/wcscat_s.c b/third_party/securec/src/wcscat_s.c new file mode 100644 index 0000000..51254b3 --- /dev/null +++ b/third_party/securec/src/wcscat_s.c @@ -0,0 +1,111 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_DO_MEMCPY 1 + +#include "securecutil.h" + +/* + * Befor this function, the basic parameter checking has been done + */ +static errno_t SecDoWcscat(wchar_t *strDest, size_t destMax, const wchar_t *strSrc) +{ + size_t destLen; + size_t srcLen; + size_t maxCount; /* Store the maximum available count */ + + /* To calculate the length of a wide character, the parameter must be a wide character */ + SECUREC_CALC_WSTR_LEN(strDest, destMax, &destLen); + maxCount = destMax - destLen; + SECUREC_CALC_WSTR_LEN(strSrc, maxCount, &srcLen); + + if (SECUREC_CAT_STRING_IS_OVERLAP(strDest, destLen, strSrc, srcLen)) { + strDest[0] = L'\0'; + if (strDest + destLen <= strSrc && destLen == destMax) { + SECUREC_ERROR_INVALID_PARAMTER("wcscat_s"); + return EINVAL_AND_RESET; + } + SECUREC_ERROR_BUFFER_OVERLAP("wcscat_s"); + return EOVERLAP_AND_RESET; + } + if (srcLen + destLen >= destMax || strDest == strSrc) { + strDest[0] = L'\0'; + if (destLen == destMax) { + SECUREC_ERROR_INVALID_PARAMTER("wcscat_s"); + return EINVAL_AND_RESET; + } + SECUREC_ERROR_INVALID_RANGE("wcscat_s"); + return ERANGE_AND_RESET; + } + SecDoMemcpy(strDest + destLen, strSrc, (srcLen + 1) * sizeof(wchar_t)); /* single character length include \0 */ + return EOK; +} + +/* + * + * The wcscat_s function appends a copy of the wide string pointed to by strSrc +* (including the terminating null wide character) + * to the end of the wide string pointed to by strDest. + * The arguments and return value of wcscat_s are wide-character strings. + * + * The wcscat_s function appends strSrc to strDest and terminates the resulting + * string with a null character. The initial character of strSrc overwrites the + * terminating null character of strDest. wcscat_s will return EOVERLAP_AND_RESET if the + * source and destination strings overlap. + * + * Note that the second parameter is the total size of the buffer, not the + * remaining size. + * + * + * strDest Null-terminated destination string buffer. + * destMax Size of the destination string buffer. + * strSrc Null-terminated source string buffer. + * + * + * strDest is updated + * + * + * EOK Success + * EINVAL strDest is NULL and destMax != 0 and destMax <= SECUREC_WCHAR_STRING_MAX_LEN + * EINVAL_AND_RESET (strDest unterminated and all other parameters are valid) or + * (strDest != NULL and strSrc is NULLL and destMax != 0 + * and destMax <= SECUREC_WCHAR_STRING_MAX_LEN) + * ERANGE destMax > SECUREC_WCHAR_STRING_MAX_LEN or destMax is 0 + * ERANGE_AND_RESET strDest have not enough space and all other parameters are valid and not overlap + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and all parameters are valid + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +errno_t wcscat_s(wchar_t *strDest, size_t destMax, const wchar_t *strSrc) +{ + if (destMax == 0 || destMax > SECUREC_WCHAR_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("wcscat_s"); + return ERANGE; + } + + if (strDest == NULL || strSrc == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("wcscat_s"); + if (strDest != NULL) { + strDest[0] = L'\0'; + return EINVAL_AND_RESET; + } + return EINVAL; + } + + return SecDoWcscat(strDest, destMax, strSrc); +} + + diff --git a/third_party/securec/src/wcscpy_s.c b/third_party/securec/src/wcscpy_s.c new file mode 100644 index 0000000..2c348d4 --- /dev/null +++ b/third_party/securec/src/wcscpy_s.c @@ -0,0 +1,91 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_DO_MEMCPY 1 + +#include "securecutil.h" + +static errno_t SecDoWcscpy(wchar_t *strDest, size_t destMax, const wchar_t *strSrc) +{ + size_t srcStrLen; + + SECUREC_CALC_WSTR_LEN(strSrc, destMax, &srcStrLen); + if (srcStrLen == destMax) { + strDest[0] = '\0'; + SECUREC_ERROR_INVALID_RANGE("wcscpy_s"); + return ERANGE_AND_RESET; + } + if (strDest == strSrc) { + return EOK; + } + + if (SECUREC_STRING_NO_OVERLAP(strDest, strSrc, srcStrLen)) { + /* performance optimization srcStrLen include '\0' */ + SecDoMemcpy(strDest, strSrc, (srcStrLen + 1) * sizeof(wchar_t)); /* single character length include \0 */ + return EOK; + } else { + strDest[0] = L'\0'; + SECUREC_ERROR_BUFFER_OVERLAP("wcscpy_s"); + return EOVERLAP_AND_RESET; + } +} + +/* + * + * The wcscpy_s function copies the wide string pointed to by strSrc + * (including theterminating null wide character) into the array pointed to by strDest + + * + * strDest Destination string buffer + * destMax Size of the destination string buffer. + * strSrc Null-terminated source string buffer. + * + * + * strDest is updated. + * + * + * EOK Success + * EINVAL strDest is NULL and destMax != 0 and destMax <= SECUREC_WCHAR_STRING_MAX_LEN + * EINVAL_AND_RESET strDest != NULL and strSrc is NULLL and destMax != 0 + * and destMax <= SECUREC_WCHAR_STRING_MAX_LEN + * ERANGE destMax > SECUREC_WCHAR_STRING_MAX_LEN or destMax is 0 + * ERANGE_AND_RESET destMax <= length of strSrc and strDest != strSrc + * and strDest != NULL and strSrc != NULL and destMax != 0 + * and destMax <= SECUREC_WCHAR_STRING_MAX_LEN and not overlap + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and destMax != 0 + * and destMax <= SECUREC_WCHAR_STRING_MAX_LEN + * and strDest != NULL and strSrc !=NULL and strDest != strSrc + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +errno_t wcscpy_s(wchar_t *strDest, size_t destMax, const wchar_t *strSrc) +{ + if (destMax == 0 || destMax > SECUREC_WCHAR_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("wcscpy_s"); + return ERANGE; + } + if (strDest == NULL || strSrc == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("wcscpy_s"); + if (strDest != NULL) { + strDest[0] = L'\0'; + return EINVAL_AND_RESET; + } + return EINVAL; + } + return SecDoWcscpy(strDest, destMax, strSrc); +} + + diff --git a/third_party/securec/src/wcsncat_s.c b/third_party/securec/src/wcsncat_s.c new file mode 100644 index 0000000..bc9e6e3 --- /dev/null +++ b/third_party/securec/src/wcsncat_s.c @@ -0,0 +1,118 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_DO_MEMCPY 1 + +#include "securecutil.h" + +/* + * Befor this function, the basic parameter checking has been done + */ +static errno_t SecDoWcsncat(wchar_t *strDest, size_t destMax, const wchar_t *strSrc, size_t count) +{ + size_t destLen; + size_t srcLen; + + /* To calculate the length of a wide character, the parameter must be a wide character */ + SECUREC_CALC_WSTR_LEN(strDest, destMax, &destLen); + SECUREC_CALC_WSTR_LEN(strSrc, count, &srcLen); + + if (SECUREC_CAT_STRING_IS_OVERLAP(strDest, destLen, strSrc, srcLen)) { + strDest[0] = L'\0'; + if (strDest + destLen <= strSrc && destLen == destMax) { + SECUREC_ERROR_INVALID_PARAMTER("wcsncat_s"); + return EINVAL_AND_RESET; + } + SECUREC_ERROR_BUFFER_OVERLAP("wcsncat_s"); + return EOVERLAP_AND_RESET; + } + if (srcLen + destLen >= destMax || strDest == strSrc) { + strDest[0] = L'\0'; + if (destLen == destMax) { + SECUREC_ERROR_INVALID_PARAMTER("wcsncat_s"); + return EINVAL_AND_RESET; + } + SECUREC_ERROR_INVALID_RANGE("wcsncat_s"); + return ERANGE_AND_RESET; + } + SecDoMemcpy(strDest + destLen, strSrc, srcLen * sizeof(wchar_t)); /* no terminator */ + *(strDest + destLen + srcLen) = L'\0'; + return EOK; +} + +/* + * + * The wcsncat_s function appends not more than n successive wide characters + * (not including the terminating null wide character) + * from the array pointed to by strSrc to the end of the wide string pointed to by strDest. + * + * The wcsncat_s function try to append the first D characters of strSrc to + * the end of strDest, where D is the lesser of count and the length of strSrc. + * If appending those D characters will fit within strDest (whose size is + * given as destMax) and still leave room for a null terminator, then those + * characters are appended, starting at the original terminating null of + * strDest, and a new terminating null is appended; otherwise, strDest[0] is + * set to the null character. + * + * + * strDest Null-terminated destination string. + * destMax Size of the destination buffer. + * strSrc Null-terminated source string. + * count Number of character to append, or truncate. + * + * + * strDest is updated + * + * + * EOK Success + * EINVAL strDest is NULL and destMax != 0 and destMax <= SECUREC_WCHAR_STRING_MAX_LEN + * EINVAL_AND_RESET (strDest unterminated and all other parameters are valid) or + * (strDest != NULL and strSrc is NULLL and destMax != 0 and destMax <= SECUREC_WCHAR_STRING_MAX_LEN) + * ERANGE destMax > SECUREC_WCHAR_STRING_MAX_LEN or destMax is 0 + * ERANGE_AND_RESET strDest have not enough space and all other parameters are valid and not overlap + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and all parameters are valid + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +errno_t wcsncat_s(wchar_t *strDest, size_t destMax, const wchar_t *strSrc, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_WCHAR_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("wcsncat_s"); + return ERANGE; + } + if (strDest == NULL || strSrc == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("wcsncat_s"); + if (strDest != NULL) { + strDest[0] = L'\0'; + return EINVAL_AND_RESET; + } + return EINVAL; + } + if (count > SECUREC_WCHAR_STRING_MAX_LEN) { +#ifdef SECUREC_COMPATIBLE_WIN_FORMAT + if (count == ((size_t)-1)) { + /* Windows internal functions may pass in -1 when calling this function */ + return SecDoWcsncat(strDest, destMax, strSrc, destMax); + } +#endif + strDest[0] = L'\0'; + SECUREC_ERROR_INVALID_RANGE("wcsncat_s"); + return ERANGE_AND_RESET; + } + return SecDoWcsncat(strDest, destMax, strSrc, count); +} + + diff --git a/third_party/securec/src/wcsncpy_s.c b/third_party/securec/src/wcsncpy_s.c new file mode 100644 index 0000000..746b1d4 --- /dev/null +++ b/third_party/securec/src/wcsncpy_s.c @@ -0,0 +1,111 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define SECUREC_INLINE_DO_MEMCPY 1 + +#include "securecutil.h" + +static errno_t SecDoWcsncpy(wchar_t *strDest, size_t destMax, const wchar_t *strSrc, size_t count) +{ + size_t srcStrLen; + if (count < destMax) { + SECUREC_CALC_WSTR_LEN(strSrc, count, &srcStrLen); + } else { + SECUREC_CALC_WSTR_LEN(strSrc, destMax, &srcStrLen); + } + if (srcStrLen == destMax) { + strDest[0] = '\0'; + SECUREC_ERROR_INVALID_RANGE("wcsncpy_s"); + return ERANGE_AND_RESET; + } + if (strDest == strSrc) { + return EOK; + } + if (SECUREC_STRING_NO_OVERLAP(strDest, strSrc, srcStrLen)) { + /* performance optimization srcStrLen not include '\0' */ + SecDoMemcpy(strDest, strSrc, srcStrLen * sizeof(wchar_t)); + *(strDest + srcStrLen) = L'\0'; + return EOK; + } else { + strDest[0] = L'\0'; + SECUREC_ERROR_BUFFER_OVERLAP("wcsncpy_s"); + return EOVERLAP_AND_RESET; + } +} + +/* + * + * The wcsncpy_s function copies not more than n successive wide characters + * (not including the terminating null wide character) + * from the array pointed to by strSrc to the array pointed to by strDest + * + * + * strDest Destination string. + * destMax The size of the destination string, in characters. + * strSrc Source string. + * count Number of characters to be copied. + * + * + * strDest is updated + * + * + * EOK Success + * EINVAL strDest is NULL and destMax != 0 and destMax <= SECUREC_WCHAR_STRING_MAX_LEN + * EINVAL_AND_RESET strDest != NULL and strSrc is NULLL and destMax != 0 + * and destMax <= SECUREC_WCHAR_STRING_MAX_LEN + * ERANGE destMax > SECUREC_WCHAR_STRING_MAX_LEN or destMax is 0 + * ERANGE_AND_RESET count > SECUREC_WCHAR_STRING_MAX_LEN or + * (destMax <= length of strSrc and destMax <= count and strDest != strSrc + * and strDest != NULL and strSrc != NULL and destMax != 0 and + * destMax <= SECUREC_WCHAR_STRING_MAX_LEN and not overlap) + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and all parameters are valid + * + * + * If there is a runtime-constraint violation, strDest[0] will be set to the '\0' when strDest and destMax valid + */ +errno_t wcsncpy_s(wchar_t *strDest, size_t destMax, const wchar_t *strSrc, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_WCHAR_STRING_MAX_LEN) { + SECUREC_ERROR_INVALID_RANGE("wcsncpy_s"); + return ERANGE; + } + if (strDest == NULL || strSrc == NULL) { + SECUREC_ERROR_INVALID_PARAMTER("wcsncpy_s"); + if (strDest != NULL) { + strDest[0] = '\0'; + return EINVAL_AND_RESET; + } + return EINVAL; + } + if (count > SECUREC_WCHAR_STRING_MAX_LEN) { +#ifdef SECUREC_COMPATIBLE_WIN_FORMAT + if (count == (size_t)(-1)) { + return SecDoWcsncpy(strDest, destMax, strSrc, destMax - 1); + } +#endif + strDest[0] = '\0'; /* clear dest string */ + SECUREC_ERROR_INVALID_RANGE("wcsncpy_s"); + return ERANGE_AND_RESET; + } + + if (count == 0) { + strDest[0] = '\0'; + return EOK; + } + + return SecDoWcsncpy(strDest, destMax, strSrc, count); +} + diff --git a/third_party/securec/src/wcstok_s.c b/third_party/securec/src/wcstok_s.c new file mode 100644 index 0000000..99c524f --- /dev/null +++ b/third_party/securec/src/wcstok_s.c @@ -0,0 +1,116 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * FindBegin Wide character postion function + */ +static wchar_t *SecFindBeginW(wchar_t *strToken, const wchar_t *strDelimit) +{ + /* Find beginning of token (skip over leading delimiters). Note that + * there is no token if this loop sets string to point to the terminal null. + */ + wchar_t *token = strToken; + while (*token != L'\0') { + const wchar_t *ctl = strDelimit; + while (*ctl != L'\0' && *ctl != *token) { + ++ctl; + } + if (*ctl == L'\0') { + break; + } + ++token; + } + return token; +} + +/* + * FindBegin rest Wide character postion function + */ +static wchar_t *SecFindRestW(wchar_t *strToken, const wchar_t *strDelimit) +{ + /* Find the end of the token. If it is not the end of the string, + * put a null there. + */ + wchar_t *token = strToken; + while (*token != L'\0') { + const wchar_t *ctl = strDelimit; + while (*ctl != L'\0' && *ctl != *token) { + ++ctl; + } + if (*ctl != L'\0') { + *token++ = L'\0'; + break; + } + ++token; + } + return token; +} + +/* + * Update Token wide character function + */ +static wchar_t *SecUpdateTokenW(wchar_t *strToken, const wchar_t *strDelimit, wchar_t **context) +{ + /* point to updated position */ + wchar_t *token = SecFindRestW(strToken, strDelimit); + /* Update the context */ + *context = token; + /* Determine if a token has been found. */ + if (token == strToken) { + return NULL; + } + return strToken; +} + +/* + * + * wcstok_s + * + * + * + * The wcstok_s function is the wide-character equivalent of the strtok_s function + * + * + * strToken String containing token or tokens. + * strDelimit Set of delimiter characters. + * context Used to store position information between calls to + * wcstok_s. + * + * + * context is updated + * + * The wcstok_s function is the wide-character equivalent of the strtok_s function + */ +wchar_t *wcstok_s(wchar_t *strToken, const wchar_t *strDelimit, wchar_t **context) +{ + wchar_t *orgToken = strToken; + /* validation section */ + if (context == NULL || strDelimit == NULL) { + return NULL; + } + if (orgToken == NULL && (*context) == NULL) { + return NULL; + } + /* If string==NULL, continue with previous string */ + if (orgToken == NULL) { + orgToken = *context; + } + orgToken = SecFindBeginW(orgToken, strDelimit); + return SecUpdateTokenW(orgToken, strDelimit, context); +} + diff --git a/third_party/securec/src/wmemcpy_s.c b/third_party/securec/src/wmemcpy_s.c new file mode 100644 index 0000000..236fcce --- /dev/null +++ b/third_party/securec/src/wmemcpy_s.c @@ -0,0 +1,68 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securecutil.h" + +/* + * + * The wmemcpy_s function copies n successive wide characters + * from the object pointed to by src into the object pointed to by dest.t. + * + * + * dest Destination buffer. + * destMax Size of the destination buffer. + * src Buffer to copy from. + * count Number of characters to copy. + * + * + * dest buffer is uptdated. + * + * + * EOK Success + * EINVAL dest is NULL and destMax != 0 and count <= destMax + * and destMax <= SECUREC_WCHAR_MEM_MAX_LEN + * EINVAL_AND_RESET dest != NULL and src is NULLL and destMax != 0 + * and destMax <= SECUREC_WCHAR_MEM_MAX_LEN and count <= destMax + * ERANGE destMax > SECUREC_WCHAR_MEM_MAX_LEN or destMax is 0 or + * (count > destMax and dest is NULL and destMax != 0 + * and destMax <= SECUREC_WCHAR_MEM_MAX_LEN) + * ERANGE_AND_RESET count > destMax and dest != NULL and destMax != 0 + * and destMax <= SECUREC_WCHAR_MEM_MAX_LEN + * EOVERLAP_AND_RESET dest buffer and source buffer are overlapped and + * count <= destMax destMax != 0 and destMax <= SECUREC_WCHAR_MEM_MAX_LEN + * and dest != NULL and src != NULL and dest != src + * + * if an error occured, dest will be filled with 0 when dest and destMax valid . + * If the source and destination overlap, the behavior of wmemcpy_s is undefined. + * Use wmemmove_s to handle overlapping regions. + */ +errno_t wmemcpy_s(wchar_t *dest, size_t destMax, const wchar_t *src, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_WCHAR_MEM_MAX_LEN) { + SECUREC_ERROR_INVALID_PARAMTER("wmemcpy_s"); + return ERANGE; + } + if (count > destMax) { + SECUREC_ERROR_INVALID_PARAMTER("wmemcpy_s"); + if (dest != NULL) { + (void)memset(dest, 0, destMax * sizeof(wchar_t)); + return ERANGE_AND_RESET; + } + return ERANGE; + } + return memcpy_s(dest, destMax * sizeof(wchar_t), src, count * sizeof(wchar_t)); +} + diff --git a/third_party/securec/src/wmemmove_s.c b/third_party/securec/src/wmemmove_s.c new file mode 100644 index 0000000..2ef549a --- /dev/null +++ b/third_party/securec/src/wmemmove_s.c @@ -0,0 +1,67 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securecutil.h" + +/* + * + * The wmemmove_s function copies n successive wide characters from the object pointed + * to by src into the object pointed to by dest. + * + * + * dest Destination buffer. + * destMax Size of the destination buffer. + * src Source object. + * count Number of bytes or character to copy. + * + * + * dest is updated. + * + * + * EOK Success + * EINVAL dest is NULL and destMax != 0 and count <= destMax + * and destMax <= SECUREC_WCHAR_MEM_MAX_LEN + * EINVAL_AND_RESET dest != NULL and src is NULLL and destMax != 0 + * and destMax <= SECUREC_WCHAR_MEM_MAX_LEN and count <= destMax + * ERANGE destMax > SECUREC_WCHAR_MEM_MAX_LEN or destMax is 0 or + * (count > destMax and dest is NULL and destMax != 0 + * and destMax <= SECUREC_WCHAR_MEM_MAX_LEN) + * ERANGE_AND_RESET count > destMax and dest != NULL and destMax != 0 + * and destMax <= SECUREC_WCHAR_MEM_MAX_LEN + * + * + * If an error occured, dest will be filled with 0 when dest and destMax valid. + * If some regions of the source area and the destination overlap, wmemmove_s + * ensures that the original source bytes in the overlapping region are copied + * before being overwritten + */ +errno_t wmemmove_s(wchar_t *dest, size_t destMax, const wchar_t *src, size_t count) +{ + if (destMax == 0 || destMax > SECUREC_WCHAR_MEM_MAX_LEN) { + SECUREC_ERROR_INVALID_PARAMTER("wmemmove_s"); + return ERANGE; + } + if (count > destMax) { + SECUREC_ERROR_INVALID_PARAMTER("wmemmove_s"); + if (dest != NULL) { + (void)memset(dest, 0, destMax * sizeof(wchar_t)); + return ERANGE_AND_RESET; + } + return ERANGE; + } + return memmove_s(dest, destMax * sizeof(wchar_t), src, count * sizeof(wchar_t)); +} + diff --git a/third_party/securec/src/wscanf_s.c b/third_party/securec/src/wscanf_s.c new file mode 100644 index 0000000..c1dcce2 --- /dev/null +++ b/third_party/securec/src/wscanf_s.c @@ -0,0 +1,55 @@ +/** + * Copyright 2020 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "securec.h" + +/* + * + * + * The wscanf_s function is the wide-character equivalent of the scanf_s function + * The wscanf_s function reads data from the standard input stream stdin and + * writes the data into the location that's given by argument. Each argument + * must be a pointer to a variable of a type that corresponds to a type specifier + * in format. If copying occurs between strings that overlap, the behavior is + * undefined. + * + * + * format Format control string. + * ... Optional arguments. + * + * + * ... the converted value stored in user assigned address + * + * + * Returns the number of fields successfully converted and assigned; + * the return value does not include fields that were read but not assigned. + * A return value of 0 indicates that no fields were assigned. + * return -1 if an error occurs. + */ + +int wscanf_s(const wchar_t *format, ...) +{ + int ret; /* If initialization causes e838 */ + va_list argList; + + va_start(argList, format); + ret = vwscanf_s(format, argList); + va_end(argList); + (void)argList; /* to clear e438 last value assigned not used , the compiler will optimize this code */ + + return ret; +} +