From 665cb60dbd35d6a185727ec9d88d48d46e10679c Mon Sep 17 00:00:00 2001 From: overweight <5324761+overweight@user.noreply.gitee.com> Date: Mon, 30 Sep 2019 10:53:41 -0400 Subject: [PATCH] Package init --- .clang-format | 123 + .gitignore | 2 + CMakeLists.txt | 123 + License/LICENSE | 121 + README.md | 25 + cmake/checker.cmake | 147 ++ cmake/helper.cmake | 9 + cmake/options.cmake | 47 + cmake/protoc.cmake | 62 + cmake/set_build_flags.cmake | 16 + config.h.in | 1 + docs/architecture.md | 41 + docs/design/CNI_architecture.png | Bin 0 -> 139376 bytes docs/design/architecture.png | Bin 0 -> 47668 bytes docs/developer_guide.md | 81 + docs/install_guide.md | 241 ++ docs/limitations.md | 0 iSulad.spec | 187 ++ lcrd.pc.in | 12 + src/CMakeLists.txt | 215 ++ src/api/services/containers/container.proto | 447 ++++ .../services/containers/rest/container.rest.h | 84 + src/api/services/cri/api.proto | 1159 +++++++++ src/api/services/health/health.proto | 19 + src/api/services/images/images.proto | 137 + src/api/services/images/rest/image.rest.h | 36 + src/api/types/descriptor.proto | 15 + src/cmd/CMakeLists.txt | 10 + src/cmd/commander.c | 816 ++++++ src/cmd/commander.h | 115 + src/cmd/lcrc/CMakeLists.txt | 27 + src/cmd/lcrc/arguments.c | 272 ++ src/cmd/lcrc/arguments.h | 346 +++ src/cmd/lcrc/base/CMakeLists.txt | 7 + src/cmd/lcrc/base/create.c | 2023 +++++++++++++++ src/cmd/lcrc/base/create.h | 170 ++ src/cmd/lcrc/base/kill.c | 139 + src/cmd/lcrc/base/kill.h | 29 + src/cmd/lcrc/base/rename.c | 98 + src/cmd/lcrc/base/rename.h | 25 + src/cmd/lcrc/base/restart.c | 119 + src/cmd/lcrc/base/restart.h | 27 + src/cmd/lcrc/base/rm.c | 158 ++ src/cmd/lcrc/base/rm.h | 31 + src/cmd/lcrc/base/run.c | 219 ++ src/cmd/lcrc/base/run.h | 33 + src/cmd/lcrc/base/start.c | 246 ++ src/cmd/lcrc/base/start.h | 31 + src/cmd/lcrc/base/stop.c | 123 + src/cmd/lcrc/base/stop.h | 31 + src/cmd/lcrc/commands.c | 432 ++++ src/cmd/lcrc/commands.h | 72 + src/cmd/lcrc/extend/CMakeLists.txt | 7 + src/cmd/lcrc/extend/events.c | 172 ++ src/cmd/lcrc/extend/events.h | 33 + src/cmd/lcrc/extend/export.c | 133 + src/cmd/lcrc/extend/export.h | 28 + src/cmd/lcrc/extend/pause.c | 118 + src/cmd/lcrc/extend/pause.h | 25 + src/cmd/lcrc/extend/resume.c | 118 + src/cmd/lcrc/extend/resume.h | 25 + src/cmd/lcrc/extend/stats.c | 258 ++ src/cmd/lcrc/extend/stats.h | 33 + src/cmd/lcrc/extend/update.c | 166 ++ src/cmd/lcrc/extend/update.h | 48 + src/cmd/lcrc/images/CMakeLists.txt | 7 + src/cmd/lcrc/images/images.c | 280 ++ src/cmd/lcrc/images/images.h | 25 + src/cmd/lcrc/images/load.c | 174 ++ src/cmd/lcrc/images/load.h | 32 + src/cmd/lcrc/images/login.c | 232 ++ src/cmd/lcrc/images/login.h | 31 + src/cmd/lcrc/images/logout.c | 115 + src/cmd/lcrc/images/logout.h | 24 + src/cmd/lcrc/images/pull.c | 114 + src/cmd/lcrc/images/pull.h | 28 + src/cmd/lcrc/images/rmi.c | 132 + src/cmd/lcrc/images/rmi.h | 28 + src/cmd/lcrc/information/CMakeLists.txt | 7 + src/cmd/lcrc/information/health.c | 103 + src/cmd/lcrc/information/health.h | 28 + src/cmd/lcrc/information/info.c | 150 ++ src/cmd/lcrc/information/info.h | 25 + src/cmd/lcrc/information/inspect.c | 814 ++++++ src/cmd/lcrc/information/inspect.h | 31 + src/cmd/lcrc/information/logs.c | 152 ++ src/cmd/lcrc/information/logs.h | 31 + src/cmd/lcrc/information/ps.c | 411 +++ src/cmd/lcrc/information/ps.h | 32 + src/cmd/lcrc/information/top.c | 156 ++ src/cmd/lcrc/information/top.h | 25 + src/cmd/lcrc/information/version.c | 124 + src/cmd/lcrc/information/version.h | 25 + src/cmd/lcrc/information/wait.c | 133 + src/cmd/lcrc/information/wait.h | 26 + src/cmd/lcrc/main.c | 188 ++ src/cmd/lcrc/stream/CMakeLists.txt | 7 + src/cmd/lcrc/stream/attach.c | 389 +++ src/cmd/lcrc/stream/attach.h | 27 + src/cmd/lcrc/stream/cp.c | 350 +++ src/cmd/lcrc/stream/cp.h | 25 + src/cmd/lcrc/stream/exec.c | 358 +++ src/cmd/lcrc/stream/exec.h | 34 + src/cmd/lcrd/CMakeLists.txt | 5 + src/cmd/lcrd/arguments.c | 244 ++ src/cmd/lcrd/arguments.h | 71 + src/cmd/lcrd/commands.c | 545 ++++ src/cmd/lcrd/commands.h | 102 + src/cmd/lcrd/main.c | 1702 +++++++++++++ src/config/CMakeLists.txt | 7 + src/config/lcrd_config.c | 1754 +++++++++++++ src/config/lcrd_config.h | 104 + src/connect/CMakeLists.txt | 8 + src/connect/client/CMakeLists.txt | 18 + src/connect/client/connect.h | 35 + src/connect/client/grpc/CMakeLists.txt | 7 + src/connect/client/grpc/client_base.h | 207 ++ src/connect/client/grpc/grpc_client.cc | 34 + src/connect/client/grpc/grpc_client.h | 30 + .../client/grpc/grpc_containers_client.cc | 2247 +++++++++++++++++ .../client/grpc/grpc_containers_client.h | 30 + src/connect/client/grpc/grpc_images_client.cc | 464 ++++ src/connect/client/grpc/grpc_images_client.h | 30 + src/connect/client/lcrc_connect.c | 51 + src/connect/client/lcrc_connect.h | 145 ++ src/connect/client/rest/CMakeLists.txt | 7 + src/connect/client/rest/rest_client.c | 34 + src/connect/client/rest/rest_client.h | 30 + .../client/rest/rest_containers_client.c | 1885 ++++++++++++++ .../client/rest/rest_containers_client.h | 30 + src/connect/client/rest/rest_images_client.c | 476 ++++ src/connect/client/rest/rest_images_client.h | 30 + src/connect/service/CMakeLists.txt | 17 + src/connect/service/grpc/CMakeLists.txt | 7 + .../service/grpc/grpc_containers_service.cc | 1454 +++++++++++ .../service/grpc/grpc_containers_service.h | 225 ++ .../grpc/grpc_containers_service_private.cc | 1111 ++++++++ .../service/grpc/grpc_images_service.cc | 421 +++ .../service/grpc/grpc_images_service.h | 91 + .../service/grpc/grpc_server_tls_auth.cc | 60 + .../service/grpc/grpc_server_tls_auth.h | 33 + src/connect/service/grpc/grpc_service.cc | 233 ++ src/connect/service/grpc/grpc_service.h | 35 + .../service/grpc/runtime_image_service.cc | 113 + .../service/grpc/runtime_image_service.h | 48 + .../service/grpc/runtime_runtime_service.cc | 327 +++ .../service/grpc/runtime_runtime_service.h | 94 + src/connect/service/rest/CMakeLists.txt | 7 + .../service/rest/rest_containers_service.c | 1227 +++++++++ .../service/rest/rest_containers_service.h | 30 + .../service/rest/rest_images_service.c | 446 ++++ .../service/rest/rest_images_service.h | 30 + src/connect/service/rest/rest_service.c | 137 + src/connect/service/rest/rest_service.h | 32 + .../service/rest/rest_service_common.c | 86 + .../service/rest/rest_service_common.h | 34 + src/connect/service/service_common.c | 60 + src/connect/service/service_common.h | 37 + src/console/CMakeLists.txt | 7 + src/console/console.c | 685 +++++ src/console/console.h | 98 + src/constants.h | 62 + src/container_def.c | 32 + src/container_def.h | 139 + src/contrib/config/cni/default.json | 14 + src/contrib/config/config.json | 287 +++ src/contrib/config/daemon.json | 36 + src/contrib/config/hooks/default.json | 5 + src/contrib/config/iSulad.sysconfig | 22 + src/contrib/config/seccomp_default.json | 808 ++++++ src/contrib/docker | 572 +++++ src/contrib/env_checkconfig | 165 ++ src/contrib/generate_certificate.bash | 88 + src/contrib/init/lcrd.init | 133 + src/contrib/init/lcrd.service | 23 + src/contrib/sysmonitor/isulad-check.sh | 258 ++ src/contrib/sysmonitor/isulad-monit | 4 + src/cpputils/CMakeLists.txt | 7 + src/cpputils/cxxutils.cc | 41 + src/cpputils/cxxutils.h | 27 + src/cpputils/stoppable_thread.cc | 39 + src/cpputils/stoppable_thread.h | 52 + src/cpputils/url.cc | 929 +++++++ src/cpputils/url.h | 186 ++ src/cutils/CMakeLists.txt | 7 + src/cutils/util_atomic.c | 18 + src/cutils/util_atomic.h | 197 ++ src/cutils/utils.c | 1399 ++++++++++ src/cutils/utils.h | 414 +++ src/cutils/utils_array.c | 125 + src/cutils/utils_array.h | 40 + src/cutils/utils_convert.c | 183 ++ src/cutils/utils_convert.h | 36 + src/cutils/utils_file.c | 886 +++++++ src/cutils/utils_file.h | 78 + src/cutils/utils_string.c | 685 +++++ src/cutils/utils_string.h | 68 + src/cutils/utils_verify.c | 666 +++++ src/cutils/utils_verify.h | 104 + src/engines/CMakeLists.txt | 14 + src/engines/engine.c | 336 +++ src/engines/engine.h | 219 ++ src/engines/lcr/CMakeLists.txt | 7 + src/engines/lcr/lcr_engine.c | 503 ++++ src/engines/lcr/lcr_engine.h | 30 + src/error.c | 35 + src/error.h | 76 + src/filters.c | 326 +++ src/filters.h | 48 + src/http/CMakeLists.txt | 26 + src/http/buffer.c | 175 ++ src/http/buffer.h | 35 + src/http/certificate.c | 62 + src/http/certificate.h | 29 + src/http/http.c | 413 +++ src/http/http.h | 171 ++ src/http/mediatype.h | 77 + src/http/parser.c | 292 +++ src/http/parser.h | 63 + src/http/rest_common.c | 295 +++ src/http/rest_common.h | 42 + src/image/CMakeLists.txt | 43 + src/image/embedded/CMakeLists.txt | 17 + src/image/embedded/db/CMakeLists.txt | 7 + src/image/embedded/db/db_all.c | 838 ++++++ src/image/embedded/db/db_all.h | 73 + src/image/embedded/db/db_common.h | 32 + src/image/embedded/db/db_images_common.h | 35 + src/image/embedded/db/sqlite_common.c | 214 ++ src/image/embedded/db/sqlite_common.h | 35 + src/image/embedded/embedded_config_merge.c | 223 ++ src/image/embedded/embedded_config_merge.h | 32 + src/image/embedded/embedded_image.c | 365 +++ src/image/embedded/embedded_image.h | 52 + src/image/embedded/lim.c | 833 ++++++ src/image/embedded/lim.h | 59 + src/image/embedded/load.c | 207 ++ src/image/embedded/snapshot/CMakeLists.txt | 7 + src/image/embedded/snapshot/embedded.c | 71 + src/image/embedded/snapshot/embedded.h | 34 + src/image/embedded/snapshot/snapshot.c | 75 + src/image/embedded/snapshot/snapshot.h | 31 + src/image/embedded/snapshot/snapshot_def.h | 48 + src/image/external/CMakeLists.txt | 7 + src/image/external/ext_image.c | 223 ++ src/image/external/ext_image.h | 42 + src/image/image.c | 1173 +++++++++ src/image/image.h | 267 ++ src/image/oci/CMakeLists.txt | 12 + src/image/oci/isula_imtool_interface.c | 818 ++++++ src/image/oci/isula_imtool_interface.h | 58 + src/image/oci/oci_auth.c | 64 + src/image/oci/oci_auth.h | 30 + src/image/oci/oci_config_merge.c | 301 +++ src/image/oci/oci_config_merge.h | 34 + src/image/oci/oci_container_fs_usage.c | 76 + src/image/oci/oci_container_fs_usage.h | 34 + src/image/oci/oci_fs_info.c | 119 + src/image/oci/oci_fs_info.h | 39 + src/image/oci/oci_image.c | 731 ++++++ src/image/oci/oci_image.h | 53 + src/image/oci/oci_image_load.c | 170 ++ src/image/oci/oci_image_load.h | 31 + src/image/oci/oci_image_pull.c | 190 ++ src/image/oci/oci_image_pull.h | 47 + src/image/oci/oci_image_remove.c | 105 + src/image/oci/oci_image_status.c | 171 ++ src/image/oci/oci_image_status.h | 49 + src/image/oci/oci_image_type.h | 60 + src/image/oci/oci_image_unix.c | 107 + src/image/oci/oci_image_unix.h | 52 + src/image/oci/oci_images_list.c | 112 + src/image/oci/oci_images_list.h | 34 + src/image/oci/oci_images_store.c | 813 ++++++ src/image/oci/oci_images_store.h | 46 + src/image/oci/oci_login.c | 118 + src/image/oci/oci_login.h | 31 + src/image/oci/oci_logout.c | 99 + src/image/oci/oci_logout.h | 31 + src/image/oci/oci_rootfs_export.c | 146 ++ src/image/oci/oci_rootfs_export.h | 43 + src/image/oci/oci_rootfs_mount.c | 154 ++ src/image/oci/oci_rootfs_mount.h | 42 + src/image/oci/oci_rootfs_prepare.c | 230 ++ src/image/oci/oci_rootfs_prepare.h | 56 + src/image/oci/oci_rootfs_remove.c | 151 ++ src/image/oci/oci_rootfs_remove.h | 42 + src/image/oci/oci_rootfs_umount.c | 143 ++ src/image/oci/oci_rootfs_umount.h | 42 + src/json/CMakeLists.txt | 1 + src/json/oci_runtime_hooks.c | 63 + src/json/oci_runtime_hooks.h | 24 + src/json/parse_common.c | 33 + src/json/parse_common.h | 30 + src/json/schema/CMakeLists.txt | 15 + .../schema/container/attach-request.json | 27 + .../schema/container/attach-response.json | 12 + .../schema/schema/container/conf-request.json | 9 + .../schema/container/conf-response.json | 21 + .../schema/schema/container/config-v2.json | 161 ++ src/json/schema/schema/container/config.json | 95 + .../schema/schema/container/container.json | 57 + .../schema/container/copy-to-request.json | 24 + .../schema/container/create-request.json | 27 + .../schema/container/create-response.json | 18 + .../schema/container/custom-config.json | 83 + .../schema/container/delete-request.json | 12 + .../schema/container/delete-response.json | 18 + .../schema/schema/container/exec-request.json | 39 + .../schema/container/exec-response.json | 18 + .../schema/container/export-request.json | 12 + .../schema/container/export-response.json | 15 + .../schema/container/garbage-config.json | 32 + .../schema/container/get-id-request.json | 12 + .../schema/container/get-id-response.json | 15 + src/json/schema/schema/container/info.json | 57 + .../schema/container/inspect-request.json | 15 + .../schema/container/inspect-response.json | 15 + src/json/schema/schema/container/inspect.json | 139 + .../schema/schema/container/kill-request.json | 12 + .../schema/container/kill-response.json | 15 + .../schema/schema/container/list-request.json | 12 + .../schema/container/list-response.json | 18 + .../schema/schema/container/logs-request.json | 24 + .../schema/container/logs-response.json | 12 + .../schema/schema/container/path-stat.json | 21 + .../schema/container/pause-request.json | 9 + .../schema/container/pause-response.json | 15 + .../schema/container/restart-request.json | 12 + .../schema/container/restart-response.json | 15 + .../schema/container/resume-request.json | 9 + .../schema/container/resume-response.json | 15 + .../container/start-generate-config.json | 18 + .../schema/container/start-request.json | 27 + .../schema/container/start-response.json | 15 + .../schema/container/stats-request.json | 18 + .../schema/container/stats-response.json | 18 + .../schema/schema/container/stop-request.json | 15 + .../schema/container/stop-response.json | 15 + .../schema/schema/container/top-request.json | 12 + .../schema/schema/container/top-response.json | 18 + .../schema/container/update-request.json | 12 + .../schema/container/update-response.json | 15 + .../schema/container/version-request.json | 6 + .../schema/container/version-response.json | 24 + .../schema/schema/container/wait-request.json | 12 + .../schema/container/wait-response.json | 15 + src/json/schema/schema/cri/checkpoint.json | 21 + .../schema/schema/cri/checkpoint_data.json | 15 + src/json/schema/schema/cri/pod_network.json | 15 + src/json/schema/schema/cri/port_mapping.json | 15 + src/json/schema/schema/defs.json | 308 +++ .../schema/schema/docker/image/config-v2.json | 70 + .../schema/schema/docker/image/history.json | 23 + .../schema/schema/docker/image/rootfs.json | 16 + src/json/schema/schema/docker/seccomp.json | 116 + .../schema/docker/types/mount-point.json | 27 + src/json/schema/schema/embedded/config.json | 24 + src/json/schema/schema/embedded/layers.json | 24 + src/json/schema/schema/embedded/manifest.json | 30 + src/json/schema/schema/host-config.json | 283 +++ src/json/schema/schema/host/info-request.json | 6 + .../schema/schema/host/info-response.json | 72 + .../schema/image/delete-image-request.json | 12 + .../schema/image/delete-image-response.json | 15 + src/json/schema/schema/image/descriptor.json | 15 + src/json/schema/schema/image/image.json | 21 + .../schema/schema/image/inspect-request.json | 15 + .../schema/schema/image/inspect-response.json | 15 + .../schema/image/list-images-request.json | 9 + .../schema/image/list-images-response.json | 18 + .../schema/image/load-image-request.json | 15 + .../schema/image/load-image-response.json | 12 + .../schema/schema/image/login-request.json | 18 + .../schema/schema/image/login-response.json | 12 + .../schema/schema/image/logout-request.json | 12 + .../schema/schema/image/logout-response.json | 12 + .../schema/schema/image/manifest-items.json | 40 + .../image/manifest-v1-compatibility.json | 29 + src/json/schema/schema/image/manifest-v1.json | 65 + .../schema/schema/imagetool/auth_input.json | 15 + src/json/schema/schema/imagetool/fs-info.json | 41 + .../schema/schema/imagetool/image-status.json | 12 + src/json/schema/schema/imagetool/image.json | 47 + .../schema/schema/imagetool/images-list.json | 12 + .../schema/imagetool/prepare-response.json | 9 + .../schema/schema/isulad-daemon-configs.json | 143 ++ src/json/schema/schema/logger/json-file.json | 24 + .../schema/oci/image/content-descriptor.json | 33 + .../schema/oci/image/defs-descriptor.json | 27 + src/json/schema/schema/oci/image/defs.json | 308 +++ src/json/schema/schema/oci/image/index.json | 89 + src/json/schema/schema/oci/image/layout.json | 18 + .../schema/schema/oci/image/manifest.json | 34 + src/json/schema/schema/oci/image/spec.json | 140 + .../schema/oci/runtime/config-linux.json | 271 ++ .../schema/schema/oci/runtime/defs-linux.json | 270 ++ src/json/schema/schema/oci/runtime/defs.json | 308 +++ src/json/schema/schema/oci/runtime/pspec.json | 34 + src/json/schema/schema/oci/runtime/spec.json | 216 ++ src/json/schema/schema/oci/runtime/state.json | 45 + .../plugin/activate-plugin-request.json | 6 + .../plugin/activate-plugin-response.json | 17 + .../plugin/event-post-remove-request.json | 9 + .../plugin/event-post-remove-response.json | 15 + .../plugin/event-post-stop-request.json | 9 + .../plugin/event-post-stop-response.json | 15 + .../plugin/event-pre-create-request.json | 12 + .../plugin/event-pre-create-response.json | 18 + .../plugin/event-pre-start-request.json | 9 + .../plugin/event-pre-start-response.json | 15 + .../schema/plugin/init-plugin-request.json | 23 + .../schema/plugin/init-plugin-response.json | 12 + src/json/schema/schema/timestamp.json | 12 + src/json/schema/schema/web-signature.json | 44 + src/json/schema/src/CMakeLists.txt | 3 + src/json/schema/src/common_c.py | 1223 +++++++++ src/json/schema/src/common_h.py | 198 ++ src/json/schema/src/generate.py | 802 ++++++ src/json/schema/src/headers.py | 197 ++ src/json/schema/src/helpers.py | 313 +++ src/json/schema/src/read_file.c | 151 ++ src/json/schema/src/read_file.h | 25 + src/json/schema/src/sources.py | 1013 ++++++++ src/liblcrc.c | 1383 ++++++++++ src/liblcrc.h | 842 ++++++ src/liblcrd.c | 233 ++ src/liblcrd.h | 326 +++ src/linked_list.h | 122 + src/log.c | 459 ++++ src/log.h | 147 ++ src/mainloop.c | 158 ++ src/mainloop.h | 42 + src/map/CMakeLists.txt | 7 + src/map/map.c | 386 +++ src/map/map.h | 106 + src/map/rb_tree.c | 616 +++++ src/map/rb_tree.h | 82 + src/namespace.c | 98 + src/namespace.h | 72 + src/pack_config.c | 2154 ++++++++++++++++ src/pack_config.h | 33 + src/path.c | 673 +++++ src/path.h | 48 + src/plugin/CMakeLists.txt | 7 + src/plugin/plugin.c | 1914 ++++++++++++++ src/plugin/plugin.h | 116 + src/plugin/pspec.c | 256 ++ src/plugin/pspec.h | 42 + src/services/CMakeLists.txt | 31 + src/services/callback.c | 35 + src/services/callback.h | 177 ++ src/services/cri/CMakeLists.txt | 7 + src/services/cri/checkpoint_handler.cc | 366 +++ src/services/cri/checkpoint_handler.h | 91 + src/services/cri/cni_network_plugin.cc | 719 ++++++ src/services/cri/cni_network_plugin.h | 157 ++ src/services/cri/cri_container.cc | 1414 +++++++++++ src/services/cri/cri_container.h | 30 + src/services/cri/cri_helpers.cc | 712 ++++++ src/services/cri/cri_helpers.h | 112 + src/services/cri/cri_image_service.cc | 391 +++ src/services/cri/cri_image_service.h | 66 + src/services/cri/cri_runtime_service.cc | 184 ++ src/services/cri/cri_runtime_service.h | 265 ++ src/services/cri/cri_sandbox.cc | 1246 +++++++++ src/services/cri/cri_sandbox.h | 30 + src/services/cri/cri_security_context.cc | 213 ++ src/services/cri/cri_security_context.h | 33 + src/services/cri/cri_services.h | 105 + src/services/cri/errors.cc | 128 + src/services/cri/errors.h | 47 + src/services/cri/naming.cc | 124 + src/services/cri/naming.h | 32 + src/services/cri/network_plugin.cc | 521 ++++ src/services/cri/network_plugin.h | 222 ++ src/services/cri/request_cache.cc | 153 ++ src/services/cri/request_cache.h | 59 + src/services/execution/CMakeLists.txt | 23 + src/services/execution/events/CMakeLists.txt | 7 + src/services/execution/events/collector.c | 834 ++++++ src/services/execution/events/collector.h | 45 + .../execution/events/events_handler.c | 329 +++ .../execution/events/events_handler.h | 38 + src/services/execution/execute/CMakeLists.txt | 7 + src/services/execution/execute/execution.c | 1808 +++++++++++++ src/services/execution/execute/execution.h | 43 + .../execution/execute/execution_create.c | 902 +++++++ .../execution/execute/execution_create.h | 35 + .../execution/execute/execution_extend.c | 1358 ++++++++++ .../execution/execute/execution_extend.h | 32 + .../execution/execute/execution_information.c | 1755 +++++++++++++ .../execution/execute/execution_information.h | 31 + .../execution/execute/execution_network.c | 907 +++++++ .../execution/execute/execution_network.h | 37 + .../execution/execute/execution_stream.c | 1945 ++++++++++++++ .../execution/execute/execution_stream.h | 42 + src/services/execution/execute/list.c | 711 ++++++ src/services/execution/execute/list.h | 33 + .../execution/execute/runtime_interface.c | 248 ++ .../execution/execute/runtime_interface.h | 43 + src/services/execution/log_gather.c | 406 +++ src/services/execution/log_gather.h | 43 + src/services/execution/manager/CMakeLists.txt | 7 + .../execution/manager/container_state.c | 694 +++++ .../execution/manager/container_state.h | 99 + .../execution/manager/container_unix.c | 1334 ++++++++++ .../execution/manager/container_unix.h | 117 + .../execution/manager/containers_gc.c | 519 ++++ .../execution/manager/containers_gc.h | 48 + .../execution/manager/containers_store.c | 534 ++++ .../execution/manager/containers_store.h | 56 + src/services/execution/manager/health_check.c | 858 +++++++ src/services/execution/manager/health_check.h | 69 + src/services/execution/manager/monitord.c | 229 ++ src/services/execution/manager/monitord.h | 49 + .../execution/manager/restartmanager.c | 465 ++++ .../execution/manager/restartmanager.h | 63 + src/services/execution/manager/restore.c | 726 ++++++ src/services/execution/manager/restore.h | 34 + src/services/execution/manager/supervisor.c | 335 +++ src/services/execution/manager/supervisor.h | 33 + src/services/execution/spec/CMakeLists.txt | 7 + src/services/execution/spec/specs.c | 1816 +++++++++++++ src/services/execution/spec/specs.h | 33 + src/services/execution/spec/specs_extend.c | 1289 ++++++++++ src/services/execution/spec/specs_extend.h | 65 + src/services/execution/spec/specs_mount.c | 2097 +++++++++++++++ src/services/execution/spec/specs_mount.h | 51 + src/services/execution/spec/specs_security.c | 1061 ++++++++ src/services/execution/spec/specs_security.h | 35 + src/services/execution/spec/sysinfo.c | 1341 ++++++++++ src/services/execution/spec/sysinfo.h | 137 + src/services/execution/spec/verify.c | 1973 +++++++++++++++ src/services/execution/spec/verify.h | 32 + src/services/graphdriver/CMakeLists.txt | 14 + src/services/graphdriver/driver.c | 254 ++ src/services/graphdriver/driver.h | 64 + .../graphdriver/overlay2/CMakeLists.txt | 7 + .../graphdriver/overlay2/driver_overlay2.c | 81 + .../graphdriver/overlay2/driver_overlay2.h | 34 + src/services/image/CMakeLists.txt | 7 + src/services/image/image_cb.c | 727 ++++++ src/services/image/image_cb.h | 33 + src/sha256/CMakeLists.txt | 7 + src/sha256/sha256.c | 266 ++ src/sha256/sha256.h | 50 + src/sysctl_tools.c | 119 + src/sysctl_tools.h | 31 + src/tar/CMakeLists.txt | 7 + src/tar/lcrdtar.c | 916 +++++++ src/tar/lcrdtar.h | 77 + src/types_def.c | 903 +++++++ src/types_def.h | 69 + src/websocket/CMakeLists.txt | 4 + src/websocket/service/CMakeLists.txt | 5 + src/websocket/service/attach_serve.cc | 91 + src/websocket/service/attach_serve.h | 37 + src/websocket/service/exec_serve.cc | 118 + src/websocket/service/exec_serve.h | 41 + .../service/route_callback_register.h | 83 + src/websocket/service/stream_server.cc | 41 + src/websocket/service/stream_server.h | 34 + src/websocket/service/ws_server.cc | 484 ++++ src/websocket/service/ws_server.h | 126 + test/CMakeLists.txt | 8 + test/cutils/CMakeLists.txt | 3 + test/cutils/utils_string/CMakeLists.txt | 26 + test/cutils/utils_string/utils_string_llt.cc | 806 ++++++ test/include/mock.h | 125 + test/llt.sh | 264 ++ tools/static_check | 543 ++++ update-version.bash | 55 + 573 files changed, 116924 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 License/LICENSE create mode 100644 README.md create mode 100644 cmake/checker.cmake create mode 100644 cmake/helper.cmake create mode 100644 cmake/options.cmake create mode 100644 cmake/protoc.cmake create mode 100644 cmake/set_build_flags.cmake create mode 100644 config.h.in create mode 100644 docs/architecture.md create mode 100644 docs/design/CNI_architecture.png create mode 100644 docs/design/architecture.png create mode 100644 docs/developer_guide.md create mode 100644 docs/install_guide.md create mode 100644 docs/limitations.md create mode 100644 iSulad.spec create mode 100644 lcrd.pc.in create mode 100644 src/CMakeLists.txt create mode 100644 src/api/services/containers/container.proto create mode 100644 src/api/services/containers/rest/container.rest.h create mode 100644 src/api/services/cri/api.proto create mode 100644 src/api/services/health/health.proto create mode 100644 src/api/services/images/images.proto create mode 100644 src/api/services/images/rest/image.rest.h create mode 100644 src/api/types/descriptor.proto create mode 100644 src/cmd/CMakeLists.txt create mode 100644 src/cmd/commander.c create mode 100644 src/cmd/commander.h create mode 100644 src/cmd/lcrc/CMakeLists.txt create mode 100644 src/cmd/lcrc/arguments.c create mode 100644 src/cmd/lcrc/arguments.h create mode 100644 src/cmd/lcrc/base/CMakeLists.txt create mode 100644 src/cmd/lcrc/base/create.c create mode 100644 src/cmd/lcrc/base/create.h create mode 100644 src/cmd/lcrc/base/kill.c create mode 100644 src/cmd/lcrc/base/kill.h create mode 100644 src/cmd/lcrc/base/rename.c create mode 100644 src/cmd/lcrc/base/rename.h create mode 100644 src/cmd/lcrc/base/restart.c create mode 100644 src/cmd/lcrc/base/restart.h create mode 100644 src/cmd/lcrc/base/rm.c create mode 100644 src/cmd/lcrc/base/rm.h create mode 100644 src/cmd/lcrc/base/run.c create mode 100644 src/cmd/lcrc/base/run.h create mode 100644 src/cmd/lcrc/base/start.c create mode 100644 src/cmd/lcrc/base/start.h create mode 100644 src/cmd/lcrc/base/stop.c create mode 100644 src/cmd/lcrc/base/stop.h create mode 100644 src/cmd/lcrc/commands.c create mode 100644 src/cmd/lcrc/commands.h create mode 100644 src/cmd/lcrc/extend/CMakeLists.txt create mode 100644 src/cmd/lcrc/extend/events.c create mode 100644 src/cmd/lcrc/extend/events.h create mode 100644 src/cmd/lcrc/extend/export.c create mode 100644 src/cmd/lcrc/extend/export.h create mode 100644 src/cmd/lcrc/extend/pause.c create mode 100644 src/cmd/lcrc/extend/pause.h create mode 100644 src/cmd/lcrc/extend/resume.c create mode 100644 src/cmd/lcrc/extend/resume.h create mode 100644 src/cmd/lcrc/extend/stats.c create mode 100644 src/cmd/lcrc/extend/stats.h create mode 100644 src/cmd/lcrc/extend/update.c create mode 100644 src/cmd/lcrc/extend/update.h create mode 100644 src/cmd/lcrc/images/CMakeLists.txt create mode 100644 src/cmd/lcrc/images/images.c create mode 100644 src/cmd/lcrc/images/images.h create mode 100644 src/cmd/lcrc/images/load.c create mode 100644 src/cmd/lcrc/images/load.h create mode 100644 src/cmd/lcrc/images/login.c create mode 100644 src/cmd/lcrc/images/login.h create mode 100644 src/cmd/lcrc/images/logout.c create mode 100644 src/cmd/lcrc/images/logout.h create mode 100644 src/cmd/lcrc/images/pull.c create mode 100644 src/cmd/lcrc/images/pull.h create mode 100644 src/cmd/lcrc/images/rmi.c create mode 100644 src/cmd/lcrc/images/rmi.h create mode 100644 src/cmd/lcrc/information/CMakeLists.txt create mode 100644 src/cmd/lcrc/information/health.c create mode 100644 src/cmd/lcrc/information/health.h create mode 100644 src/cmd/lcrc/information/info.c create mode 100644 src/cmd/lcrc/information/info.h create mode 100644 src/cmd/lcrc/information/inspect.c create mode 100644 src/cmd/lcrc/information/inspect.h create mode 100644 src/cmd/lcrc/information/logs.c create mode 100644 src/cmd/lcrc/information/logs.h create mode 100644 src/cmd/lcrc/information/ps.c create mode 100644 src/cmd/lcrc/information/ps.h create mode 100644 src/cmd/lcrc/information/top.c create mode 100644 src/cmd/lcrc/information/top.h create mode 100644 src/cmd/lcrc/information/version.c create mode 100644 src/cmd/lcrc/information/version.h create mode 100644 src/cmd/lcrc/information/wait.c create mode 100644 src/cmd/lcrc/information/wait.h create mode 100644 src/cmd/lcrc/main.c create mode 100644 src/cmd/lcrc/stream/CMakeLists.txt create mode 100644 src/cmd/lcrc/stream/attach.c create mode 100644 src/cmd/lcrc/stream/attach.h create mode 100644 src/cmd/lcrc/stream/cp.c create mode 100644 src/cmd/lcrc/stream/cp.h create mode 100644 src/cmd/lcrc/stream/exec.c create mode 100644 src/cmd/lcrc/stream/exec.h create mode 100644 src/cmd/lcrd/CMakeLists.txt create mode 100644 src/cmd/lcrd/arguments.c create mode 100644 src/cmd/lcrd/arguments.h create mode 100644 src/cmd/lcrd/commands.c create mode 100644 src/cmd/lcrd/commands.h create mode 100644 src/cmd/lcrd/main.c create mode 100644 src/config/CMakeLists.txt create mode 100644 src/config/lcrd_config.c create mode 100644 src/config/lcrd_config.h create mode 100644 src/connect/CMakeLists.txt create mode 100644 src/connect/client/CMakeLists.txt create mode 100644 src/connect/client/connect.h create mode 100644 src/connect/client/grpc/CMakeLists.txt create mode 100644 src/connect/client/grpc/client_base.h create mode 100644 src/connect/client/grpc/grpc_client.cc create mode 100644 src/connect/client/grpc/grpc_client.h create mode 100644 src/connect/client/grpc/grpc_containers_client.cc create mode 100644 src/connect/client/grpc/grpc_containers_client.h create mode 100644 src/connect/client/grpc/grpc_images_client.cc create mode 100644 src/connect/client/grpc/grpc_images_client.h create mode 100644 src/connect/client/lcrc_connect.c create mode 100644 src/connect/client/lcrc_connect.h create mode 100644 src/connect/client/rest/CMakeLists.txt create mode 100644 src/connect/client/rest/rest_client.c create mode 100644 src/connect/client/rest/rest_client.h create mode 100644 src/connect/client/rest/rest_containers_client.c create mode 100644 src/connect/client/rest/rest_containers_client.h create mode 100644 src/connect/client/rest/rest_images_client.c create mode 100644 src/connect/client/rest/rest_images_client.h create mode 100644 src/connect/service/CMakeLists.txt create mode 100644 src/connect/service/grpc/CMakeLists.txt create mode 100644 src/connect/service/grpc/grpc_containers_service.cc create mode 100644 src/connect/service/grpc/grpc_containers_service.h create mode 100644 src/connect/service/grpc/grpc_containers_service_private.cc create mode 100644 src/connect/service/grpc/grpc_images_service.cc create mode 100644 src/connect/service/grpc/grpc_images_service.h create mode 100644 src/connect/service/grpc/grpc_server_tls_auth.cc create mode 100644 src/connect/service/grpc/grpc_server_tls_auth.h create mode 100644 src/connect/service/grpc/grpc_service.cc create mode 100644 src/connect/service/grpc/grpc_service.h create mode 100644 src/connect/service/grpc/runtime_image_service.cc create mode 100644 src/connect/service/grpc/runtime_image_service.h create mode 100644 src/connect/service/grpc/runtime_runtime_service.cc create mode 100644 src/connect/service/grpc/runtime_runtime_service.h create mode 100644 src/connect/service/rest/CMakeLists.txt create mode 100644 src/connect/service/rest/rest_containers_service.c create mode 100644 src/connect/service/rest/rest_containers_service.h create mode 100644 src/connect/service/rest/rest_images_service.c create mode 100644 src/connect/service/rest/rest_images_service.h create mode 100644 src/connect/service/rest/rest_service.c create mode 100644 src/connect/service/rest/rest_service.h create mode 100644 src/connect/service/rest/rest_service_common.c create mode 100644 src/connect/service/rest/rest_service_common.h create mode 100644 src/connect/service/service_common.c create mode 100644 src/connect/service/service_common.h create mode 100644 src/console/CMakeLists.txt create mode 100644 src/console/console.c create mode 100644 src/console/console.h create mode 100644 src/constants.h create mode 100644 src/container_def.c create mode 100644 src/container_def.h create mode 100644 src/contrib/config/cni/default.json create mode 100644 src/contrib/config/config.json create mode 100644 src/contrib/config/daemon.json create mode 100644 src/contrib/config/hooks/default.json create mode 100644 src/contrib/config/iSulad.sysconfig create mode 100644 src/contrib/config/seccomp_default.json create mode 100755 src/contrib/docker create mode 100755 src/contrib/env_checkconfig create mode 100755 src/contrib/generate_certificate.bash create mode 100644 src/contrib/init/lcrd.init create mode 100644 src/contrib/init/lcrd.service create mode 100755 src/contrib/sysmonitor/isulad-check.sh create mode 100644 src/contrib/sysmonitor/isulad-monit create mode 100644 src/cpputils/CMakeLists.txt create mode 100644 src/cpputils/cxxutils.cc create mode 100644 src/cpputils/cxxutils.h create mode 100644 src/cpputils/stoppable_thread.cc create mode 100644 src/cpputils/stoppable_thread.h create mode 100644 src/cpputils/url.cc create mode 100644 src/cpputils/url.h create mode 100644 src/cutils/CMakeLists.txt create mode 100644 src/cutils/util_atomic.c create mode 100644 src/cutils/util_atomic.h create mode 100644 src/cutils/utils.c create mode 100644 src/cutils/utils.h create mode 100644 src/cutils/utils_array.c create mode 100644 src/cutils/utils_array.h create mode 100644 src/cutils/utils_convert.c create mode 100644 src/cutils/utils_convert.h create mode 100644 src/cutils/utils_file.c create mode 100644 src/cutils/utils_file.h create mode 100644 src/cutils/utils_string.c create mode 100644 src/cutils/utils_string.h create mode 100644 src/cutils/utils_verify.c create mode 100644 src/cutils/utils_verify.h create mode 100644 src/engines/CMakeLists.txt create mode 100644 src/engines/engine.c create mode 100644 src/engines/engine.h create mode 100644 src/engines/lcr/CMakeLists.txt create mode 100644 src/engines/lcr/lcr_engine.c create mode 100644 src/engines/lcr/lcr_engine.h create mode 100644 src/error.c create mode 100644 src/error.h create mode 100644 src/filters.c create mode 100644 src/filters.h create mode 100644 src/http/CMakeLists.txt create mode 100644 src/http/buffer.c create mode 100644 src/http/buffer.h create mode 100644 src/http/certificate.c create mode 100644 src/http/certificate.h create mode 100644 src/http/http.c create mode 100644 src/http/http.h create mode 100644 src/http/mediatype.h create mode 100644 src/http/parser.c create mode 100644 src/http/parser.h create mode 100644 src/http/rest_common.c create mode 100644 src/http/rest_common.h create mode 100644 src/image/CMakeLists.txt create mode 100644 src/image/embedded/CMakeLists.txt create mode 100644 src/image/embedded/db/CMakeLists.txt create mode 100644 src/image/embedded/db/db_all.c create mode 100644 src/image/embedded/db/db_all.h create mode 100644 src/image/embedded/db/db_common.h create mode 100644 src/image/embedded/db/db_images_common.h create mode 100644 src/image/embedded/db/sqlite_common.c create mode 100644 src/image/embedded/db/sqlite_common.h create mode 100644 src/image/embedded/embedded_config_merge.c create mode 100644 src/image/embedded/embedded_config_merge.h create mode 100644 src/image/embedded/embedded_image.c create mode 100644 src/image/embedded/embedded_image.h create mode 100644 src/image/embedded/lim.c create mode 100644 src/image/embedded/lim.h create mode 100644 src/image/embedded/load.c create mode 100644 src/image/embedded/snapshot/CMakeLists.txt create mode 100644 src/image/embedded/snapshot/embedded.c create mode 100644 src/image/embedded/snapshot/embedded.h create mode 100644 src/image/embedded/snapshot/snapshot.c create mode 100644 src/image/embedded/snapshot/snapshot.h create mode 100644 src/image/embedded/snapshot/snapshot_def.h create mode 100644 src/image/external/CMakeLists.txt create mode 100644 src/image/external/ext_image.c create mode 100644 src/image/external/ext_image.h create mode 100644 src/image/image.c create mode 100644 src/image/image.h create mode 100644 src/image/oci/CMakeLists.txt create mode 100644 src/image/oci/isula_imtool_interface.c create mode 100644 src/image/oci/isula_imtool_interface.h create mode 100644 src/image/oci/oci_auth.c create mode 100644 src/image/oci/oci_auth.h create mode 100644 src/image/oci/oci_config_merge.c create mode 100644 src/image/oci/oci_config_merge.h create mode 100644 src/image/oci/oci_container_fs_usage.c create mode 100644 src/image/oci/oci_container_fs_usage.h create mode 100644 src/image/oci/oci_fs_info.c create mode 100644 src/image/oci/oci_fs_info.h create mode 100644 src/image/oci/oci_image.c create mode 100644 src/image/oci/oci_image.h create mode 100644 src/image/oci/oci_image_load.c create mode 100644 src/image/oci/oci_image_load.h create mode 100644 src/image/oci/oci_image_pull.c create mode 100644 src/image/oci/oci_image_pull.h create mode 100644 src/image/oci/oci_image_remove.c create mode 100644 src/image/oci/oci_image_status.c create mode 100644 src/image/oci/oci_image_status.h create mode 100644 src/image/oci/oci_image_type.h create mode 100644 src/image/oci/oci_image_unix.c create mode 100644 src/image/oci/oci_image_unix.h create mode 100644 src/image/oci/oci_images_list.c create mode 100644 src/image/oci/oci_images_list.h create mode 100644 src/image/oci/oci_images_store.c create mode 100644 src/image/oci/oci_images_store.h create mode 100644 src/image/oci/oci_login.c create mode 100644 src/image/oci/oci_login.h create mode 100644 src/image/oci/oci_logout.c create mode 100644 src/image/oci/oci_logout.h create mode 100644 src/image/oci/oci_rootfs_export.c create mode 100644 src/image/oci/oci_rootfs_export.h create mode 100644 src/image/oci/oci_rootfs_mount.c create mode 100644 src/image/oci/oci_rootfs_mount.h create mode 100644 src/image/oci/oci_rootfs_prepare.c create mode 100644 src/image/oci/oci_rootfs_prepare.h create mode 100644 src/image/oci/oci_rootfs_remove.c create mode 100644 src/image/oci/oci_rootfs_remove.h create mode 100644 src/image/oci/oci_rootfs_umount.c create mode 100644 src/image/oci/oci_rootfs_umount.h create mode 100644 src/json/CMakeLists.txt create mode 100644 src/json/oci_runtime_hooks.c create mode 100644 src/json/oci_runtime_hooks.h create mode 100644 src/json/parse_common.c create mode 100644 src/json/parse_common.h create mode 100644 src/json/schema/CMakeLists.txt create mode 100644 src/json/schema/schema/container/attach-request.json create mode 100644 src/json/schema/schema/container/attach-response.json create mode 100644 src/json/schema/schema/container/conf-request.json create mode 100644 src/json/schema/schema/container/conf-response.json create mode 100644 src/json/schema/schema/container/config-v2.json create mode 100644 src/json/schema/schema/container/config.json create mode 100644 src/json/schema/schema/container/container.json create mode 100644 src/json/schema/schema/container/copy-to-request.json create mode 100644 src/json/schema/schema/container/create-request.json create mode 100644 src/json/schema/schema/container/create-response.json create mode 100644 src/json/schema/schema/container/custom-config.json create mode 100644 src/json/schema/schema/container/delete-request.json create mode 100644 src/json/schema/schema/container/delete-response.json create mode 100644 src/json/schema/schema/container/exec-request.json create mode 100644 src/json/schema/schema/container/exec-response.json create mode 100644 src/json/schema/schema/container/export-request.json create mode 100644 src/json/schema/schema/container/export-response.json create mode 100644 src/json/schema/schema/container/garbage-config.json create mode 100644 src/json/schema/schema/container/get-id-request.json create mode 100644 src/json/schema/schema/container/get-id-response.json create mode 100644 src/json/schema/schema/container/info.json create mode 100644 src/json/schema/schema/container/inspect-request.json create mode 100644 src/json/schema/schema/container/inspect-response.json create mode 100644 src/json/schema/schema/container/inspect.json create mode 100644 src/json/schema/schema/container/kill-request.json create mode 100644 src/json/schema/schema/container/kill-response.json create mode 100644 src/json/schema/schema/container/list-request.json create mode 100644 src/json/schema/schema/container/list-response.json create mode 100644 src/json/schema/schema/container/logs-request.json create mode 100644 src/json/schema/schema/container/logs-response.json create mode 100644 src/json/schema/schema/container/path-stat.json create mode 100644 src/json/schema/schema/container/pause-request.json create mode 100644 src/json/schema/schema/container/pause-response.json create mode 100644 src/json/schema/schema/container/restart-request.json create mode 100644 src/json/schema/schema/container/restart-response.json create mode 100644 src/json/schema/schema/container/resume-request.json create mode 100644 src/json/schema/schema/container/resume-response.json create mode 100644 src/json/schema/schema/container/start-generate-config.json create mode 100644 src/json/schema/schema/container/start-request.json create mode 100644 src/json/schema/schema/container/start-response.json create mode 100644 src/json/schema/schema/container/stats-request.json create mode 100644 src/json/schema/schema/container/stats-response.json create mode 100644 src/json/schema/schema/container/stop-request.json create mode 100644 src/json/schema/schema/container/stop-response.json create mode 100644 src/json/schema/schema/container/top-request.json create mode 100644 src/json/schema/schema/container/top-response.json create mode 100644 src/json/schema/schema/container/update-request.json create mode 100644 src/json/schema/schema/container/update-response.json create mode 100644 src/json/schema/schema/container/version-request.json create mode 100644 src/json/schema/schema/container/version-response.json create mode 100644 src/json/schema/schema/container/wait-request.json create mode 100644 src/json/schema/schema/container/wait-response.json create mode 100644 src/json/schema/schema/cri/checkpoint.json create mode 100644 src/json/schema/schema/cri/checkpoint_data.json create mode 100644 src/json/schema/schema/cri/pod_network.json create mode 100644 src/json/schema/schema/cri/port_mapping.json create mode 100644 src/json/schema/schema/defs.json create mode 100644 src/json/schema/schema/docker/image/config-v2.json create mode 100644 src/json/schema/schema/docker/image/history.json create mode 100644 src/json/schema/schema/docker/image/rootfs.json create mode 100644 src/json/schema/schema/docker/seccomp.json create mode 100644 src/json/schema/schema/docker/types/mount-point.json create mode 100644 src/json/schema/schema/embedded/config.json create mode 100644 src/json/schema/schema/embedded/layers.json create mode 100644 src/json/schema/schema/embedded/manifest.json create mode 100644 src/json/schema/schema/host-config.json create mode 100644 src/json/schema/schema/host/info-request.json create mode 100644 src/json/schema/schema/host/info-response.json create mode 100644 src/json/schema/schema/image/delete-image-request.json create mode 100644 src/json/schema/schema/image/delete-image-response.json create mode 100644 src/json/schema/schema/image/descriptor.json create mode 100644 src/json/schema/schema/image/image.json create mode 100644 src/json/schema/schema/image/inspect-request.json create mode 100644 src/json/schema/schema/image/inspect-response.json create mode 100644 src/json/schema/schema/image/list-images-request.json create mode 100644 src/json/schema/schema/image/list-images-response.json create mode 100644 src/json/schema/schema/image/load-image-request.json create mode 100644 src/json/schema/schema/image/load-image-response.json create mode 100644 src/json/schema/schema/image/login-request.json create mode 100644 src/json/schema/schema/image/login-response.json create mode 100644 src/json/schema/schema/image/logout-request.json create mode 100644 src/json/schema/schema/image/logout-response.json create mode 100644 src/json/schema/schema/image/manifest-items.json create mode 100644 src/json/schema/schema/image/manifest-v1-compatibility.json create mode 100644 src/json/schema/schema/image/manifest-v1.json create mode 100644 src/json/schema/schema/imagetool/auth_input.json create mode 100644 src/json/schema/schema/imagetool/fs-info.json create mode 100644 src/json/schema/schema/imagetool/image-status.json create mode 100644 src/json/schema/schema/imagetool/image.json create mode 100644 src/json/schema/schema/imagetool/images-list.json create mode 100644 src/json/schema/schema/imagetool/prepare-response.json create mode 100644 src/json/schema/schema/isulad-daemon-configs.json create mode 100644 src/json/schema/schema/logger/json-file.json create mode 100644 src/json/schema/schema/oci/image/content-descriptor.json create mode 100644 src/json/schema/schema/oci/image/defs-descriptor.json create mode 100644 src/json/schema/schema/oci/image/defs.json create mode 100644 src/json/schema/schema/oci/image/index.json create mode 100644 src/json/schema/schema/oci/image/layout.json create mode 100644 src/json/schema/schema/oci/image/manifest.json create mode 100644 src/json/schema/schema/oci/image/spec.json create mode 100644 src/json/schema/schema/oci/runtime/config-linux.json create mode 100644 src/json/schema/schema/oci/runtime/defs-linux.json create mode 100644 src/json/schema/schema/oci/runtime/defs.json create mode 100644 src/json/schema/schema/oci/runtime/pspec.json create mode 100644 src/json/schema/schema/oci/runtime/spec.json create mode 100644 src/json/schema/schema/oci/runtime/state.json create mode 100644 src/json/schema/schema/plugin/activate-plugin-request.json create mode 100644 src/json/schema/schema/plugin/activate-plugin-response.json create mode 100644 src/json/schema/schema/plugin/event-post-remove-request.json create mode 100644 src/json/schema/schema/plugin/event-post-remove-response.json create mode 100644 src/json/schema/schema/plugin/event-post-stop-request.json create mode 100644 src/json/schema/schema/plugin/event-post-stop-response.json create mode 100644 src/json/schema/schema/plugin/event-pre-create-request.json create mode 100644 src/json/schema/schema/plugin/event-pre-create-response.json create mode 100644 src/json/schema/schema/plugin/event-pre-start-request.json create mode 100644 src/json/schema/schema/plugin/event-pre-start-response.json create mode 100644 src/json/schema/schema/plugin/init-plugin-request.json create mode 100644 src/json/schema/schema/plugin/init-plugin-response.json create mode 100644 src/json/schema/schema/timestamp.json create mode 100644 src/json/schema/schema/web-signature.json create mode 100644 src/json/schema/src/CMakeLists.txt create mode 100644 src/json/schema/src/common_c.py create mode 100644 src/json/schema/src/common_h.py create mode 100644 src/json/schema/src/generate.py create mode 100644 src/json/schema/src/headers.py create mode 100644 src/json/schema/src/helpers.py create mode 100644 src/json/schema/src/read_file.c create mode 100644 src/json/schema/src/read_file.h create mode 100644 src/json/schema/src/sources.py create mode 100644 src/liblcrc.c create mode 100644 src/liblcrc.h create mode 100644 src/liblcrd.c create mode 100644 src/liblcrd.h create mode 100644 src/linked_list.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/mainloop.c create mode 100644 src/mainloop.h create mode 100644 src/map/CMakeLists.txt create mode 100644 src/map/map.c create mode 100644 src/map/map.h create mode 100644 src/map/rb_tree.c create mode 100644 src/map/rb_tree.h create mode 100644 src/namespace.c create mode 100644 src/namespace.h create mode 100644 src/pack_config.c create mode 100644 src/pack_config.h create mode 100644 src/path.c create mode 100644 src/path.h create mode 100644 src/plugin/CMakeLists.txt create mode 100644 src/plugin/plugin.c create mode 100644 src/plugin/plugin.h create mode 100644 src/plugin/pspec.c create mode 100644 src/plugin/pspec.h create mode 100644 src/services/CMakeLists.txt create mode 100644 src/services/callback.c create mode 100644 src/services/callback.h create mode 100644 src/services/cri/CMakeLists.txt create mode 100644 src/services/cri/checkpoint_handler.cc create mode 100644 src/services/cri/checkpoint_handler.h create mode 100644 src/services/cri/cni_network_plugin.cc create mode 100644 src/services/cri/cni_network_plugin.h create mode 100644 src/services/cri/cri_container.cc create mode 100644 src/services/cri/cri_container.h create mode 100644 src/services/cri/cri_helpers.cc create mode 100644 src/services/cri/cri_helpers.h create mode 100644 src/services/cri/cri_image_service.cc create mode 100644 src/services/cri/cri_image_service.h create mode 100644 src/services/cri/cri_runtime_service.cc create mode 100644 src/services/cri/cri_runtime_service.h create mode 100644 src/services/cri/cri_sandbox.cc create mode 100644 src/services/cri/cri_sandbox.h create mode 100644 src/services/cri/cri_security_context.cc create mode 100644 src/services/cri/cri_security_context.h create mode 100644 src/services/cri/cri_services.h create mode 100644 src/services/cri/errors.cc create mode 100644 src/services/cri/errors.h create mode 100644 src/services/cri/naming.cc create mode 100644 src/services/cri/naming.h create mode 100644 src/services/cri/network_plugin.cc create mode 100644 src/services/cri/network_plugin.h create mode 100644 src/services/cri/request_cache.cc create mode 100644 src/services/cri/request_cache.h create mode 100644 src/services/execution/CMakeLists.txt create mode 100644 src/services/execution/events/CMakeLists.txt create mode 100644 src/services/execution/events/collector.c create mode 100644 src/services/execution/events/collector.h create mode 100644 src/services/execution/events/events_handler.c create mode 100644 src/services/execution/events/events_handler.h create mode 100644 src/services/execution/execute/CMakeLists.txt create mode 100644 src/services/execution/execute/execution.c create mode 100644 src/services/execution/execute/execution.h create mode 100644 src/services/execution/execute/execution_create.c create mode 100644 src/services/execution/execute/execution_create.h create mode 100644 src/services/execution/execute/execution_extend.c create mode 100644 src/services/execution/execute/execution_extend.h create mode 100644 src/services/execution/execute/execution_information.c create mode 100644 src/services/execution/execute/execution_information.h create mode 100644 src/services/execution/execute/execution_network.c create mode 100644 src/services/execution/execute/execution_network.h create mode 100644 src/services/execution/execute/execution_stream.c create mode 100644 src/services/execution/execute/execution_stream.h create mode 100644 src/services/execution/execute/list.c create mode 100644 src/services/execution/execute/list.h create mode 100644 src/services/execution/execute/runtime_interface.c create mode 100644 src/services/execution/execute/runtime_interface.h create mode 100644 src/services/execution/log_gather.c create mode 100644 src/services/execution/log_gather.h create mode 100644 src/services/execution/manager/CMakeLists.txt create mode 100644 src/services/execution/manager/container_state.c create mode 100644 src/services/execution/manager/container_state.h create mode 100644 src/services/execution/manager/container_unix.c create mode 100644 src/services/execution/manager/container_unix.h create mode 100644 src/services/execution/manager/containers_gc.c create mode 100644 src/services/execution/manager/containers_gc.h create mode 100644 src/services/execution/manager/containers_store.c create mode 100644 src/services/execution/manager/containers_store.h create mode 100644 src/services/execution/manager/health_check.c create mode 100644 src/services/execution/manager/health_check.h create mode 100644 src/services/execution/manager/monitord.c create mode 100644 src/services/execution/manager/monitord.h create mode 100644 src/services/execution/manager/restartmanager.c create mode 100644 src/services/execution/manager/restartmanager.h create mode 100644 src/services/execution/manager/restore.c create mode 100644 src/services/execution/manager/restore.h create mode 100644 src/services/execution/manager/supervisor.c create mode 100644 src/services/execution/manager/supervisor.h create mode 100644 src/services/execution/spec/CMakeLists.txt create mode 100644 src/services/execution/spec/specs.c create mode 100644 src/services/execution/spec/specs.h create mode 100644 src/services/execution/spec/specs_extend.c create mode 100644 src/services/execution/spec/specs_extend.h create mode 100644 src/services/execution/spec/specs_mount.c create mode 100644 src/services/execution/spec/specs_mount.h create mode 100644 src/services/execution/spec/specs_security.c create mode 100644 src/services/execution/spec/specs_security.h create mode 100644 src/services/execution/spec/sysinfo.c create mode 100644 src/services/execution/spec/sysinfo.h create mode 100644 src/services/execution/spec/verify.c create mode 100644 src/services/execution/spec/verify.h create mode 100644 src/services/graphdriver/CMakeLists.txt create mode 100644 src/services/graphdriver/driver.c create mode 100644 src/services/graphdriver/driver.h create mode 100644 src/services/graphdriver/overlay2/CMakeLists.txt create mode 100644 src/services/graphdriver/overlay2/driver_overlay2.c create mode 100644 src/services/graphdriver/overlay2/driver_overlay2.h create mode 100644 src/services/image/CMakeLists.txt create mode 100644 src/services/image/image_cb.c create mode 100644 src/services/image/image_cb.h create mode 100644 src/sha256/CMakeLists.txt create mode 100644 src/sha256/sha256.c create mode 100644 src/sha256/sha256.h create mode 100644 src/sysctl_tools.c create mode 100644 src/sysctl_tools.h create mode 100644 src/tar/CMakeLists.txt create mode 100644 src/tar/lcrdtar.c create mode 100644 src/tar/lcrdtar.h create mode 100644 src/types_def.c create mode 100644 src/types_def.h create mode 100644 src/websocket/CMakeLists.txt create mode 100644 src/websocket/service/CMakeLists.txt create mode 100644 src/websocket/service/attach_serve.cc create mode 100644 src/websocket/service/attach_serve.h create mode 100644 src/websocket/service/exec_serve.cc create mode 100644 src/websocket/service/exec_serve.h create mode 100644 src/websocket/service/route_callback_register.h create mode 100644 src/websocket/service/stream_server.cc create mode 100644 src/websocket/service/stream_server.h create mode 100644 src/websocket/service/ws_server.cc create mode 100644 src/websocket/service/ws_server.h create mode 100644 test/CMakeLists.txt create mode 100644 test/cutils/CMakeLists.txt create mode 100644 test/cutils/utils_string/CMakeLists.txt create mode 100644 test/cutils/utils_string/utils_string_llt.cc create mode 100644 test/include/mock.h create mode 100755 test/llt.sh create mode 100755 tools/static_check create mode 100755 update-version.bash diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5d9d455 --- /dev/null +++ b/.clang-format @@ -0,0 +1,123 @@ +--- +BasedOnStyle: llvm +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +# AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +# AllowAllConstructorInitializersOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +# AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + # AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true + +# Taken from: +# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \ +# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ +# | sort | uniq +ForEachMacros: + - 'linked_list_for_each_safe' + - 'linked_list_for_each' + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: Inner +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +# SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f97ca1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +*.pyc diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..25b9689 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,123 @@ +cmake_minimum_required (VERSION 3.12.1) +project (lcrd) + +include(cmake/helper.cmake) + +include(cmake/options.cmake) + +include(cmake/set_build_flags.cmake) + +#set(CMAKE_C_COMPILER "gcc" CACHE PATH "c compiler") + +# Get the latest abbreviated commit hash of the working branch +execute_process( + COMMAND git rev-parse HEAD + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE +) +message("-- commit id: " ${GIT_COMMIT_HASH}) +add_definitions(-DLCRD_GIT_COMMIT="${GIT_COMMIT_HASH}") + +execute_process( + COMMAND sh -c "date --rfc-3339 ns | sed -e 's/ /T/'" + OUTPUT_VARIABLE BUILD_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE +) +message("-- build time: " ${BUILD_DATE}) +add_definitions(-DLCRD_BUILD_TIME="${BUILD_DATE}") + +if (NOT LCRD_ROOT_PATH) +add_definitions(-DLCRD_ROOT_PATH="/var/lib/lcrd") +endif() +if (NOT LCRD_STATE_PATH) +add_definitions(-DLCRD_STATE_PATH="/var/run/lcrd") +endif() + +if (LIB_INSTALL_DIR) + set(LIB_INSTALL_DIR_DEFAULT ${LIB_INSTALL_DIR}) +else() + set(LIB_INSTALL_DIR_DEFAULT "lib") +endif() + +# check depends library and headers +include(cmake/checker.cmake) +if (CHECKER_RESULT) + message(FATAL_ERROR "ERROR: Check library and headers failed") +endif() + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" + "${CMAKE_BINARY_DIR}/conf/config.h" +) + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/lcrd.pc.in" + "${CMAKE_BINARY_DIR}/conf/lcrd.pc" +) + +if (GRPC_CONNECTOR) + # parse .proto files + include(cmake/protoc.cmake) +endif() + +# enable embedded image +if (ENABLE_EMBEDDED) + add_definitions(-DENABLE_EMBEDDED_IMAGE=1) +endif() + +# disable oci image +if (NOT DISABLE_OCI) + add_definitions(-DENABLE_OCI_IMAGE=1) +endif() + +# llt and coverage +SET(CMAKE_VERBOSE_MAKEFILE OFF) +OPTION(ENABLE_COVERAGE "coverage switch" OFF) +IF(ENABLE_COVERAGE) + MESSAGE(STATUS "Enable coverage compile option") + SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage") + SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage") + SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage -lgcov") +ENDIF(ENABLE_COVERAGE) + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src) +OPTION(ENABLE_LLT "llt switch" OFF) +IF(ENABLE_LLT) + enable_testing() + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test) +ENDIF(ENABLE_LLT) + +# install all files +install(FILES ${CMAKE_BINARY_DIR}/conf/lcrd.pc + DESTINATION ${LIB_INSTALL_DIR_DEFAULT}/pkgconfig PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE) +install(FILES src/liblcrc.h + DESTINATION include/lcrd) +install(FILES src/connect/client/lcrc_connect.h + DESTINATION include/lcrd) +install(FILES src/container_def.h + DESTINATION include/lcrd) +install(FILES src/types_def.h + DESTINATION include/lcrd) +install(FILES src/error.h + DESTINATION include/lcrd) +install(FILES src/engines/engine.h + DESTINATION include/lcrd) + +# install config files +set(conf_prefix "/etc") +if (CMAKE_INSTALL_SYSCONFDIR) + set(conf_prefix ${CMAKE_INSTALL_SYSCONFDIR}) +endif() +install(FILES src/contrib/config/daemon.json + DESTINATION ${conf_prefix}/isulad PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE) +install(FILES src/contrib/config/config.json + DESTINATION ${conf_prefix}/default/lcrd PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE) +install(FILES src/contrib/config/seccomp_default.json + DESTINATION ${conf_prefix}/isulad PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE) +install(FILES src/contrib/config/hooks/default.json + DESTINATION ${conf_prefix}/default/lcrd/hooks PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE) +install(FILES src/contrib/sysmonitor/isulad-check.sh + DESTINATION ${conf_prefix}/default/lcrd PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE) +install(FILES src/contrib/sysmonitor/isulad-monit + DESTINATION ${conf_prefix}/sysmonitor/process PERMISSIONS OWNER_READ OWNER_WRITE) diff --git a/License/LICENSE b/License/LICENSE new file mode 100644 index 0000000..cefc2d2 --- /dev/null +++ b/License/LICENSE @@ -0,0 +1,121 @@ + 木兰宽松许可证, 第1版 + + 木兰宽松许可证, 第1版 + 2019年8月 http://license.coscl.org.cn/MulanPSL + + 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第1版(“本许可证”)的如下条款的约束: + + 0. 定义 + + “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 + + “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 + + “法人实体”是指提交贡献的机构及其“关联实体”。 + + “关联实体”是指,对“本许可证”下的一方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 + + “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 + + 1. 授予版权许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 + + 2. 授予专利许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括仅因您或他人修改“贡献”或其他结合而将必然会侵犯到的专利权利要求。如您或您的“关联实体”直接或间接地(包括通过代理、专利被许可人或受让人),就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 + + 3. 无商标许可 + + “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 + + 4. 分发限制 + + 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 + + 5. 免责声明与责任限制 + + “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + + 条款结束。 + + 如何将木兰宽松许可证,第1版,应用到您的软件 + + 如果您希望将木兰宽松许可证,第1版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: + + 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; + + 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; + + 3, 请将如下声明文本放入每个源文件的头部注释中。 + + Copyright (c) [2019] [name of copyright holder] + [Software Name] is licensed under the Mulan PSL v1. + You can use this software according to the terms and conditions of the Mulan PSL v1. + You may obtain a copy of Mulan PSL v1 at: + http://license.coscl.org.cn/MulanPSL + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + PURPOSE. + See the Mulan PSL v1 for more details. + + + Mulan Permissive Software License,Version 1 + + Mulan Permissive Software License,Version 1 (Mulan PSL v1) + August 2019 http://license.coscl.org.cn/MulanPSL + + Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v1 (this License) with following terms and conditions: + + 0. Definition + + Software means the program and related documents which are comprised of those Contribution and licensed under this License. + + Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. + + Legal Entity means the entity making a Contribution and all its Affiliates. + + Affiliates means entities that control, or are controlled by, or are under common control with a party to this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. + + Contribution means the copyrightable work licensed by a particular Contributor under this License. + + 1. Grant of Copyright License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. + + 2. Grant of Patent License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed, excluding of any patent claims solely be infringed by your or others’ modification or other combinations. If you or your Affiliates directly or indirectly (including through an agent, patent licensee or assignee), institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. + + 3. No Trademark License + + No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4. + + 4. Distribution Restriction + + You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. + + 5. Disclaimer of Warranty and Limitation of Liability + + The Software and Contribution in it are provided without warranties of any kind, either express or implied. In no event shall any Contributor or copyright holder be liable to you for any damages, including, but not limited to any direct, or indirect, special or consequential damages arising from your use or inability to use the Software or the Contribution in it, no matter how it’s caused or based on which legal theory, even if advised of the possibility of such damages. + + End of the Terms and Conditions + + How to apply the Mulan Permissive Software License,Version 1 (Mulan PSL v1) to your software + + To apply the Mulan PSL v1 to your work, for easy identification by recipients, you are suggested to complete following three steps: + + i. Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; + ii. Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; + iii. Attach the statement to the appropriate annotated syntax at the beginning of each source file. + + Copyright (c) [2019] [name of copyright holder] + [Software Name] is licensed under the Mulan PSL v1. + You can use this software according to the terms and conditions of the Mulan PSL v1. + You may obtain a copy of Mulan PSL v1 at: + http://license.coscl.org.cn/MulanPSL + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + PURPOSE. + + See the Mulan PSL v1 for more details. diff --git a/README.md b/README.md new file mode 100644 index 0000000..976a5bc --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# iSulad + +This is a umbrella project for gRPC-services based Lightweight Container Runtime Daemon. +iSulad provide a unified architecture to meet the different needs of CT and IT. +Compared with Docker written by Golang, iSulad has the characteristics of light, agile, fast, +not limited by hardware specifications and architecture, and can be applied more widely. + +## How to Contribute + +We always welcome new contributors. And we are happy to provide guidance for the new contributors. +iSulad follows the kernel coding conventions. You can find a detailed introduction at: + +- https://www.kernel.org/doc/html/v4.10/process/coding-style.html + +## Building + +Without considering distribution specific details a simple + + mkdir -p build && cd ./build && cmake .. && make && sudo make install + +is usually sufficient. + +## Licensing + +iSulad is licensed under the Mulan PSL v1. diff --git a/cmake/checker.cmake b/cmake/checker.cmake new file mode 100644 index 0000000..fa17cb0 --- /dev/null +++ b/cmake/checker.cmake @@ -0,0 +1,147 @@ +include(CheckIncludeFile) + +# check depends library and headers +find_package(PkgConfig REQUIRED) + +# check python +find_program(CMD_PYTHON python) +_CHECK(CMD_PYTHON "CMD_PYTHON-NOTFOUND" "python") + +# check tools +find_program(CMD_TAR tar) +_CHECK(CMD_TAR "CMD_TAR-NOTFOUND" "tar") +find_program(CMD_SHA256 sha256sum) +_CHECK(CMD_SHA256 "CMD_SHA256-NOTFOUND" "sha256sum") +find_program(CMD_GZIP gzip) +_CHECK(CMD_GZIP "CMD_GZIP-NOTFOUND" "gzip") + +# check std headers ctype.h sys/param.h sys/capability.h +find_path(STD_HEADER_CTYPE ctype.h) +_CHECK(STD_HEADER_CTYPE "STD_HEADER_CTYPE-NOTFOUND" "ctype.h") + +find_path(STD_HEADER_SYS_PARAM sys/param.h) +_CHECK(STD_HEADER_SYS_PARAM "STD_HEADER_SYS_PARAM-NOTFOUND" "sys/param.h") + +CHECK_INCLUDE_FILE(sys/capability.h HAVE_LIBCAP) +if (HAVE_LIBCAP) + message("-- found linux capability.h --- works") + add_definitions(-DHAVE_LIBCAP_H=1) +else() + message("-- found linux capability.h --- no") +endif() + +if (SYSTEMD_NOTIFY) +# check systemd + find_path(SYSTEMD_INCLUDE_DIR systemd/sd-daemon.h) + _CHECK(SYSTEMD_INCLUDE_DIR "SYSTEMD_INCLUDE_DIR-NOTFOUND" "systemd/sd-daemon.h") + find_library(SYSTEMD_LIBRARY systemd) + _CHECK(SYSTEMD_LIBRARY "SYSTEMD_LIBRARY-NOTFOUND" "libsystemd.so") +endif() + +# check zlib +pkg_check_modules(PC_ZLIB "zlib>=1.2.8") +find_path(ZLIB_INCLUDE_DIR zlib.h + HINTS ${PC_ZLIB_INCLUDEDIR} ${PC_ZLIB_INCLUDE_DIRS}) +_CHECK(ZLIB_INCLUDE_DIR "ZLIB_INCLUDE_DIR-NOTFOUND" "zlib.h") +find_library(ZLIB_LIBRARY z + HINTS ${PC_ZLIB_LIBDIR} ${PC_ZLIB_LIBRARY_DIRS}) +_CHECK(ZLIB_LIBRARY "ZLIB_LIBRARY-NOTFOUND" "libz.so") + +# check securec +find_path(LIBSECUREC_INCLUDE_DIR securec.h) +_CHECK(LIBSECUREC_INCLUDE_DIR "LIBSECUREC_INCLUDE_DIR-NOTFOUND" "securec.h") +find_library(LIBSECUREC_LIBRARY securec) +_CHECK(LIBSECUREC_LIBRARY "LIBSECUREC_LIBRARY-NOTFOUND" "libsecurec.so") + +# check libyajl +pkg_check_modules(PC_LIBYAJL REQUIRED "yajl>=2") +find_path(LIBYAJL_INCLUDE_DIR yajl/yajl_tree.h + HINTS ${PC_LIBYAJL_INCLUDEDIR} ${PC_LIBYAJL_INCLUDE_DIRS}) +_CHECK(LIBYAJL_INCLUDE_DIR "LIBYAJL_INCLUDE_DIR-NOTFOUND" "yajl/yajl_tree.h") +find_library(LIBYAJL_LIBRARY yajl + HINTS ${PC_LIBYAJL_LIBDIR} ${PC_LIBYAJL_LIBRARY_DIRS}) +_CHECK(LIBYAJL_LIBRARY "LIBYAJL_LIBRARY-NOTFOUND" "libyajl.so") + +find_path(HTTP_PARSER_INCLUDE_DIR http_parser.h) +_CHECK(HTTP_PARSER_INCLUDE_DIR "HTTP_PARSER_INCLUDE_DIR-NOTFOUND" "http_parser.h") +find_library(HTTP_PARSER_LIBRARY http_parser) +_CHECK(HTTP_PARSER_LIBRARY "HTTP_PARSER_LIBRARY-NOTFOUND" "libhttp_parser.so") + +pkg_check_modules(PC_CURL "libcurl>=7.4.0") +find_path(CURL_INCLUDE_DIR "curl/curl.h" + HINTS ${PC_CURL_INCLUDEDIR} ${PC_CURL_INCLUDE_DIRS}) +_CHECK(CURL_INCLUDE_DIR "CURL_INCLUDE_DIR-NOTFOUND" "curl/curl.h") +find_library(CURL_LIBRARY curl + HINTS ${PC_CURL_LIBDIR} ${PC_CURL_LIBRARY_DIRS}) +_CHECK(CURL_LIBRARY "CURL_LIBRARY-NOTFOUND" "libcurl.so") + +if (OPENSSL_VERIFY) + find_path(OPENSSL_INCLUDE_DIR openssl/x509.h) + _CHECK(OPENSSL_INCLUDE_DIR "OPENSSL_INCLUDE_DIR-NOTFOUND" "openssl/x509.h") +endif() + +if (GRPC_CONNECTOR) + # check websocket + find_path(WEBSOCKET_INCLUDE_DIR libwebsockets.h) + _CHECK(WEBSOCKET_INCLUDE_DIR "WEBSOCKET_INCLUDE_DIR-NOTFOUND" libwebsockets.h) + find_library(WEBSOCKET_LIBRARY websockets) + _CHECK(WEBSOCKET_LIBRARY "WEBSOCKET_LIBRARY-NOTFOUND" "libwebsockets.so") + + # check protobuf + pkg_check_modules(PC_PROTOBUF "protobuf>=3.1.0") + find_library(PROTOBUF_LIBRARY protobuf + HINTS ${PC_PROTOBUF_LIBDIR} ${PC_PROTOBUF_LIBRARY_DIRS}) + _CHECK(PROTOBUF_LIBRARY "PROTOBUF_LIBRARY-NOTFOUND" "libprotobuf.so") + + find_program(CMD_PROTOC protoc) + _CHECK(CMD_PROTOC "CMD_PROTOC-NOTFOUND" "protoc") + find_program(CMD_GRPC_CPP_PLUGIN grpc_cpp_plugin) + _CHECK(CMD_GRPC_CPP_PLUGIN "CMD_GRPC_CPP_PLUGIN-NOTFOUND" "grpc_cpp_plugin") + + # check grpc + find_path(GRPC_INCLUDE_DIR grpc/grpc.h) + _CHECK(GRPC_INCLUDE_DIR "GRPC_INCLUDE_DIR-NOTFOUND" "grpc/grpc.h") + find_library(GRPC_PP_REFLECTION_LIBRARY grpc++_reflection) + _CHECK(GRPC_PP_REFLECTION_LIBRARY "GRPC_PP_REFLECTION_LIBRARY-NOTFOUND" "libgrpc++_reflection.so") + find_library(GRPC_PP_LIBRARY grpc++) + _CHECK(GRPC_PP_LIBRARY "GRPC_PP_LIBRARY-NOTFOUND" "libgrpc++.so") + find_library(GRPC_LIBRARY grpc) + _CHECK(GRPC_LIBRARY "GRPC_LIBRARY-NOTFOUND" "libgrpc.so") + find_library(GPR_LIBRARY gpr) + _CHECK(GPR_LIBRARY "GPR_LIBRARY-NOTFOUND" "libgpr.so") + + # check clibcni + pkg_check_modules(PC_CLIBCNI REQUIRED "clibcni") + find_path(CLIBCNI_INCLUDE_DIR clibcni/api.h + HINTS ${PC_CLIBCNI_INCLUDEDIR} ${PC_CLIBCNI_INCLUDE_DIRS}) + _CHECK(CLIBCNI_INCLUDE_DIR "CLIBCNI_INCLUDE_DIR-NOTFOUND" "clibcni/api.h") + find_library(CLIBCNI_LIBRARY clibcni + HINTS ${PC_CLIBCNI_LIBDIR} ${PC_CLIBCNI_LIBRARY_DIRS}) + _CHECK(CLIBCNI_LIBRARY "CLIBCNI_LIBRARY-NOTFOUND" "libclibcni.so") +else() + pkg_check_modules(PC_EVENT "event>=2.1.8") + find_path(EVENT_INCLUDE_DIR event.h + HINTS ${PC_EVENT_INCLUDEDIR} ${PC_EVENT_INCLUDE_DIRS}) + _CHECK(EVENT_INCLUDE_DIR "EVENT_INCLUDE_DIR-NOTFOUND" "event.h") + find_library(EVENT_LIBRARY event + HINTS ${PC_EVENT_LIBDIR} ${PC_EVENT_LIBRARY_DIRS}) + _CHECK(EVENT_LIBRARY "EVENT_LIBRARY-NOTFOUND" "libevent.so") + + pkg_check_modules(PC_EVHTP "evhtp>=1.2.16") + find_path(EVHTP_INCLUDE_DIR evhtp/evhtp.h + HINTS ${PC_EVHTP_INCLUDEDIR} ${PC_EVHTP_INCLUDE_DIRS}) + _CHECK(EVHTP_INCLUDE_DIR "EVHTP_INCLUDE_DIR-NOTFOUND" "evhtp/evhtp.h") + find_library(EVHTP_LIBRARY evhtp + HINTS ${PC_EVHTP_LIBDIR} ${PC_EVHTP_LIBRARY_DIRS}) + _CHECK(EVHTP_LIBRARY "EVHTP_LIBRARY-NOTFOUND" "libevhtp.so") +endif() + +if (ENABLE_EMBEDDED) + pkg_check_modules(PC_SQLITE3 "sqlite3>=3.7.17") + find_path(SQLIT3_INCLUDE_DIR sqlite3.h + HINTS ${PC_SQLITE3_INCLUDEDIR} ${PC_SQLITE3_INCLUDE_DIRS}) + _CHECK(SQLIT3_INCLUDE_DIR "SQLIT3_INCLUDE_DIR-NOTFOUND" "sqlite3.h") + find_library(SQLITE3_LIBRARY sqlite3 + HINTS ${PC_SQLITE3_LIBDIR} ${PC_SQLITE3_LIBRARY_DIRS}) + _CHECK(SQLITE3_LIBRARY "SQLITE3_LIBRARY-NOTFOUND" "libsqlite3.so") +endif() diff --git a/cmake/helper.cmake b/cmake/helper.cmake new file mode 100644 index 0000000..b120312 --- /dev/null +++ b/cmake/helper.cmake @@ -0,0 +1,9 @@ +# use to check result +macro(_CHECK) + if (${ARGV0} STREQUAL "${ARGV1}") + message("ERROR: can not find " ${ARGV2} " program") + set(CHECKER_RESULT 1) + else() + message("-- found " ${ARGV2} " --- works") + endif() +endmacro() diff --git a/cmake/options.cmake b/cmake/options.cmake new file mode 100644 index 0000000..c13c2f4 --- /dev/null +++ b/cmake/options.cmake @@ -0,0 +1,47 @@ +# build which type of lcr library +option(USESHARED "set type of libs, default is shared" ON) +if (USESHARED STREQUAL "ON") + set(LIBTYPE "SHARED") + message("-- Build shared library") +else () + set(LIBTYPE "STATIC") + message("-- Build static library") +endif() + +option(ENABLE_GRPC "use grpc as connector" ON) +if (ENABLE_GRPC STREQUAL "ON") + add_definitions(-DGRPC_CONNECTOR) + set(GRPC_CONNECTOR 1) +endif() + +option(ENABLE_SYSTEMD_NOTIFY "enable systemd notify" ON) +if (ENABLE_SYSTEMD_NOTIFY STREQUAL "ON") + add_definitions(-DSYSTEMD_NOTIFY) + set(SYSTEMD_NOTIFY 1) +endif() + +option(ENABLE_OPENSSL_VERIFY "use ssl with connector" ON) +if (ENABLE_OPENSSL_VERIFY STREQUAL "ON") + add_definitions(-DOPENSSL_VERIFY) + set(OPENSSL_VERIFY 1) +endif() + +option(PACKAGE "set lcrd package" ON) +if (PACKAGE STREQUAL "ON") + set(LCRD_PACKAGE "iSulad") +endif() + +option(VERSION "set lcrd version" ON) +if (VERSION STREQUAL "ON") + set(LCRD_VERSION "1.0.31") +endif() + +option(DEBUG "set lcrd gcc option" ON) +if (DEBUG STREQUAL "ON") + add_definitions("-g -O2") +endif() + +option(GCOV "set lcrd gcov option" OFF) +if (GCOV STREQUAL "ON") + set(ISULAD_GCOV "ON") +endif() diff --git a/cmake/protoc.cmake b/cmake/protoc.cmake new file mode 100644 index 0000000..a5675ca --- /dev/null +++ b/cmake/protoc.cmake @@ -0,0 +1,62 @@ +set(PROTOS_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/api/services) +set(TYPES_PROTOS_PATH ${CMAKE_CURRENT_SOURCE_DIR}/src/api/types) + +set(GRPC_OUT_PRE_PATH ${CMAKE_BINARY_DIR}/grpc) +set(TYPES_PROTOS_OUT_PATH ${GRPC_OUT_PRE_PATH}/src/api/types) +set(CONTAINER_PROTOS_OUT_PATH ${GRPC_OUT_PRE_PATH}/src/api/services/containers) +set(IMAGE_PROTOS_OUT_PATH ${GRPC_OUT_PRE_PATH}/src/api/services/images) +set(CRI_PROTOS_OUT_PATH ${GRPC_OUT_PRE_PATH}/src/api/services/cri) + +execute_process(COMMAND mkdir -p ${TYPES_PROTOS_OUT_PATH}) +execute_process(COMMAND mkdir -p ${CONTAINER_PROTOS_OUT_PATH}) +execute_process(COMMAND mkdir -p ${IMAGE_PROTOS_OUT_PATH}) +execute_process(COMMAND mkdir -p ${CRI_PROTOS_OUT_PATH}) + +execute_process(COMMAND ${CMD_PROTOC} -I ${TYPES_PROTOS_PATH} --cpp_out=${TYPES_PROTOS_OUT_PATH} + ${TYPES_PROTOS_PATH}/descriptor.proto ERROR_VARIABLE types_err) +if (types_err) + message("Parse types.proto failed: ") + message(FATAL_ERROR ${types_err}) +endif() + +execute_process(COMMAND ${CMD_PROTOC} -I ${PROTOS_PATH}/containers --cpp_out=${CONTAINER_PROTOS_OUT_PATH} + ${PROTOS_PATH}/containers/container.proto ERROR_VARIABLE containers_err) +if (containers_err) + message("Parse containers.proto failed: ") + message(FATAL_ERROR ${containers_err}) +endif() + +execute_process(COMMAND ${CMD_PROTOC} -I ${PROTOS_PATH}/containers --grpc_out=${CONTAINER_PROTOS_OUT_PATH} + --plugin=protoc-gen-grpc=${CMD_GRPC_CPP_PLUGIN} ${PROTOS_PATH}/containers/container.proto ERROR_VARIABLE containers_err) +if (containers_err) + message("Parse containers.proto plugin failed: ") + message(FATAL_ERROR ${containers_err}) +endif() + +execute_process(COMMAND ${CMD_PROTOC} -I ${PROTOS_PATH}/images -I ${TYPES_PROTOS_PATH} + --cpp_out=${IMAGE_PROTOS_OUT_PATH} ${PROTOS_PATH}/images/images.proto ERROR_VARIABLE images_err) +if (images_err) + message("Parse images.proto failed: ") + message(FATAL_ERROR ${images_err}) +endif() + +execute_process(COMMAND ${CMD_PROTOC} -I ${PROTOS_PATH}/images -I ${TYPES_PROTOS_PATH} --grpc_out=${IMAGE_PROTOS_OUT_PATH} + --plugin=protoc-gen-grpc=${CMD_GRPC_CPP_PLUGIN} ${PROTOS_PATH}/images/images.proto ERROR_VARIABLE images_err) +if (images_err) + message("Parse images.proto plugin failed: ") + message(FATAL_ERROR ${images_err}) +endif() + +execute_process(COMMAND ${CMD_PROTOC} -I ${PROTOS_PATH}/cri --cpp_out=${CRI_PROTOS_OUT_PATH} ${PROTOS_PATH}/cri/api.proto + ERROR_VARIABLE cri_err) +if (cri_err) + message("Parse cri.proto failed: ") + message(FATAL_ERROR ${cri_err}) +endif() + +execute_process(COMMAND ${CMD_PROTOC} -I ${PROTOS_PATH}/cri --grpc_out=${CRI_PROTOS_OUT_PATH} + --plugin=protoc-gen-grpc=${CMD_GRPC_CPP_PLUGIN} ${PROTOS_PATH}/cri/api.proto ERROR_VARIABLE cri_err) +if (cri_err) + message("Parse cri.proto plugin failed: ") + message(FATAL_ERROR ${cri_err}) +endif() diff --git a/cmake/set_build_flags.cmake b/cmake/set_build_flags.cmake new file mode 100644 index 0000000..d9b33e9 --- /dev/null +++ b/cmake/set_build_flags.cmake @@ -0,0 +1,16 @@ +# set common FLAGS +set(CMAKE_C_FLAGS "-fPIC -fstack-protector-all -D_FORTIFY_SOURCE=2 -O2 -Wall -Werror -rdynamic") +if (GRPC_CONNECTOR) + set(CMAKE_CXX_FLAGS "-fPIC -std=c++11 -fstack-protector-all -D_FORTIFY_SOURCE=2 -O2 -Wall -Werror") +endif() +set(CMAKE_SHARED_LINKER_FLAGS "-Wl,-E -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wtrampolines -fPIE -pie -shared -pthread") +set(CMAKE_EXE_LINKER_FLAGS "-Wl,-E -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wtrampolines -fPIE -pie") + +if (ISULAD_GCOV) + set(CMAKE_C_FLAGS_DEBUG "-Wall -fprofile-arcs -ftest-coverage") + set(CMAKE_CXX_FLAGS_DEBUG "-Wall -fprofile-arcs -ftest-coverage") + message("-----CXXFLAGS: " ${CMAKE_CXX_FLAGS_DEBUG}) + message("------compile with gcov-------------") + message("-----CFLAGS: " ${CMAKE_C_FLAGS_DEBUG}) + message("------------------------------------") +endif() diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..8ba17ed --- /dev/null +++ b/config.h.in @@ -0,0 +1 @@ +#cmakedefine VERSION "@LCRD_VERSION@" diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..f849492 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,41 @@ +# iSulad Architecture + +## Overview + + + +![architecture](design/architecture.png) + +iSulad是一个符合OCI标准的容器运行引擎,强调简单性、健壮性和轻量化。它作为守护进程提供服务,可以管理其主机系统的整个容器生命周期:镜像的传输和存储、容器执行和监控管理、容器资源管理以及网络等。iSulad对外提供与docker类似的CLI人机接口,可以使用与docker类似的命令进行容器管理,并且提供符合CRI接口标准的gRPC API,可供kubernetes\Hasen 按照CRI接口协议调用。 + +为了方便理解,我们将iSulad的行为单元分成不同的模块,模块大致被组织成子系统。了解这些模块、子系统及其关系是修改和扩展iSulad的关键 + +本文档将仅描述各个模块的high-level功能设计。有关每个模块的详细信息,请参阅相关设计文档。 + +## 子系统 + +外部用户通过调用子系统提供的GRPC API与iSulad进行交互。 + +- **image service** : 镜像管理服务,提供镜像相关操作,如镜像下载、查询、删除等 +- **execution service**: 容器生命周期管理服务,提供容器的相关操作,如容器创建、启动、删除等 +- **network**:网络子模块负责CRI的Pod的网络管理能力。当Pod启动时,通过CNI的接口把该Pod加入到配置文件制定的网络平面中;当Pod停止时,通过CNI的接口把该Pod从所在的网络平面中退出,并且清理相关的网络资源。 + +## 模块 + +- **image content** : 管理镜像元数据以及容器文件系统。 + +- **resource manage**: 容器资源管理,如设置可用cpu、memory等资源限制 + +- **Executor**:执行实际容器操作的runtime,提供lcr作为默认runtime,可通过plugin机制扩展 + +- **Events**:容器事件收集 + +- **Plugins**:提供插件机制,通过不同插件,实现扩展容器功能。 + +- **DFX**:提供日志机制用于定位问题,提供garbage collect 机制回收容器D/Z 等异常容器资源,具备DFX能力。 + +### 网络架构设计 + +架构图,如下: + +![CNI_architecture](./design/CNI_architecture.png) diff --git a/docs/design/CNI_architecture.png b/docs/design/CNI_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..cdf480c68f52b4d3a45af2ba158afbd4b483a743 GIT binary patch literal 139376 zcmeFa2Ut@}w>KWUg7qLpQ8_kHX<`8s1neNqMsETtB1n}ITChY!KvbkRQ2`Z0@7>U( zYd|_dy0p+sAdvs;y^|2jd)xco`+fKSd_K=PNHTk7%__gOX00___lrvB*RgD6L7`CV zTLUxOwwtE-tQZ+qV7v_ur@^TzmHH zK^>9Zw{M?-fB;JJB1-mel#Ir~g9lNv`Y4q>;^N|xl9DKW0hEf3jEoFQ1C261h|+OB zbLPx)9cP6zGAMm4%JPV;tgM3UnbrFD6ciLtXmOPB^NSZRqVD*rTvR~a6GvHws;H=- z+$B+#cnu8=9gT}9bR-J>0p%s5qod=jk3#vLLETH#*Vn(HuYn3YgTkhw?p2}OGf`Lq zDnb_JUT17(-Hy?ggii9~&`KuKSMY+xuVvExW!os2JGr$dyV`h^P{X?eX&RQiv>$e6Aq#ff(q8J@bM1{P}Z@h`N2L zB-szd(C6-S3OUR40KKlUYAQD|5?vROnwnai>h&B?EKZHh%*;d+#;{c+Y*XyLrsCY( zT<5weFG5dfSx<3hT@^kMkHggaB6N$v!{@I=m0*OfI=;$aWQLDy?{XI=RJv~)p)c*edF;WMKM56W+d&XwR z#>S?``oaH5Q?os@)TyblDd^8E_%lU?{&X35c>)tyVR~D3n0C zGbgXw>kiJJcz0cmI5Sw}D( z3eN4bGN_Q*ur{E?YsCga_|1cVZPyOJZc(%J!QOTbbruJqcb7b39_T*1-zOvGh-+(f z?!Tcj^g&Z4#r9h-o4QF{%0Oh=zJ9r+=;wCaM(D?n zTUh9a%z6Ih(9h$VJE0$6zOpOUx+=wQ5bgq;NMP$uh|>KiJYO!N%>$D3We zCdYkqi*NRdEo==A)EghT-K%FfPl=C<@g4sLKJTfgWNt05SC`Hks&D9a!o?*7lD+c4 z=UQ`H2YSK(f&--qaS1NDDbP1>=D~-CyS71VE~w`f&ytIwxxFV%K8DlgK0I#XqT|&K zJ}@6L`IsBaKBaeCxNUgCGADPGGBNri?_63)o+V(^P|sZ4*TA?*n_j()5b9}#hhDu# zxpm+(@PW7ch=YQ;1crR{_?%51iNa$rTf{H2sV+2+k{L3u*>-oqY1U-Esd&6bZ(zRu zhu?VKK;F}|km0{qFtg=#;4mI%#?Qc|sFiBk^i zhVkIq+J!On+S>%!&aau~hGlPk6GYoqa8hX{*SELuI{6L%I)KPB!D43TJ+dKaL3!qr z5Ue@KtEa5%bx9HV&9kbvSL7YC;r|#Kg~qZkjLy<#4fz<$t{W~{&J8V*&((6m<0O6J z%1-88g&4!Qm%rlQ5@GaU{czswgpV>&VPUp3x{bm$UX$xi-5~})$gxbR#~OABIK-n`M^H*znZdG3}1%+R) z$s0?Z%!}UJ^^r={oHmVnD25+OAGzN>GGN;1I9{OM*mBp^$$UOIN_&6YsI%g%bvC#k!4SS5{Du1@CNYo;u!Tqw7pXq!nbn_q|{?Zq!pRTDjh% zv1nn7#EqDyHt)k~;^y3}^^TLy2uE*K`d*zeR=`GC=$*lfvw%t;D%Q5yA#lHOhhBp%$_&hSYW3PX%aumsVZvRxEC+I5^(5)XV%Q=(# z`2D@y$8I^~o@tS!i-SHzEi7$?ME%O?qkF@4d1NRunb11&fkRE$l;K^3@aHos3Idzw zu8X9VRj7Ox$#S=UB+w%|VsCZ$99;yRCGT#9ro_sSCN}6g%sq!lQHjmPvC3<8S&gpM zsJQc80fx2jpLNIy_-?zyy6^H>j-_%w(Mb%@GM+8Or&QZJ?GM}Ok+BYepluoc(#K|c zf=ybMW!RzFUg#M*K5y9Tk>MNLA|9DENcQ*O`)pvg48f6-q5505LcPrXwgMx~X(PWy znPaobt%@CM5(AoDVgBY@W?60MS|_2YT|hB56?!qfS|qJ0den8WAV5b?@+5~f_ZP&T zZmS7T=%~*5J&snd8ynlPvqyDKYR5(cTj$Xku=7`l8P~?@_GXpFvDDnyLxdCgZ;)> z?(j6u>cqF*k(2TwV)+vrILw@^ee9hU2{TYr=5L`8ca9zjFRWW?Jb$@joyFvkpz}gE z^OBEsPT)p@1Df-tYuK_mwOHRG(^V=WM$q8d=T)4Hy31g7~+<%A1HFz|p6cr?aMQG5zO}!;g zoZl^XC!wgKJJ|cJTKZ<|(}lZFDZ~oy2rFt?$?SEuj>vT-?dcJHNmAF&mAK%51=~F% zxvQ|NsiPfmu48biJsWSUm{FtVlf=KWQ?%{uof z&E`BVv(BCz?wq9tW{>U9JT8T)q#0O#yG~A0=~Fh%Y*~&!%|fbwY$QjHUK#UP4S76u z)X}rVh+*vy=5}>xT+YQ>vX36ns93kj`?HigSfDeBZo&DV6af*23MAR=3mh<4Ka1wh zUyzjY$hdhIH{#Vc{}T54-%(LQCy6#EJ)m`5`&4ICDgc|pi?k1Cs@gk`37Eq!ZHW`x zkU-;THM^PSKoqgFC++rAKunr@mJ!`+e*YrnZtFKV)5{`;v(YQso#++k1CO~^;uB1d zCBA>z6?%g^JW=dowgMnZVX~rlD4}ifX2=*IpZWk~~V0Yn>Fls9tya@q0WDV`D^?F@!+|`iQ*ZenTQ&JY2 zFz_Ci^XjseqL(8}ufLhe?!r#{=1|T1k%EBMtCEVq`o#05&Uw;eHSu@A0pF-h!DqgY zSLLS+4TKw9L_*d4lZt8hVMANH4s2oo|D5ds_Jzs%0HV6&B@XLK8j8JpAG|h?449*o zeUZcZEXI325m$Yn7KXT2gFdT`G_K$*X5ZqszQ2fw`t?n%_B4!FDBjObrm7=@;g{gW zaF~r2kq|F>oT+^i$FSzPYQ6rOI~$osIdioOc$98`!dYfO`hZ!O8B(j6PaL1d6p#6J z^nr2b>RH-mT7^jQYa#Vsc1Da0ZgWuN4 z&c~j}M4ObSh2Q*En)0b&yd&Sv0zZjnnms6Ud?Z-9c#u@cdPBtS+eEA4?X+_HCZ;(w zO|Ug~V&o&dChpOdz>9%+-%gKqrn%M@WP+8*$&!=2gSZnY-QfDbIF_9>LE;_#2*m@G zCT<2ky^2!mbQ}*3XoYPpO0l)It03Uxp0J}JBypO*LO;2Pzk`7iQRf;L&!UecK(^c> z_?}FuXR$+r%@RQH6M=Z?i#0~0-*SgXvLV&I>BJTz*P0bv=yP)s(}T=F(1`3*j9)eB zgPoXrZ-qhZ79_SLL{mTo(~DG6M5QL-PYKD~0xh-NChCDR(PKBoZ(b zg}l!St~GTS^@M!k2A+~vyokOQ1kGCJf8eADZH53VTfI8hy_BV6%dbkWu6OoW`2pLE zA_#PY#n=y0xB9Rb9=^G#_`4^ZUnUL4(15jPs2@n>;iB?!&2Q}%nA>yHj#n21aw}}_ z0+!+zW&iSZ$PMS6wz0+ow_AS>xohMV%*g? z{Iv>fpkcbz=cR|CgO|KWTEkU5Ml zgb7au)L)Diiu?$NMpl2HGnwAgR#==pvDM$eETOOM5=H7}8k;gSr)@pUaGq}ogn||u z_YdpL?%@@CSgETulRpTVc69;QygNqdq;5=| zV(74XH&6p@puVS($aSRuY(|{wiPH}5@*--7oNIiyLaJNFFby{`=S^cTl9sAnFxn@S zu^gSFIW0HnUkj0>!myHC`L%*>Y3ZwvwLQJwct@77-rpeh9RxRl496j}Eb4WOqe#ZR zySu*gB_v#g!a+6FIY5j8AZB+8e*6dmAc0S;+KRiHTog9w%@*7@_6Ea5Cr>}oI#MAI z4+Ulyr`KGx-49ASTuZ;J{6bz{d95~YsA@WexUX-8P?2^cfHG0DfE@eRzn+h7^cUjS zVYVC$S543U5vd~tj(b<^#2VA}r#jq$?iKP#J*B4?6a$VYSQKOhzYYFdSQ=;1s;Nc> z#7aW2HrjQQ?!0dmO!0BJ`0U8+IMtq94YcKE{~hN|KHMI?Koyr6A@FkEw0~``qLKi` zsLUZImQ6_+v%kkL@OYc$06?kn#aQFbl>VON>krsi$CX(ZxU}{fM*}@ofS{~~d63DT z-IQtdD^I$rn=#2=qK2n)~YBrF)JPZVm)OpMdwP>}t0Tqr|Ebhk=@7m|YJRA4kzJ4FtI1iAfT?xoa zg6WTsX2;9Haf{N|T4fFwcAwK-S7-E11$}I@H(Qr~gC>?kCf3{;0tZpw_c(KJ5!@2i zZFRuO{ImC(s1w5AJPTyDg|NFE4#r$n8){hE_rNF0Xk#Dsnd%UhHpvhZ|xJTbaC9a_52>_wvE;?3cx4T1hmLXo(Dug z2PEKL*1-uirEi><2rriClg8?H7hGCc z^6QE^k0|X=UQL;3VCSzmWKa>z}%E z;#d@i!kzJs!*ccQ>yS8eM(o&DWU$lt`=K3LfQQ5^)%<+;XKnHU&=UFL>j zR!EPEb=T-m^SbB214(99`&EsK`lC8DrUW58*|vc$ZJFLV<#ImGT(S7gb+@odc4BK1 z#Q9al*sBxGb@-j;o0QRp0(2pu+Dqbk%=KEZct5k9PMc{*HBYSSYCf=YPO35;9`V{Da0=i@)HF?EA{% zNY@zDg=BmfeHqukaEs>hPe0Pgtjw>j|EjB~3&2^OKhOWr2R?Dhl#)}#46Xu`PQCzb z+4Z%NFnlJV{W?!zxu*)}<>K`;+gDRMN!P8Wc@maH!#>4z{`hNE@%{Egr10uo59f1z zE(_Kz-iqQ#tTkA_CK*3i{pN)5aQO_ILayf;adsFb2i0z4dyU0+6q!8Ba}-N#(ompG z71$Zs8InSu*Wt^hYr0wkT(7_ZU%rbCZe-F#y4x#ZA62(q+DgT7TX3VWQCxSQbV*@) z*EG?6an}0qSdb$3N>H~l_bIwGtxXcgHQv3yPlaa-&r7`S2JlRRT%@oX}n)&Gtw|&Gs9Xb3&=- z_n18#6#X2s4UXo3M2Wn(k+eB<-*?V2vyr3+ZS&*~XYYenct3{xxl{0ZV}vgM2B~Mm zqPMUXubICo*AtK&iOkmAxfV_=%ebpHG=F3d>rRy7-yz5*(3(UTkx3D>tn(fkK zO|1=*&*s2*7#nEgi$kC3c~mY#tMNaKV9Xwzf>)QY_#iRE?K%phGNfi+>Fh4(A7ZH8 z@bMpdoZFH=+i~%eQgHZPhqKVjm6+gm6@kl&utL4D8?!%>dKH4&BDj)ubKMSeix!t3Bol)BP`gVywW;iUsMi=( z8GjkkrTTrWH8*i1p~*+an?3WK>}*ANxDT_K8nX$HqnW@Gym3-k|$~2b1MEx|Y*Bi`p$L zvcfGI0^fWW9DA&0!X@#W%_t?Q)~j^<&s1*D!@UQVw)t`qD$ZK<{zLq1>j$j(3H z?=_Basx9Azo$YG5s4>=(v&+A}hnM=GPF=`Jz{lSJE$>pJ4>AE)FCCmuI;+P*>$1v z4RXU>Je(NUzCd!@E=po>_`?2ziIQ|~ZVOo&Hu%5N&6~PHq7EF>3A2w3y**4oW zILsC$U3~xaiaFSx0FD;=j$Z(DoJpM^w)YJ3LCUw{P{Cm>JoUjAYUkp<(P zwM;nsxHzuRUR7UE(eAwkXr18Q(s?$s`^JC=&gp7Xdw;n3LR58MS7D&fikdYKSxLn& zFsQHQ999;HQA>ukcwPe8#^WG4Rc>cQ9_u7jI@f4=8nM?ACc4TdbA%&5+mhMtvA$hZ zY@ZI3*V-dqna##67vygF<7q{$RF2TD+|)sX+YPG_M5;>0%kyisXJD$Y z@%c1{lLy|$*l~uw=OipMlx!(GAGtkBSF}n# zsrWo+4XLw2LmN9auqj7!#1kV;F#Z&Rp2KxdH!bqJGe*o~|Dz{kiweCdN$!V9g{(QfdTg4*=+6^`3kkg7hn zhniWA9&?KqLzn{)uRTvKKuj#>u%$lkV5oprR=h#RweB}Pc3cO2NClUVw;#e5o}Ko3 zbOMfdAb4A6j@^$I95OAhV>Q9{U2kl{vX`&m=Q)~J&>wiW>PGutUFC-l87^CY28Mp% zgT=DhUHsGJ)tYo=2OzcJSKuqJ6Z)twq)vWE-mY5xw)r$?b$Z1rv&N$4c;L!`4nR=c zjyp&t>5?Zh&R!ad!~>nxNVU~3viekZzH&I;f%gvQ?tbBNe|=#FD@~H|Ns^bOP6`Q@ z0B!ksH|od6y_0h~(nE??ZbOPldo}gVy;K9Ly|t|De3G|8%*WqNG#$b}>Avsl4OrH# z@bn!&$Wk`A7BHY4_mu@RH{Mc_d?V)f;Zi~g2{M{>EXM>8H30#E`8I3*iuxu$3|bgh z{0E|}?QS}%YPtxDem{t814J2=TctSZpkyU}ZE#di-uef^DohSHClSU7oY+vf3n7aWj6sskoe8RFgIpmoG(&XWo1~JCFj3S7LG|e-L-o+ser{ zmO&&vJ9o59Gg+5k+Xd{D;}1Jk_;q5FRD2|uSpH8FX9uTZ+y!sFfFp{z6qoed`9U~~ zzE(xTWysoDYDyDRSo!@}Ri$pbxcMHotFi4PeffeUnx|r7&I^ZikKX#&$l_0IeVct* z`xiB>q6pqG!5&eBwR*n4|LZ`#OBuIef*HTIEuj9dKTu!Mne5D=^<@vN4$unqe7hwQ zELv652T8ofdyA7;elei3z+u8v$3SAlCRkFNb5cCwYrehdG{?$Ak~R2UvSDakvrx;n ztxdG36ONi||LsRgr8|C{`tfU{ zygxU2=8{?ku(_V{BMDu;7<;>-=KDH`<^epwwXu}jiDfibme?21PXsiizlL#Sg!H<5 zSXGB5yF-q1=-@S86@s0i;k~(X4t*Ym#SWz5$H2N<+&o-9kBs5F8#0k+=6c@I= zxzDbgdCP6dVe}0Z#*RGJZw13as@rT4|DVyhI(_ zy%t7AK0;0UpB4I}!P-?>W={A|h>HQR3BtsptYD{lMd9Od!&!1toFbxe$hP0|tO?og z!#RAdX!|);$-dXyA60a*Cb5Q&nfsJV8?1I?gmulY*!_mhE@x(xO87WUdpBgm)DPS( zKm2Ko^g;fanpEF?@G$U9tjZsd-Q_lt7;_{#s zvU{keZ6?#yuQEZk$ploGz_JsWL z8q1*Opf4*K@GZTy`Hq073D(lt>aQ#QxxxK6;^qabz6ZC2uR|x4L0h({l;=E{wB% zgD`I6Enfr&!5IcQLSM_vRGsP2*x?QQA94-{!KP+A0%H;Mqct?j-uX}>8*2(JrGna= zV$1}+gy$VZ6*^o%q#sv zhJsS{V*bPRbs7mSR?uX$*PBnwkSSLHVKVSyG$H*OLND&n60iG}obJ(`SxpZcFp82!X*C>Fehm3goj<;`qh%5?soJsKuHf!hE6 z3s%0!ZMAw9%sA;EyBpii4-K`+MJa@d&=>)%zwKVmA-x-d-G zuew>*CCO~r5CBY;CS{9oET$xNvt7iX4lp`?9t(y}qzu{q_oXa<(!hV182#PYld}FN zRI^&Ty7TK#YUp&sDokuq4PkAa?b0A}vC^~9sJ+6vdE88*d>Nu>25IWE%mr@@_V&U6 zb+42fh(7XGf_}}h8f`otSVFVPezG1)YjbuTABQvra2a1e;WQ?taFD1x8$wF`; zVUdIvob^;Lhz0!wI#nQ>{9c|;(-iod5Pdr!5_pzL-^{hVfBX9B1ez_0r6){Y(tOENf*I_ps)6-%M|H+tc_%B5HfGm09Dx&^eFfQE?!@A=OiOy zWZpT9GTc%B6Awd?<7xi4Tj^}u2hQP=S;3I8GYK2X@H5e$oxV^4HHBaHr*8>Bi>wZP zLm;)k(}pfcC_G!_#2nzsKY9o(O_hXDdvT>f0OfjtA-!G+Usx77GDqvs2bC9Qh4E;k z?uYPZakafP{9e=xxM;E{RHQnxsAF) zI$}2Hm_4^AznJDJa>@ED&Uz_T4%s zyaPQl7MxohxUNWycd0r=G~A|<3!pDfp=ulo^VXS6PqRqNtC$`NZ3~nBoM1ZUqA=Ku zx3g*g+HAZqRntK9UDbEP;LR}vM4)(o!C!L6Ishf>+M4&MBxDFoNmdPJZEv3Q(>oy= z>kzG{l3}?{JKZwD7il96g-gUi+2D_j^k`#r7RFXrZx8x5Xaz z-nB>Zsx#Ih8Z#$NNop=b4FP`D0=?}jTK47-?EZ;YPEtKX=r$ELFAKG zLJIfqW5S2KBOkQ1?a8}qT2Uq@-q<&HA#P)54=fWHH@p(rrK;t9dJ}S#$-ah$b#JmW z%h`6+uok8x`-YL`jVabYPd&Y}`LV!DygL;4C=}PGi76w(&gV@AV6c8(E~wP3#P;!^ikbibEa;NJzGeoWOVn!?J~k`j(<0 zcO0+ZX^OSv>uM!h+9y!T5=46CbcyY2y!*(8G`>v%hf>d14ae>4xQ+NdQ%)aA>K5LH zvvA{+1hVcl8c+?^FF4oA(wwy+QI#tkT-`-o3xdv`+@e*gTxlE8(xTJ*^Q=rO%FAH| zI;aSp4ImnwUe@m@?2+nLd+K$pdOUINZ|Le%4GdrLfQcK%nra%H`r2PeY~-xl5vxXo zxzrjy5)dX`T**ukG?#>6&UUSKvd=2c@dS}K@l%x2Raa`fNv))?nW?!Apl&7`r|oG| zu59l#`(4RlA#GT4Mj)!r@3*OL@H++h$8OG${Oaz`Z_s{9;kt<3PTsH^VIv?<$+R;}{46geUO3t>0-D=rQ9?=3Rk#Wb6_vM;`B z724&Tu7}q#R%LsI9j%$_={Cbju^Xe3wydJGbiAouBit%Kd1zhDZQ2nAiYG5PWnn{hr#U5C8jyvsa1wsCe@ z_L0`T#%e6)-E1u3l9_g+uB3G~)mI$I4;WcRNFs*(747Dvg`MQIrNESohpdjDEpQaB z(NS{qP@)XkKL{BzKIDP(N9bKexS=NbK*eclR%}N zA#wLXp=7kFMvJV>6HL<-IA;%hoYzNoDdoI5wRAyQP504{c+DWv<@PrL_WM;bxb(hO za_C0hU18-=%^EAJ6wNIpEiu?x&Pa5NHqz|H$P9&NIS?g24)EBOG>N3Q;hHT4Sq@kQps!^qj*GfpQsKG54bB2 z^ZnT^Z0*rh)tOIkR!noRq1U8VaQ>|)>pw?y_wpw!>jBeTo#zOX3Q!W$o?-dAyH{`b zjS{TUE+Oja9U=6@)#dtIu_voI+k_;Sja+Wp3FXU;9lomMb7n@8YnIpSjtc85ed9mX78!QN_TJ)*3)VS9`%I5U1?J4 z>eGZZZbf$UC)18|@3|tQefLeTQktqm$tqfL8U#{D!&C!?4`mDZ2x$ZajFuC8(Dn&aZAXM z(e#D{4JumMemCVe1o|s&(@u1>>+}h%SwBAi5@0l;XxW@O)n|f);))#LDQN?3@?1W# z?74@IRda)^hp4F}?x0?fR^}TEJ{-N+r7NEl=~f|c?yx}Gn{(HFY@&Kgl#@91w0nfs zwuLdrN$Ke6mHFyAj@vND03x1Q?8<3~-;{!1g?1~q@cNowmWB%+6GRjjPP$NUJqp#}&M8KO?<(4Oy<_hQU-+q@Mw_TD2VH7Y zLk=rZv-><5AHZ32+mjo370(u}QGbB4wjK1d!qL~NT+&@?>u;2+!sW$L%^<-%w=LvH z0^L`3a)z8u@7QU4wAhQxQ{%YoS~BnWhTc7Liiw#Is|4y{pyI z@G;W2kVkQ3Dy5B(wd~s4pC49hZQaAD(ILDn9TpemxFDfDzq{K_(i?XSEL=&R#>{(< zW-PsSm3V<#?EzFZ{t6sHHN4Yoe*$_Piy)&}Ts>wwtl7}aplR&eGJtL8pk|kn)P-DZ<2Ni3+bIWh}(#8`@|Q-IoClowmpInI6-Av z8n>n6)iW)}v3$mE(}poV;~nD-K|ZQ*Jn@I8X%#wP#HM{at;f*MKtcXNLw}d5x|1wW zQ4n3w=!Cio8BvR&3AP;61ypI3G>*8)M#AZyuFdM{obtW;P3=iwhHQJAn-_kYVqlII zBLUhWO@SH`7HOAv#BAhdb$Vv|*sJSOP=-TP`%6%{g`zn`fS+iGcy1kjZK&J~*ZM)Z z8DW>O&SB@ty1j?Z>;}{@u_L|FtqSd&TZ94rLQxRce&BenZCH#$GgZ^OfS_PJg!~FD zC|xP}gQ6U=<=zIn<&BhtBrV5I<*t+vg~D>rmq1GoYJ#(Zl(WGb^njU3r8h&0MDe@^$r}REYvkTeBB7BQw8z2aCaG05d z5Jy|KJS2;)=R8;Kq}hI4I8W^-(pPK2b+(Sy_l_)Xd_GP>2Bx}~0+b-xrx@ti8BgGf zq@^1o^Hzw-DT$rey*u8uArs4*&rb;Vf)Nlmd4Mu@bgT15ZsFFjmQrO=jI$L~ zw1Zqrj=Ddg?S6Cc-6i97H4OnTYI+65LN zj+8dHW;hym*J%qV)QE(o040m9XJM7r2)L{tF&oLF9-`so5Hpu#!|Kz_im4bGVc5!d zMjSDeiWhgZ_?mM80M5AB+EP}vY5^w~;qWYfT0t;-`vrYL;6ps3Dr?N}QiOs0r6}pD8^LhW$ue;E8 zfN0cOHqN&plP{xnMaGfvxATeBgbmw-oZRtEHRX0GHwB!ULRrXDzP%2_`H}BKMRt;M zV>dqgIC^K=Q7K7|t<8Nq!lNaO(^45$hrnjtO@0NcLB_5qM@u`OJyH1X(EEC2Mm)M} z&7=b|t;FJ{3`9LLceQaws43|ET*iY5TPSXE0+*dH2 zC0&M-;7NAq?KJ-7(e3*DUDp^QL|_@oSJIG!DgW{lcK<5|{e-Sa-fI!@=thI2z+f1P zUj_9tGLkklV7LDzc?i2nE@?Y~i4kQYkrI`{rG<*YDv|ln#G{jsijLjul7E-=((RZ;Mu)nQI6d_Yd#b0>be7aFB?k%fBR!YcChG zG`x3h$7<|L50fJdPt>$c09m!QFRB*En|F-RnyuP5LrQnUcwpQ)$($wmO zngJ@>GnyjrP|*-;yaaWN@Jq7#OS0JIsX70Ktv%-)DufnRS)foKzR>fzuZ%4>jcJ&! zAGL9LEdOi?Du|Ipm#OL4)ddn)kL_x9Yu{gs`T`0dw6aH2>cZL2LMK766W49!f%Id4jYXRS}cS5sdw<^Eq;4(L7{{OdZ(wFlCug!#BEMnPn2S`Te{ zz2B7TOZrjaQ?_dkp=1<=W2qucIr%S{lR~s_j3mgxz6{E6S_24WS0kB)V}|p1rUUkM z(U)12**3IERG8{2GUIjWs~h)zMxj9IVX?eYsnv;045|Wfy~S0e&XDjwR{We)gk!&LPN0Sk7&FpWA-zfxa<*b-bV6qGyuHzowgjfv;#3R zMUh_Q$md#{g^-;x^lQQ$e3yxQ_YHgpk{m;lsTx%Gba85s; z_8IHH`B}04inq%+w-XBMV! zNnZSfna?ZiWpRzd(FD~=djSb@gN9mznQ1dvE>okbjVPUtIJH#^z61=F-#v@Q5aGnL z0@dO6LpmzBJR%X{T;qep09U^M_?g#@-^Y3FrF|y0`0SrP^BzNZ&;U~Hw0mg9Z)kG1 zWpr4$j_#vr#CApj`r4Tbv?b0jF7X^sn~t3^-MP0Gh(K*`APO?6h%_a5mUhr~%?|>I zYN;|t>DVxF1X1k43d8|HT_Ua`8g#mPiU#1^7~J2(<$IP!$91GlBLAHLO$dhIqy#mK zoV)RfCTZ{{E2#hBpwKw&8B^Lb=@mMNWI$69e2b^^kR3wyxo8t?ICU?h<-)UKkTw~3 zBUA37RB8&iJ_6kk@~o(!eG&dkGt0S~J+%E0$V^Qj*G!`jEXfxN9qKX%r#YxK1bj-< zG9}2^;hT8}MX%84jb7w+Xv`lz&Kf<0E88KH zhc6#qdniOB`spHhrvQj!IH~R$0NR;G0syB_Z2(y2lR!lW@G7qu>DX}kVCMP~)R9xj$f09Ye_+ZbDMq7>OHsETAXeBk9=%%f zVmVJ|(hAgdp_%cCXUUvCc^UgN$SHDFBp9 zTvGv7WFn1MO`t6x7#!S9`zdx=GrsF)ngrxLV5JiS&m8ueQl7q>HEF)oW|TYq>J9lY^X205X+~t=+#-8Hh5>AJ|U%ulJP)* zfWcJ`5Ehtnim4O#A||-_AUHyZfR`80n_3tvUIM{_3EU61Nb50btIz^3xSq#Mt7$JS zGYV3 zS_SvO{Gg zD1D}!g8ESz8ZyM(BDu(W=2o!(eUkixenIbSq#e0nXGFJzYZAboX$?j{iLn`-odlW` z5P!j7fgdrG=K*MOf&q<=T^vXY9fNy<|E%y|gRqU_2AyMmqV?6;e_*u@1T-b>1Mt`N zaPJ{Q>r$=ZE119N-v2)>w>}QMn(J3MYk;s`+w4fLm!4J|F6R3e~om>`{>sN z{&lwcn@a{4<(OKi>Fk#A0Dx`C)NRjjhLO+({40NQyR;XH{p3het(ikAIs*OgiJpFs z=^ztjzU#s8k){x4N5?_+jbHq+BvAv(`j7bTYK4t3EzJ}Ulx)GW~ ziox5pf8Sr%ac~K$gb`J_E;$o_avLw|`wSz_KIpny^l`y*)HWV^G`3{r#!o9P15mD= zjQBhswX5Op^=v2$Cq@*{rxA2f(}+LL(z=x^u@=4cNQ+5 zN&_wb(92yIG>4Z+!0CGUoilW@cf%L7!NCKHq3JwWOFKT(2B99&j>a?&qY7weE*gVe z^%3Vz-+2B(B*dmubS(h*W~N&_bZ&4(ffq;>Pu2fQXvELfy(kCfxQppiq-z{G-~V4E zp#Q4YKnYv;azCw8eo=rR*yc2x(*oCRq010-Ha@NH3|+hvMYn=Yv$U56{yTzwKR}%^ zUtqsY#!%xxE5s{VL{quWf%Zp?$LD27DJ3)7OD^s~tOIDq<@&t&0V?@$?dUKlUfo7? z9TGXMJDQUwSA)JUOWd0{K|>%U*DIJ{@W-oT}n- zHZfhGUJI&~=Pu+gspCl0! zceokF9lGYyk%M05V?ue_EfqSGA&@|Br_j+Das~gTLBJJEx50_vUySqrfdzbd=)VCh zFcQiIql`Nk3}Xu~0r3M!p#Q3mr!ceP&{2SP6NNthpeLi=MN=RsJor})0x4AFEpor; zHR@3xNVh7ZmyApJaBY9W91O+r#UXdinvAi^ zJj5*Pm^rmiL(@D`UKy;)9eE!|8go1hIXh2-R&cs^>ZG3|`ftBwm%@zLF7TbSpF1fa z=^(vQ&_nFRFw{os#33&8pc{=X(85`N4w-fv#c#{ZrDQCw+XlWa~ymHonagslpB zTlUEJZl11EpH+5aT{t(9l*7t|?q=YhSJQ)H#2szDg$-{FQs}A5b|mQeY-7XC3FI|r z?3>v~J!z&#UQ-RKbo zYWE>I(^R3H#-aqF@bR=xX*maXE*@sVAy_&11s6XTn6pYG%onqCWVNO~CE!|wyXp(w zUB!Q3nuG=p2|)nEwFFw zEHV=$A1h$xRNnaJfjsr~{@h?bG}?`GXLPUkDD*lahBL@?IGnA>sa@(vn3D5RVk<#+y)~;@FduAE9Rl>{& zIB-1rg(bYWcx9%diU09XeVLhku>~ zz{-3Y(&P9{QZzn)T)x1i=eW`2y+?NbJw@9jN?2du!l)Kg`^t^ZA-n)bOhQm zr#0$32_#XQQ1G4`G>$o; z5O4?x2xt+QXAs7qC2t?4w#*1X`N7HjL$kTcQ2ww1>=tN2REmV3fEIWvdav$?pF zI^4E!p2=QQ?%ShS3!7@cM@TKoPwF>7qu{l4V9 z(xHaT55dAY!IZn>(d@D0*cU<)RZ?B;MGDh@10Y@gGmyHOV{6pNM2+4HT@#PF;+`qh z1rM8J`^uxiwUVJe7xIx%&Gf%HqRww7Dpo~|Z_u39(NWKynozwR@H_Nt-ojc&n5%c% zvDPez=nBmr_rI(dKs{$(KJJETU! zARaLDjU?vK_u1hCRMqzT)0?47DxNn#+{PvBQ&r|ZNImv8D$uzsR(l2&dmED2B%E!i5q*f=t4{i9GRbuvP`z1J@Dx`tT|F3jKsES`< zZPOh&?Q)plBBE>1*l7Q?t>=Y~m`3&%OA_WVA4gQid%N+juQ^Ui?X{sVh3|~S5+X~j z`HnmSZ^HU1gn+U{phNT<3ub|QsW9dG*9}vOa*jpz?k}e}bQN3V?DWGV?!^ZSgnr_z zBJ})w23{t}0w9Fl+_1ZTIcS@N4Y=@JJ0IE=~U4gmx-A=|MA))vc{X=yf3J zaI}#{xH>cIVmA3a-_WMc*G@-=Uo;<(mX)YDQ+kB66HVS(Qymz!vLq95FmuZVGUs}5 zU1dWj^*M3rk#@7i8R3)5?PT@^O4nE|es45!UAf_g8r{QUyD~mFMZe&xF3;O09y&y* z{jQ$&DNA$nzBt(!5h+iqqXX-ghjyL(>ou8DBx*6ZgF^V6__xuKlU$sw@{Wpn!jd=I z$qvRs#p7eAqYmE;+PZB()LxB$#|u?MCob~3Q*6nnG{jYd%BsN;FZA1s@5p3PfN(RbJFMAi{NhL@dDH=n zXEpn_ipmKLtL9EYHkxw=eiiyBLf|gVy1V?ISNd+6U7}nDS2Hd(8Rs0Z)BjYfVvKfh z{#5(d;w_u`FD%Bcsvv*oXt<|dNr@{FS=KeS+1Tv3bc?wee3MgAkv z(abx;-MCzB8oUHxiRR8Bgap^!6J+?`gL@w{q#B?s5Rx(US-y{FlZem-37lHDkU&7@ z0YT+B;Q&p8Nusr5g`$F1ZYGcB&UJu>a2no+GY$<~&9NJ@FuV$F<}*(wydMJ@Yi6i5 z>G#-{0|~RfMMyCPC~$_n^EXNs&k@IO@jFc{tP(kZ4q6D^$ZhQ>q??&!6_;F4QR+dP z=n+kMzdgxLce0UAKD8ByS&DA0aIav)Nv-zg?kg*WBQbo1_P`G1$v4ZC4NUIAw{Q60 zW&q{g5Lo45eB0t<^LL%@>>0__9&jNA#MQFPl20}9F=xKlNdTHKae3YXKyOjtG#Tgv6n!3%;eYGQoJ4Pke~^l==JiQ4R42?I z*3WVb?#l6_9PC(S?ADN;e2WxGdDeG@LZ}^4_n*=o&^dA8@ZnzR+?T+#K+Mf7LE7oz z6>fxFl()oB`;p_$P#3ZBEBA$MJYvSxXN~g%wfwbhvb4gTz06`MwH_pe7g3K&GAG+q za;wWHu+?h+7T*SERTG@|zGvr%s~zG{>rHTy(lr%$=EqZ19C>6*C)(KNYwJZY**fC4 z=GLoyXDX@*%M3a}i1i1N>TBuSxey894qDp}$F1DfnMGvDsVP&f>~BM=l+e7*jo^My zT(E#&T1)rMF@qqj-E}x>EXl??veTE-@c*Oky~3JqoeO| zC@5%Xp<3uA^p39*P$HnBpwdJHqy$0@9SsBo1XQFqlSnV2_i{Fg1x5eg)%QH-T;(Fa z-JPACzB9A`o^vYNU0GFVAo+{%(rd$970J?qLv|eJ-=$8A3#3&oKnx_cgsgk{(_O@5 zjr=tUi+S3v@=u=dW-5Gy?D(m3m^23f%x$8j=?e|i8;fvw3M^O{JaFUa^6J3%o7fq% z>-9xk_e>2H4kmo*yftRzT5=-QYQgC#LP5_xF2p_P25w7+!k^YzBl21G6L@=<)Y6-d z$I=Jl+}72^mbQ!;x5sp>7iv8zbvA3pZX&lyjdYRYIh#xG*8URuxh(n@V`zer$T!eC z$oD_pU65GhPIaQU#0C)#M=d;6lpZxUE9#g_(;Dh*TO6PDH#IS(noz*6IW|M zwp*84>_uHG#J3p5BxrxVmqzca{~XyLf7lAm z6u03Is*7#Ui`LF4L=|-Mok4x+&oxF|{V>GO79k{SP@qYe%yWIAcYRUl{UKOQP_WV5 zpQj&~NmK@5V_;#cJHbndI&6rE@W<63u;|Gqin+b|4IP_(kCM-|YuiV5S}n1$wQHWh zSif_qHC>u8YHOwmCM!hfy1MooO=YO%r;K|3&Pv`x5hU=e%EMeRJ2iM0md5IyclCp7 z?-YAV#QfAalI+*T+0^B^OZ03G#sk;lVLP(E%gE66)miz}XS}Ih_dWUx6DxH@RYmmX zqeR+`38Fa#1Lwr@;-H^da1SS!=?jOi$Z4%b-?ukLCs90ZxhJ4q+u2gSysNQ-(w>aI z$@}VbP|1;Ch{!)Pl^+D2L`4mUYPY>=xF=r@Dv)q9$%xmIqB%~Tt2>hka(y-4`CGw;mL<;jpeY;gNc2VF^<>@N?OQy>DQm`x~Fv6OmM!{OPVlpeJRm? z?b(T|Av65LLdyj)mveZ8HW{<`GVy>|;5c%k{fs53(^~ zoEF4=c2i`9z+jC+ob;G3&oyj-T6*NRQa#}mvw)CE*6BH|M9_(+T$1~ zB6!hb+jXDM%Wa6CL4C4OZ^t3ATZex`4)b~z1fc(a9a?k!?tg!i`OlEqAENHkP{Z@<@R(QE-!F%j zvDw>^--IZm{sue<@6WuQC|*(K7QO5E*teLY`lCCdf12A zTWOgZ{}ppyW-0u-g8cuU6cA!w3V869UVhIgeOL}6{aU)AjD9Ws{qkqoA|P_pOwzad zamn!;7RP=>Usk}oEAm(86MDO>Wm{op608K`CncaQc3sQ&$H-aueiQc7Ea_b`qAe-xFXuR0Bci|lfqUDUH3}w<&!CznHrQ2$;jWkW z&E8v=^ZY+U-f_Hn~e6N_;Qtm`N)$ao+F7BAKu^lpnv1+)e;fPn2VI3*N*2qu7#`5 z$QIpWVTo0wza0_)`i?o#EeQCH`7`+aAlpJzR-1ePXH$})kQ)sZU#-ngJ#^;)Ju##A z6GX%mvo99`JO4pUJ-s{ZH7Xr z>RM%bhDfblma~Y81K=SYZT_}Tz%s%;9NGTvF+KNWt?aaVSkGhRdBUG?uzWVb*aM*{ ztqx+f)dvUk7uvk!7o%B`0HB!Pad1X?X_mHlEew3M6a!W8e+6Bf zl}`C9KF)w{ARj<7iGv@~c^^R@yl1er7SbC43@#JQgUAiRC5Ep+{N>gu_jtD|QXqsHVS!K9;6O6Qqp( z!2OT;RsKpI!loEe;Ev|h{*4yCZ|?jMcnV8b>54dhjL#-KZ6S2AwUebk?*JP8z}8MsQnpY$d#e+Ci?%5 zztl5``d;1t6Bkh`pCH!XCXIvsNnJN+hJ0SFD;fEIaE8J&5Hx8G7jDO@>jG2-{~@1# z5TLYfK2O~LK$mSCRPQrH5h$hQg5u}=^zO|6WId<9O28in9gYOAlI#*&c}jVslv`o9 z#XdvI|M`hD_+-R?d+7!E{(mkqP*NMzS(GQW`TFaB^61qDNM*+>A{wQ(wP8K_lRJru z=Y-h{4|()nL8ZkUei}j%Z~QlhXXHT<5u~%Bl!`rw7b(uWf+=^BNd+PjuUp2(q|tZx zAnt;I;J;XWbN3$kJe@^BiA7&>o$cMwi34a)$|pZNS!dTHGN)>fjQU-!-2LmFxfP_mfLQa?aH*{V^{N<=( zO5H-A6kWhdqa8zobQ`>{L*v}GMi_h&wHO|?#h`h!Oc@hA3ty@$dr)6A52Y&~@cIc; zo0*vE!!39dVlU!0`U~ef(YI`Jrkq8Yv!eb0%lT%TLR1>ntpF z=?{s`8Aon~ov%t-?*SOHyj+To`#H7ghu9SWL=1uyCgF6x{oSoaCyPkACuf^C!sl zJ7zmI8(p4P9CyI%W9;xH*-<=@asl^nHxLO_yX`DHO@AnknBBNxO7M=&l3#jqFUrl@I~#*j7s^>M*=n%KET<Q!9V`anzwprpQLWT})BGPs zHzYpqcwDm0Gl2DAw<3KR#VXRi5Z0ml$oF#E{9gp*B(0~@H?s8o)TuFTIGcvIL;&)r z3+1X0(a^89KyzpTJ+f_G~--+jMQ;^;gH4JZ^F1y+1JaZH+ zewuuk)T_to#-W-v5hSH6q(aGkz_F*G_J{6{q1dUz*WLzYjlZb&!5TJI;s+nA%B$Q* zVR1(|+10{^&ZbW7jYC9~t_ulPjx2#a99i|+iNV0DzP|WH9UeGQcA-|@>U!|*mX3^4 zSoLM8bASp-?R-$x(Zh)B4AkV_!rR{g2nR)W4HthHg~EfD7?Q)H_#sJwt4I?<{}Vd|HHIBjfasH+?COu`y*s?gXaBQy=Dw) z{4H{D7)p~judH%uqsYr3jbY~-@;=3xGpnjWoO=i~vC6*n3u|6*i=CR;c^73p>`|!R z;jWSLpcih3Q(PveUvWV<7yxaK7myR?T&>pR zEg?_*%qL0ZY9nh$0;;Y!e~hi=mQ|MIt6LYHJ*tL6N$I+&#-W>H@JCfbj_Fscp*&v> zPnVHAk9o0^7NW6B!u4O|%YwvT;Bs`z{bRCi1rW|$HJe0sOU`e(`=z^a{M&x-BGQPf zpF6l)MBDXGuNNpnqT6D!v+-lBQfpQcjyKX-43B?&tFQ7Ze{0IrG&*%5F)oU>tASk^ zeyl~nO5>jL1>X$fAyR*~%V@@7Z^LnvrKliR3Km;a6KQoL0e2*=m?)*&7Y*eyv_Z$A zwg^WaY#rP$bGFLP`=q1$?9s*H2-(fn3%9E975KX^!#WOQFgR&Z{qtS{5-G=~yyE5! zW$qW5s}fsdU+X6tg*u=|+WEe20vh5Eic!-x%wDpxP=h<%Oc;LkUbf8q?xcc5i{!(^ z##lq&PiF^m=B=-B-y~!NdsLisD!Nr-Am!nNbCg9)Iud3+G-r=V!FWB#zBLJ=uNfgV zx#E;PGKiz5$J*OQF__al^%k;<=nq-zdogC(9TgQY-HWnpigQ+4T@g==VGZ)JgLY3U zhNoUnugX8{-bRLS9v!12lzxIGlAsnFN+NcZ5%f1GZcW?wEC???N5!RgcA*$(;gIW zk&U~IYPCsxi?PX=D-t7bq}T_EYn47%=()g?()Zdmz-lx^lsEa4|30?L*PI8j zAw{mLhAtDC^+MW$wsloj>+w(BIHE`Db$YPaIAP>f{Zk|B%>!NEn^eJsAFQ&W#&LUk z>r4@j_Z$zd|Ezme=K66foBlYI)YqTDHR!|Vzm@jeu4mDV`za?LYa0%}B{ny2D_yi~ zNX0vMzb6{q^_Yp>rO9m!F*G2TTG8q!&j3JhiuB6h2Z=5yNp5wTospo-{zy=R} z39h{R#I55*K;^#6)jlx%421Zm)Jz`^eKdG0to^dgjjcpo2m4!6^*U?TakvDGUa8Z? zXll-jDm+VWJ>UHjP~^=~Chh=uYgx>GN!$#irX(JpoaOyU&tdBAh68VAD7NN?#v%Am z&5c<`SKDp{YFC%VOI$hy-y~@|FH=7D9EReT@N!XcQf$Q>+~qh9dvn_&@> zlskM!-ShSlU<-bCKtk6b5u-@K8PbV6l`49KZS zHs_-BC$7}=Evj>!E2&!_^Dwt>=hY}pje$J17Zsz~E}!1chUj}I&GI|3-gDz1Y}9M% z9mNNyyw)rPNPK73<7{|R@8*EsWg-0p<*=yAYweS#YPNd9vi2EG8-8&rD)z+rEgWEq zNVh}A=vT^hs#d>22m0w6kX`XjX4gk{I%J+MulRgbmS%q`)s?1^Ws_4l5yr`ziS!5- zF@P^%KHc3c3(rXQiO@*WE_~H#hxB zM(1^%k7v>Th9_^eiERvwm){??a4o>9F?-O?J5j2%*TG*tZjBet?^?P1+Z`VvQ7WB5 zJS>_ZOu>kIoKe1y8#Xc^Yp2Fr;4=~{Ft!;~>86Of7}0SH;rS=)>~Q+R`klzN5EmSO z^I+~*-BhzH7LP{x%77nvmSnaw&G~lrl;}BI2~F$cJo>t+?qkp=$~%V;+v-WZ4u&oZ zpCIz@DpDaj1djj;;WdJW8PcfT&9RUvsBvJCu2Z~EZ06g;0+by)BWxoalSLf8;L=}y zOZt;{W37FEi_N6Pm{gw8V0(&1UF8I3m2v;lh<5cg)$^NWQ^~wM()!m@Z=pSp3_iZw zS!(W=lyjZ?ra78MZcFmq5E23x(tleDKdjPfDN}bW30vDw9Q7&DN=lfIvUzXkEvGZ3 z^VvRmItmcji*F23d^v6?Xba1RU*gRSE`+Ue4Q?7bNt#Z!;K4m#$~B8A~)QHKb^!XLq3Dk9qr3?W4|P!WR`MjA-Qhh`^Ci=Muj2 znuTbV)8{SyUhC*6UaDl1=x28c7{*?9dTiJ8Qlg08cTpQ^{@9C7>k8qqsxmA<@2;T- zhwcYI)*;uwCF0mQOED5*@SP3EyaZYbCKV2!PK+`ppE9R52iIJ|-RZ2f%0Hp&!frnJ zHXR>3vZ>P#ZyKg2sAfogB=w%uetI_FS(+4z-HOvh9lsqXnK-pwuc!qDyR z4YZet1q@%^r2bykC2fpE{CA?MJ;cibxNj9bR(3|Y9A{&0K(1d;_Wn}3)x9%w9na9d z&)mU+3OjNi$af+ds-G7Wd7Logmdd_fX82Ki5X;Wd>O$+1Fl>?1jU@)uxED0KIm^SW zoo8fV0FW>n7LlWtZ5jD^bKP*CK^`uVx|HM74T4EZ3v@#WM433k^Q;RH#w@oTewsr9 z1yCd_mzCufq-4afmRvsCL6avg6k~Whq&q(Xp?@j$t2l3aEAQUyoOfBG6}U;EW}~x% z6JveemQ?%mn5t{zqA<6PY@W(;!Bb;FQ5x*m2@)2h<1|)9@l`-&@8;|y@p%jcMK#Sp9dJ|oxC`l zWQt+BVe!)+K@!``;Gbg8>=Q&EbJ^)RHG$_zou%p*(_Lg(eQEFRAy}xceRk)!#EHCc znWn0PH`i6I{-*~2~Ln(s*n>a>@beR`cgR9QX0UAL`snCK@}&|pRthdT1cBlIh@ zg+0=O>xp(l?l~zD5$zmNBWkGBWK$QzhTOxQ$B9}U4)TvgZ`k(3z76Xq;t#4sLmwB_ zlJY=MiWCPLX|QDeV%|`sCi}*`9`(A-3Tv8S4p~0_x*N=?QfLlbhYn{Q~?t^Ts2#LIO;cl+f<~hqY)r8-;jVNT?FBSKdejb zOrrmPn7YlxgaS&bj$<-vyU+8d(Vv62r%;q3Ygi-eA8-gEhuW>FT$d`%N&dh=V};B4 z_n%006)^0GDidCs631a;97Lv+j*HT|%1bX70v(@XuB~UH*D!RP7a9sw&RNS=-mg0* z(==M0PgDJ#Ga@LzIbxaM-8BPpv{7#EsT{M&?2%wPN;I~di;;!vSwzpGIcyRwi6TU zU>UjZTN^I&>hX4)Ig1gaUQaZ2vp@V6BV_fP8}lU@3Zfr|nmP`5Z7+qH3QxQ{y9d5x z2SsRPdoC2_NyNu%=C~F4>l_tMxn_lW@Bm(uytzzZe_cS5-i4lrq~wt8iH6Q3;$VnQ zz*Jt#<*yV506q@5$bFmg@?FreqcN0YMNZzM zpkp(?vFcknqav`o5SrX7rv8x85R#cX7b=F+BF3~^11UeQM5U^IQ^l9$bqm#oe#oG zG{JK|m{1YI2WLt1yPcEuG2kR}9eicVK2LHJ@zJ?$-v0xJ<}9LUg7BuuM5`@(^`6t% zxA5+YbIO%kC#jb#ip(i9#5YeW6?JY*L&qW;4d#M(3hQ0rw{S<-K2^{f@AGNdeQPkr zTV~2)%oa1UuH^Y1lnfepg{8!I_4QY3M-{4a??yylE+J0T1{XWm^{wN$TkM4+uA%;g zU$byF5_o*>spp+$iq8&g;0UOd)(yMEBcj25q;{dw?y)3BIE3MoD%v!UEPPH>x>y0*=R9Q$p z0c5<4C|!oh{~m2HYlDce0*$g^&El*z1{U{KFZSK#IV&HsV@q^Rg%N=O&B|-l^T&_e zO4xKIGkpo4T=0Zjk!JNES}52uzjd@bL-unTClc@?SiGg5-7R1YRvF?sR-T@=`ka#) zD-^MN(HzRl9rp=R9{GEaB-4s+ORFaGc=K%IbFa5_wh3z&mRU-ttPC!d>1!1nVYF;GAcFZ`$E9N^||9MXmAGM&wp2mr<%n`eE=#oSj3g&%=#FBrmi zA4Xi_R?+dCn3ftKPcNE|O;`Rp1#mmj?$PgXd<{#|FUM_A$8eWsHYe=NO&;vjDl1_b zDHR$L)n|pybHmz!Yp0=Qm>ysaeCE*G?!zilD%M`Uz{+7QHO){M{nPE4#EG_B8VOYEZ)kWa zJzihrAf2SaepDy7N0A)X8Z~XTMLM8XfPFC&5{Rue!CZV>t_5x>RX4Jj zy#DErvYTL1w^~|WzLm2Z*5igIxZHHM2-C|^pSmsj(V|zJk2EvO){VjOloeuXP5SvX zlav;>X%(TRF8eo(ncwuF!HoY)(RgpG6sDC>t#K4*sjb(j4dcr248UiPu%-=u)lVGG zmhwk#m~g+qa&q6#ppyN0cs@69c||Znpxo!cA!($~m>G3E#yGP(B6v+zJ(A?8<#rG) zbNYC-g@A}BxA8GxE*G7tw??u5m*k}@jH4CgL&v)`^ZvH-^ewEWb5F`O=U4w&`JNqm zNbS8X));M7iPi0<`skMSD#vApbEDhymSgLceGZtncptK2w=f(e-IytQ*E}k$L+5_j&E9;QvFUH)N>sT! zz#SwG(IwxPF@aSq{yz<-&!_0I)dxr5H#5vltOFg{@oZ;P#N`IGiiL%V=}9Squ#)G( zBy53}m-U0aIud8!gwG|JHD}B0?U&>~{;{;Z@{uEtB3wwTzt)D{D4=f6a-vZt*DlTqcCW4^aN)l<6ngbM=3OV%W_wHw*69 z_A6v2CZ1584EN(Xw?yjwTpL@;f}vCUfnRAV><7inm*ESJu-ZnHSvj zs%4>G-D0SqvAgS?V6cUSI{QM>pm|D#LuY;9vGwbdgRNWioEq(fWaD>hbBBGYBi{AI z5wFwD#xHQW16F^;``73gW<#w(_y!q{8&gwtyq2?PlbViKD>n2}9Kpv8;T7LtVBmm>4z8P z7CoAE%nxh^285#X2+V)7F8xs4p|Z|PYKSk3mz$TgGiO&-|M&Bz0t6;Mp8px@m&hP$ zIKi-?dC6%Sn}i+&K|3A#xFl68@>&X=ZPnJs6vPixEZWLQ(Rwn++b14&Vq0X!#;XgL zybBK7esML0biZ%6%tex29tqyZz1)Hum-EgUf z7bY@t>(V*FV`keV;d{#p_@*{*Z+yY{nuW}b`IK7qUTt@7IHVK+ZIcOKN;s)eIHngxg z6|qTJXM4*hJuxV>PoX{LP@SD2f$NL&gbDR*TfE1e>@;NSYr*7#Eg|a*GKpiM=1yDz zi3k!2WBgQ_y+bBv6Fx=7FDg25MwfQIJNH=4)}X=JJC{he%gr_vJ{dKKYSj6KegHjl z|G4~!A89|RF+MQv$<68r#F)}-6Cs7y*Sw)Erzmf}AOV$Z<94dRc*Jq1Q=n?7U0sQc z3#SOHvpb4CVc^uPo%h&dr_JJi_0{sHtDJ_j{fTNHF19v{-FW zttXe?^7Y&swxx4nEu%S0#yrK0iVD9$2d&_cW zzDHU>S000*p(mTkdx`>9e39!cNzXw8UsP=O=cq073XOs?gLRBdEe19B6uaF#E%iP; zn}hqo@*FMtz1}#vf&QOTE9<6n^t`qo2|x5((Ah0KM0qztY0J`6>$Yi0%D9g;rYxU) z?)@f}KRPIW?1)Uy49+eu#x@Wjx9;Z2=h@mZ}>2h@_wbH zY+xz4x0T$0K1K>3-VnFv$ZBTf^LsW8UBU-W9^#i?_x#fqdV<}rB(Jr%Ju~DsHVM3c z;JadH`;gw_K@Y?4d~p5^so;v}q-t}7_Wej{O$N_=H zRbSKgy$)vpw0uPdl?FG5{o@a9#vkX-E`yOd!sz_@;|!GeEkg-|x;Z~HfQw!Z96j5K zR(R87EsI@<`?fUohdW@)=!cQ%R~&hX<%6Ek#yQ4l>hjU$?RVut!T~s;Ew7msXk;vTrUFj6sL zg81+^5P!;I%_a}Vw)vNDdy;o@qzt%XEO2QCu-BJQ%#ft;)I(pGh&=x%nbm*C$)jZp zGKegx3C&pQz_O(_H~>MXx&5v}8v|WWmgAVt00z2afB8c|n|w%e(<7GKYE;I*VZRLn z|8Dj=w(2A&mN>cc?Fsh7xPs z5~MJ}I4-q6kIQCv9|LXs4lF+phQ^JWWDG&B9Ac#2D8pFq>2K=+4nbja8PZB~dDczU!f<_#0fY7m7-X{;WJFy#WFHb!$3SKNa!k{i;OmR4OnUxp z5TL!`h7K{6)hzW_nWTAeIVu>W*|;U0NMcM{^4p}+I{7eq2IT6uSyhaE-&?-#w5hmm zaG?s&V$s4ppXbXq&!964tZoz&l`H=GqAAb7@n8O6%90m?7AzWQOZ z*ueE5k(EJu4t|IDS%J#RV4j_SE|lxhaJn18Xtl*)eZgE{^`;QtA6B(WsxW65(6-F& z>@^JqjM-t!j#{k@Jg6ffI&gWw3d^ zT0ia%%p4f#6=9+my!EE4B6r^<#m$xfNbGkr`Pu_E0O6PK5XM90+{R6lTo-bt-pi^! z19mJub;Z7CU@~uDOkc2{NGaOYZR%KdEyaha$V`QB!xON`wt(X))_ii)f^q^Me3|IV z9cz%R5UKw7h-bY%J5w`%J`6f;iUeLSQywlah^gU4?(WD7@thpR)fBq#h~()4neTL~ zs%_@YeEX(wmn~j|>|0C*1h!MhYi}Y4P1bpOc%-SRoj5fxFfis@HnpwvaQocnrGDQX zOy}XGQ@e1H5!>4RwPFa8o)Y7$wXvqM}7L-PJS z#ow4Iqd-?od+lEJ0aO$;WDx&jnm#)-poLO7+tYQ!1LfT8Coi>*UhW9Gl?QO z6Drr9U`Ic|exKp#^l5P<@22gDC~JBeC|&!Z@LUF<%m~!7`~XHGi)ie0Juq+l6YR8? zyxYvMqq{K7O5QKgKn>vKyZ7-7)VP2w{2#lu1YpsO9^@VEc}P@FkxyZFod5Ds;UMtV z+{)q@V)m^JqqbxhQwzrQn_={?VOtkq=&G-UGo!`RFJ86imr9841_2DW{O>NuA1IZ@ z4TrLd73*@V3T)!_+NQ5@!-PKw+;HTn1G1_H$?}TuAJdzgVrt+5RyShs!=$e4GHF$c z?XwNv8C8*<@=kuqriipOSh+uc z*Vcd*yZAl(5}g4bT=Xzvyq&a4)vDsTjnNsqP33-7y zdDa4%O3RHKo-GKK=VW`n_OBB*Kx*n$I_ZcM-JT-vFXG-Clq7!WC{tPtPkTg;vDp2F zELZT7OT*A@_iZ6JznyaLRW3ts{dVzt;Gk>S+19>T8s}@Gd5v{U>z7jNa4^2o&spPV zQNd4xrYRwEf!ICQ>A|ueM#6GtkA1o{mvVlTuVhVqyfso|K5t?_qVmJS^lS+2s|#xF z3{V~Ik+ai6^^jFvpgQXZHPu$gFZSs#+D*<{FZ6W;vL>mUN00KBoo&+6O&^Z_fFNvW zSt(dzpL)&L$uB)CSZveNCf9N|1@n^5&dhY_xw9!Ma!~fZ2XFNN+xE5#qb!C=T^X;S z2YX|no?}-mEEE-suvpu1*NJ1|vVl8+pR__~z_kq@gQ@#Aeaolu&krEoc@!f{4m^af z6c?sv;TJzE!ey}k2`{lN$okQ!Jw1upYsEp$7Kms3CbMuR7^PVgOrqN!d4J_J2bXe% zHEiYJ4Q6`X);KPta}!HCsA&EnhZqx|hP!i(HGle~gnFC;BT#k3@DD!#aN|rclJiY# zL2b}4RzqeG_seDwshSM|a^>@zz7e}e3b-}TcV#G~{jhC*J1;0sRA~w~Y^zDe8Kae| z3v;%2`0MZo(IZ&0ddxbQ-=ul$_W|jR^#PtR*tbDj1M)&POB#ratq>;KpCCc@?GXKT zKg+?Mv#7^}%KtC~>gxC319{6inXye5xI1@k^CvXwVV>g1!me=Ax`*HrieVxwr%pfQ zv)5xtXOZB{XaN)sKfJdva${@V^Wz}79T@m0K@jh!NT%aD*9wy&qH-#t0#6(p3#3TT z9N^x`D(#?N*r6gmV zKpAdW>w}r1G5Q^+`_7KqI5e*~Gcx&`Fku`(D?}t^P%^v{nwI%O}-gc97q%6Kk;8{oGV%f#OIiTOxLj)$sMiTl?a29oA62-5Jgj4cy6 zj8jF!W$s@ajFyt$s9z%K0B26i%u}M<7t3`&{RjXG7M|4&k!F;#rC4W*r8sslWJW|8 zBa*RPMKfkshlhQ!Jc$^xq);Ot{DXzTl%y^BO1de-QibtKn}crvtqp44R?@X9s5TAF zHh>J3P62F+{e#Ul;O<*t7vD+}C~g3>Xa5i~iO>kry6wZ)+<~J6NR#fei>m&?s2*MY4@j>#{Djv>HJ1OPVYgKw^d^E_Lwtf zb&3OL9sVbw zxqGCIGebfAUz0UJ4v7IvrOynb&9C(j3E*mhA?TH0cI5GND1(GaP+#{~`Y%H|KUpg4 zjGk7xpv}MIA9KxGU=%s1pHZ?p@@ur<`_2EspDDOuU@}OlrpWuz_A#i0;pD9ldFF7L zSwM0!iXDKBNuWFt&@eqa>^Jj?F4GsXh|#Ll(On)~9mT+J`qyW&1Y-0Y`m85!i4*=6 zbF1J6A+WLiP6{Gg8{W?U!Rq5*APrV~<_{-VRB8*# z6g~sw0*bqbB8t$Qj4;6pjRF{{9UZ1QyEnPAd||pdWXKa#wt;pAP10^V82C<)b|OUl2pD)@e$G$@s0KYs}o?i&(W(okSON16@2WAP6m z(5_9MNzc@rR^l@bnCR?ne)wKTHH0lg;UGQX8MNYCZe%ma6qr{v^;R~clVV8z6de9>d&L#keUr1E?(y#w zMJPr;$NXI&SYoW8-5+S`i{&GCDs>KzM|%h|;m&peU2o-sx&ZiS8FYK7?*k-x8e zCy-d6P}2??ytP4UijD_hFHD_tJ^d{tk_6Li&W2)<@7K~R z%dY~`KJo_{0(-wMt>@i_6#k`Ia%pdwBA;v^d^K~OzbW+OC}>1v+kIta0&l=T!23Ei z=W&CvTt998?TotA-wky7QKA&#Nv-SD>uuHr@~07G*%!!j(BS58qT?I5fS;*UrM^&}l1$zx^&oD_nxtVDHKbDY zKY-GJ%gB4>yVMb5RQbxT6+17G6h|&LpP*x*5ZtxJT=$E;qQ# zM)2k2-R+_6Wt720@!y0;LnEMNecrM<^C3?y$tNksHGEc^1!PXlzU~^Qg8mGv|6z`p z1Jda>jx5<+-&(j-m)S*ZzU(}%G%-{L{~!CZ1N&M^LwspFv1r>rRJ`zp1Cw_l3i*Ej z_qy9)`B0Wyf7o;1nn4NYjD{2STR_b}zupW?Ai`G{$pJW>My6WbA@`^ImQ~f*jrUTs z+fYm^&+&b++~E$A_ACz|{Tx80JKw9s@Br(47fe$0Q2qx`Eo!cRw( zq<%Y=!^ED}UhHdZjGo&M?qTJ!3F!)mw!+9lRi-%jo4rw-MT~((Hgs_1^tBhb9pd2g zL_0c8UUL5y(@=>qHs;`yLs@Mnv=2@$v`NY(f27m*c zYh72`_HRl0APUMAA=Bt$`7XIt*3{vIoe~4JZ?=PWy`mf6KKMh~JOFKhWS1!E(j(-8 z>7I8QzHNeMnFBxoWvZix%3jIx09Y%_rDFL#ZNsP(z3KA1BHOK9QKx@?PrvT{E#N{o zF&||TiDM$3PASFxFi-oXu{G7wdZ6fwuAlxX4bL?~?!xqC=f@K?MDsmpi_wF52MNqU z|A@NKS4zzyeg&t>Czm~?)M}jDGBqSW-{*ICW5ajhqXMq%PyAspi}xX&$)V}#z8qdo zwDyAV2J!ix_JSba$jI>Bo`%Hr|7N`#pCFQ9y3_OI&mo4RrlF-ZVFUM&&(6SGQx-2hU8;xZLM-FiC8c zwWH_hP|y|THwg&8_;}Fp1VV3;VCD3jE!_me=Wd-k-Oh@>wMCokIj2olLV<$R74r>$ zO4Zy|rUi=Gmje|8Jy<>=THqP&PS))r>VXc^&s!+7!Jdn^Mj9o(c#VBuLWdfrWA$1$ z%g+z~2RJ`$*{}g&x}J)Xq{z|auwzQZ)4>!WkkUYp$Nz{ReTD>mbA84ek|NzB z2G_qa{+Ead+6b_J!B!oMILB8U_q){h19apWAAjXGsP&y)#_p8Y`WsANBysa?B#ektL%JA|1AbMVMkvb59t`hy64= zkCuB4u?#TU<=9*Xto8v2#@_HDTSG1!o?A=m+ZzR(D5)CBE%J;Yi^t#n`Wniv0QW|p zpc|lnj&0R36l=PM_y8igDqsUna3SmVJjSSud8e&$=MLu&O0_+en`~--!!bt&7U_{c z7T=gbG)fLay@4M51dgN)0&1{f=<*o$JGBd9iiyL?RC|rtMq{^VxdC6`3Rdr5%2|U4 zQT89m`~(R(@gAdXxD97hB#XoE(sbSxg6i8N)waVf1428Y;uI6;ktNwz>?sB0@x{?d zX%@HdAz|dR8{$BbB;;I%Uwj1}0ul+-P1IBz2ubD*(-WH7c}VeaelHXD=Ip=2E|5u# zfp-FI0fz4=L~6?8hx^{en><9@dJ{tfYIS?BN8}f!IlXqnId{j;P5s=H_$fN$ z2Eu5i@d)TrYo-F@u7)1c2C#YSkk#@~QYP^EuVplMx17Zm+I&wpS}8noVO+7pvbD0t zYxXYIZ<^Cn|kNyl`H^0VgLujMztsx6(lCBoYw(lI^K zi?uC}9`&jpcWP9m--a#s;Q^o-08lJS$^bs!ByDKf+v0!`Nrsbdnp6C2t2n41J`=ol z27^1HR*@)Z{U#xcX%D&0I>A;Er5JNcf;Kv+%0GDSo;s#e`QB;jootWszBCLp04{kw zUd93Cym<6UvB5qop95%Dfe(83Dz%lpY#MD3AS_4s+Y{L&X_25EWX=rVSat%kl`aBv zz{U0tjRfS)!461>njP5&Ct6a#H*ax#BAu@=C(Jz#m7m;5QA1@NOg>ycZ;|6mTljW2 zP=0YH5bNO}mvI@u8UwZB5K3C`@I-nOg5pRU3gQk)f*e$x>uklIJ4-Gdle}P!At(4v zX{Z)@NCrEg$+PWTs#Kza=9ah+;s7Q)DRZ3FS6GAc*;T}E%4Y1Fsn|=<*`%7Sv*Iu! z5h@8Z)Nh_WAfw|M#F*D8DNVSL3f!~%uGH8FN3fmsvG9BIr&28!^gOkbXGV-h3MxII zI5H=R)E}eX!KcjkjZ@Dg+{}$c1QW7!d9K*^EB}VVPos$Gz0Rr z%xB^9T8KA+7(;#1)kjT3zBfA)wY-kk&4{0C_fnHh&+#Ah7|L`_-K-$kx~p&p*EK60 zk9f_xQHgIqZfga=!y~n5m+8YQ51&pEtwH0bEr3+rfw+i3NnM5uA+BXdK;##v6)O*P z_}A*}oJ_l#G;=COoa-*9Q=3pH3qY?DqcnDsW+&F1Vyo!?YFffyNNBQQ-kNev)1xUb z!ly}Udzx;i_Pw+3-FC%Amufnvro4*Ry_$s|xD_Vgy$+pg-(9hUX>ITJoa5aNxfNEp z#fUt+vCe#QP(#^{p6Nh7q8 zZEXZQoxAHHiEG#h;JCNiJcX|f#*E-ipy#$F-!W*MwLMIpwZH6^YqwRTe=!s}kl@?} zGO$C$#9x1$`0SGUDJBS)`T%D+c;sC~8Rsc`w+&=$C-K6?g3A^ilM*+6;0?$|`du-= zRjW2x?K)!DYpCaCA)KBX-qG-Cl3x&%^rA1Pk`!S!^u!hlkYtL(pTOJnPvvCe%8|-& z&xwW<+w9oJwg9i~WWK7K`EW`@!U;LtA|W9P!nH0{{B!D-$!ibZ>+m>CQ>+dgEA>Y} zyVdnO=cx(~kph!42g4F~$2ABA>Q(WG3^eH%{vx_$Q_LFRlz+fO5p~_frOGWpUn^wq z9`jT8<=Lh`sCUPxsyP#)TzS*Fv^)TqBnLqGIbqt?z;I$JM^o1>7d`T9_CE~i#qOnp zM6I%rrLz#HBRRVSr{1U$i6n@*f}YePmpP$tM!l~Zm5T3`y=?7K8<`aBmLh0089$Z{ z6o6Q^^+~r}mr-_`3=#S6&vzf5uVQU8opwCc0S<6P)Wcr#2%(~3cL!;enJwAN;WlzQ z-H43*3a1&Ufi6Ru+YiCb8jf0f9tH}#1gW`0Q**%sQD2YRh3B3^7KG`=H?|~uZRhE) zbPGkzv-VjX7=vD4G+UxxlbM_J!>1Vg8}|kiL5m!jIzlfI+k0&4`K_AkFOc$9vrF?5 zvU}XV6IFFL*;=mW1CQ~D6Be|uIe6b!Kx0IASL_S6rK(lhhGMtzR;?X`kjf%hoMhc# z%+$-`j^%j zPJivf-HsNOLMIw0Jv zM8dqoMp1bVEAOJ&mps-Uyk6XAjbHkq|U&Fg6kySz2dKzEGz|-<@_2PpnC&$ zkLC=(QJpS-N;Xw#!C}!4nzwjKMzFq2*QY?}X%o%#Cf-0S32&%MGAYB2+$MFoSzq0j zb-$M6fk|=I4~ESq}FLONggYsye|nP5yKLKtX5 zk$Rmdg+3-2lBFL^-tS1iCn{-LDXi&o(!C&Pruhp=`6s|6w}rQIYi8#ppiT5OuehaI zry4aB-Jf9NDwP+eK-chHo75an^SNSA%BhqD6;JrYHsd|k`T2XHdNYLwF_|6*TgIN* zSO?_>6@_{aQpaC2V;mb;K>tW!3WDBOwHCUWBJhz*yR1A6$Sk;FUs76b|2Cg~?nTjR zi^Is0a=&JYs!+ZQKvU&Ftu_g@qM$>vpQYUKj;%VWM%_jCGdBld?R-KicDJ>;Cy4go zqvkP1N(lr}fnbTY@ENtnH(^56K82$mmmubI7qhy>I`376_KJvD@G+08rbEh4UH8Q| zc_$Xz*FRvAIUJ}SO9_>_+<6DHCWA;pywAjmwzj^WRC!PGlhV5Kla2^Pz<19(k7-Cm zu16)k**&h2)Hq{pG+H5~h@hnwW-krMJWFMF!T zzHh6v$)OUJ7f61&p%EsSoiEq6HKj*Fq1y7*z!RBYv7 zNis@x{tsRl7cm(Z6RDi-m;|Lt8RSTADfY@sZW#zeJ(K|xorG+ zsdd%+M|6+$wE{dW1MVqm-C@xX^fgey!w;S%<@AvGi4he0goVKS z``Fl~rVdr%b;BaS6I)D6{y6X#O)ytYFhPz_IZ8o*ZFF{?FO$d#M6$3px%n-z>Kci% zn}X!ose#IErC4p#xb!fwva0V3E=ecrfE8y%=Ep9*;!DH;u#|V1%traA zJ+H|Y18j!TQnCUdW###AByec(D@SSUWHxA)m6M>^JPLDPI*kB@)CdjkorWW~`#K>`v^Q2a+tP#$qxVAF-LL4S;qSJD za^^M7l|b(Yx}a^3SnOzRy~0+!9_>m`rrExjBhL}UBD$YG^?3|Z%9Pbzbch|J4CvX0 z@0(OXye3)heU4cjq*BZ=uI`$lX_!jJ5l$od| zyDAaosP+ltWimPWmF1&vg!LT?@F!GM!5e1|eqKYgq7iUIcbg3EmKnsWWIp#UTapt2 zS_XSv7Mjxv)0#>*B27^~_!ylHOM0VQn?Dl;+*wfMKdQ1*GBME)BfJz8Jp3@bylr&t zSJ7M=S}YBH#2XV8fOb_*O+K*~ubDMP^6{^@>O3MM)p@K~8dFfxd)1OR{a7~My-fmC zjWSLY_%9v94F_0?YkyEsecxTN157|rLUX17kXj>BAoUMGnlp*UBo0%wwiXm(MAgjF zHk1c_q4Et3TM_d|a+d_3R5*}xcuurqjoP{dD(~kAS=c{sRR~NPip%^z+W5PGi~1cV4ENQcCL z0j2j2d2=NpB%tS(cg}t1z90L?maMhr9CM6s^fBW}Dxq&_7v+|UC>0RncR(aLI^3;x z7xbhtoGCC`bVis41qwf$P6u;~C!gsJN2nxqA1Uw&FM7hy1u$Ud0O)H5GKK5vo<5^y zTAp{u<~^~+U+<^ptLy=@gmej{F^{35nuFs>?81Re(>k)dFFJxWsV`h1h3!6#mcs7Q z=_txx)*_!@++at{WDtfGk9#xP77HQ`3n91qJ`cgBVx@*?slMw~ z1jaMb)y+1+vd7&WAr_FPY(BCg?fh^CRVRPnJ0m~yvxUU*bCLOPnIr(-)8k!i(Qwuf zZSW$#+F(E}q8Q6f9t$y+j=eZ~IVARV(}~kFV4q?Iokfe~8@smNY;62YAg*>2f|7>~Y%xV}f_v$n~b z>z$b-HIzA{<*}Jxst?n$VqKq}M+;yxo3WLI5C^r(KhuYq}hQKT`FAoaFdELaCqgjEIcJo@j2e%0bNK@bvn zD}PJ+SL8^MxU0Jy0A>m?JTF5pJ42i||8F3nZv}jETm-KuRT1hE7{FBztbp4_A$N%8 zaf+LkX+M?QA{winjwN>$51XeyUt!!DUl{zS$by*5A58iL;#yZo6c`Urk2p9-^bbo8 zIq)4z$|>h1DSsC1d@6BHPy9jsgL?%Y?GBG`)Lv(mtQ=zlSSnzVg45|Y_z5->C7ve^ ziJ?^a$D&Ae*2x%zZ!Q)@t}_Ym02)AXQ{0>UCZMvSXpNjlQnN@8QXn;u2BHLe@#>JYT=% zf$~2um+EZHPatS?9|Pk6=J5{D&C%Y(S8j%AtSPqg&@#M*u-n9uf?8rYeKd>ovnK#w z1NS2f>tbT>;nxToKLN3{1z^CP*xD$0&?1L4rADmN9Yu_jZ$**DSBvC+2?DZ~fnZs+3G$d~K#dE)&M z8-&S5Vr9df>1fdlg46mjKGjW`neZ3i-@brfABuZ@+b2Mw_7M*Wzh3EO1~2V!K-;$R z9E^{Rv!Ys`ztQqUmy`LQKdDsoW9)*S$1CN$2kgzN(c)lll0adsCiQF@Fn6ux+(e~3 zn#qOu$a8q$rnhO_Y-=F#=T4RRJxyD^6r5tA@=$wvAa4_zv!oxGx%Xoa1^)$)ko zC;Y;3ts|Yq$FS#nIzK)Eq6rmwfv9*`qg>R23;Wn>O656jgFBu6)f|j-)J=5UStjH& z7GC%>1~_=@zVet<(M39Sw(5vkowTZtnz2s-+7z5_q(uqG61KKmT5Reu=Kxlu^Y zuAayJY<9dv(Gx%S?2so%w|(VV#mKTPvva9|UDyo#dV>wX9etT2*-w zWyzr0hSNn{t7;%MSl?I!;`$hn)0p({f&alH(lhx@3i}@9!6#T{A)E`_AZthFi2~78 zZC3KqrN*(#?*z;%ie)^QA7ez#I=h z1REw^GQ9#O^`O+;g$mXgZ2*(lkIftdBOm5JV?l8dflIv~JpnDo2IQ+)IZrl7YJxHM zz{O$`oe`{+JjG%j?mdAdo`U&*bxR;QM(O9Y!nt=0{wzV3=8$V{S}{r{*;X?*NxH|x zj(`zroLtELa86$Cy;Vm2Mt|IT@R{WTaDH5{eKdB#yQ3^Og^LQ@GQmY}&pp5WpyoP? zB208S3nI7({#>iis3k>1Zh>eROPaa%0+K$lebZv{petMS-tT^Z%{K{x@oE3+Xc8UT z(9oS7%YMBbj>^SfFRvKt%X;^SWr}i<;-AhC|K2JYkR`Vk{F+uE zpA2KWRNUN{)n>gGOtv5U9nT#d4lAN}J>o=sZXV$RP%g=Q-IBEgw(0pVajx&b#r{4$ zYvQJ`%is0>3UWO66gc>fP#pEkVDD^x`vWmf3xb;zISo?G2FYKqdA4ALt(Le%keQfc zdyW5onD;Mc@rPlc*&o!4OZA;%l?Ste&fLcZRI8sUGHYF%6jdb`%*wdSYO?Fq+RYF9 zVz+;xEnmF{tF(|cB}jJ{M;Yn4J;Yx76^_=Ekx!DEW$+`{7@JBev!*6O2wBp zR#iVUj5-f$!HdHvK1N+62XtwJdA^lm7Sm(Ho@af+f^l&{SIm>PA-eUUE5c zBo4Gf+y@~gZYu!D9q3p9tp&lK1DZ90$%dv-3G6%psIWs!s(GbENMZL68=Hh6r_50> z;2k`_H&?!n)ew67TO~(Y{_Zpi`+M@xKls^XgkCkqb(P5X?#fZGd&Y%O`vsV%K~XfG zXjyrS%83Y|twW+9(SA@x?qefq)IQ|zlVq^UbHHYy^V9Rz!Tw1Uu6n4Ehj)&p<8F3WZ0FKm{kG@`sI zD!A|+vAj8W_qdGQEj4hq-F?g}iUhfJ9rXxQV^%2&tD_VRXn#eMj`Hu;J<~r4cxOGEVt2Y)yIxd^M;15~dv|Ppx z^ux)4+M0X!%9#RIVyY)y!sw~ zJu7?3j*&n*{h=GE%QH+!T-S7SrO2hWFD8z`&hj&P}z%P3O3K=S&~c03>kh1g%$~QSOkwQ_gXmgzKuH{e{f*Acyre z?QinnWjJU)K9Z2UZe5expP*-LsNDyjmS`Dz8qE2b=fm9(oW|4h-9Nd}sd-ZxpoQ#W zxtzSd;~?5T!A2G??kXO%AIf|K1vfbvVY>#lWF{N(T!4ASR(pM0LW*pY+nS(D&13D0 z%tR1o8<`I*x&`heV+QFzgIUnjv3$LTK+N8Iq(qz4%s=|SOr`VffF=h1LIM6b;0JcR zc{o+`@RfeIj6KfdKhBLPqQQ$3N7SaRqqt;$@Oe2n%$?sOB9>z7eh@^;vd(Te_Z)PP zaU#hw*B>dM0$4cMkcyE%pqV+`k8#PsX&V>4y1y4lE0@Xl<3JwZP3?y#5$Y>*2?N9n2$?y z{!m!TGP?!GKf5E;+*7xsda8?6;uemM9j?e8(n0a90(1y%&MM@0H(IEm#xg;dxu$y| z)19oT5fQ3>E&YG)p4gn5UU8%xw5;*~6mC+#vkK~FVcUe@_xF&FZf+~ZP&Y=*QR*w@ z)$#H&H^0S7o&>F;v+XaO4}^iT{=taH)E6y(R)M}$2MzT-nNyo%Z!HJaiQHD@k9SDjvqjd(3gT*2q)JJ75*5;DD|eT6I@X zy_Z9cPQLFN-6-e!{W$IzbyrN5t^ilg%pXb&(So zbK=)|^h4hU2RYsU5@SjE^5uv?ZYQBW$7S+c#OSVI%#@b2+4#KGE1ZN1?x$=f`Kit z$sE}}0Ns875TQ*Xpc1CAp3Rn`v2eK@74tPHQJ4O@d?{T~)6xFUfu$qOvDcd1K99i; ziz1kHl?yH`nlUg3(Vvm&gWr=xyVo>jjMU}Jml>STNlGf0aI=qdl0lmt-H-%2da~Lm znd_o5zjGSI&jrA3u>%7&CN66_n#9w(Wv9B)5#@o=Bf`WVYq~S|B3{hH5Yb$)K)1;{ zf@oiCevW)&nI|Zw<2D{V)$hK53+LX9AE?KL?w&ro)eLk^avLJg1tQFGC$u#*a@XOF zFx_B|O*4o-3EDI#@#QyHn>bF4eyJQ4QUDRwuUw}4XYT}1mW`L!?zU5T z*E^i!FCSiDl#gN*4950V+`m2>33)DX(GvHbl;j=my3&I3UVV?-=m*+zG|QqGq4Y}d z%o-qIjaIl-pF%-C&`-Mt^sy)V9)!S2{vtj#-iynYo|FR)MMt7 zM2cJvn8zVKAPI&*vCY{p}m>u1LOeo%EXTeS*~KV#nV z$Q5zjGn?lQ)@e#GD9SW-!?-1xNkOJ%>kBn!^BrLYC^(u>)%w9x(3={)-Ci75%XEYN$bF)*_}@K5>x;^E}dzFP}c%{45O2mSKE zmB=T+s1Q0dDunN`!W?_~<*S*wAVvhiR_NXHR23t^z`PC?YHsl(aVRThP?B+tF@UWN zusP9r<{xU5)$g49Rp^~@pDq^;F~fJz#md}ThZ*67%mZ$9Hv4^U$B!9rhc0mD)@jVp zBkStto;Bt4^PFhP-s!JkY0jg{pstfU#Ky8Ta;D4Tm?QV*WP`YFgXVxIW*P#b1()qH z`#o+yWR?_M*=<&wo^>nqtX(rpiTRJSV6@XN3|L>F$GXLjY>cKW}91wrgCPhDnSVvEg(*79LN zJ7EfIxw1t}?wu3%lQO!a0E5v7j~(29p1{c>#_nx3bFA&zoqX?}8BjVmC-lFEU$~4q zW|s25vv`QHhMS$hEgkuwu%1y&I?D-+h*zZtr`F^k;`|UeOMrf*I@xkGC(Jav@_#uy zLLnd%i9il#dln2B@#Hj-^7%vKR;%K*V zo*kHzUCdcNF-wu!=q;SgLBL_4k-YS{;KwZp$|vK z3DCu8z_6H;9AxN-`JhI;l<2c56I9zC+cl!^6a ze4y8LZs37<%z5hY*g)3~5&JrxmgwlA%Z;sq8tywgscl&MWxt3tXw|p$7m3{n;Ejuh z%f+4^Crgj?GCG+VHV~JY?=P$&Du3f4Z`R(WV;&Noy2DK=3ZnxO?y52q=n##4j+1Ah z4(MMzhXes6-Ey3fNt{AIhlU*v;Z`ttlS7}|$-S&&Hh?Et$W!`AJ$Z!P@ z=*h5o|2|(^Q*J3j&P@<%+h)*_Qw*pt3j_+S1L#S%0VqdV&_PI)DEj$_OIqzuMx!y) z3}k0@%<}X7$a3ym$kRW*+o1|QQ7#JV*-b`r*bakqH)^XR;wonPn1R!+oe z-y?5zmpE>nOkEx znv%gvbjakWZ*v!D&l%Z)IXt~aT{P|yZ(JOFIJ)gh6?4-pCZ4Sn85juaBNySUr)o&_ zz2jGCbYZfsbbImD=?iLVB3ds&x%14k9G1y$_bJSnVf>A@!Dij&xHi8Y6d687sKK7I zO&jp&dxuhafE?`#3)f+=G6xWRpVS&V5wEuv-m+BQkZRlC|MmMwzSnna%F>n%8NF)^ zsEWz?5|$bkX{@l$tWS22MER>Atz=_zHBS)zt{m}Zo>? zxK{nFR}4Plu-MDU;muD9mCH)Td_hK_rsq)-UL#<@Os>l#!AS1YGx&68l*V{>wU(~i zaK3WB|25n@9T2;mt!XGDPtfAt`LW8YP8OWKWVeQY(*!I-MsW+4SjxDy07eH!{t|%F?wJh-(L=8Z{3};bJ9oMPyX@l0u*7PHsi ztg~|ZMwV2^QQBL)?Acg&rMrd(-ZWl^!S(H;ft7`<8+@NGB{JN|<38+BIu>9p9~9Tr zFj`swCV+=VhN9}T-osh<7^rs9)>Lu4si}9sL?h2J3mztc+oIgOcDL%F2Ub@OObkF4 z*onCzW75|iAI&LfZo?*ORcgs~HGT7$8&?fUYonFZYp|)OD|xg_4*9jntj;1Xy$E_K zW8lJ0HVLE8LZ*Ml?z|tg#=#cdvqbmh9PFWH@0D?%zQTCkB==9@bw&C+>{A2^$5FKd zyFY5UUb*g8y3|^(#w9LlHB5?cESpm|7U$(3iI1wFSb$@k)xn?!~wJnoHp zeR3G<#2!8~!u`$ z{}ZmLo83GtY26J?PcsXmPwIXSrE5**{0(yUjMBi!Y>UN7!|SJaEV3ggOk0@lXcic@ zaUcJ2xUtGEnMiw~lCY(b{5{LLd|x$B9Zh4ZivGi$ziHHDvfW+KJ#Iaium`2mBgbB< zX??0SjBAlUaJq)aBuH5q4ZUe!$8#I=&qzHo*{o;vM}rMnlTmr&6j$&9cQDqDr8Eu- zkS~!z<~@(+6`&T(q5Qh=}WQ;su4(6CKqy$$$Wvn{tq810J9zkt% z74V6Mywy>`T}?k3Lbn!-tOHYLp@J1h76fRjctSl-?g~nvlJzf8l`i$YpxGN6`{frO zg3PWBYoQJ|W;@m^v>{bH-|GH|NrFv})(FPtkOB2JW1z&0A^aV&8Zu9a8ZYTSTM645M$l)2dHIYF3L(nbOdyp>^G#22wS_ICX>HQ zVUKy_s5UW#byFlX+!UBy$Cp5ko6R!NnPcFp5a~&LWnqca05>D0_g3}`f9#Qw=m05^Pw~NGLm-JUGEjZvN6u8TrY@Y1k zfvDg)auOOc3*6)YKBy3c3MR$8+~TGX2}=1pEN~h;9?NPxci!jK*d~5XOX+IkPh5@| z>H$*4lyR`n@5Pxxq5EatpLu3lcQSL4qnBxzA`R*CeRA0uE}zO8f_HTfYT9{7=#)F; zg0=+KIzyY~{gV8dZWn`&4HNHcm4xJs3=xxP+EY)7572j|#RrpA&PAC=1^EO^7ZrbV zrueQ}_l3P|>FIt)wwcKwmA$)yxFDFl5)GfWOsHMkK2lb&`XkO2v;)=tJf7g8vO3!w zMRj3}R095xi8w2!!#o*Q0}L=p+^1Vj`zlq|#f1KqJvh*OMipayHm_1q1BHoKQs|2c z*0#6f;0bG&l~K@CeBc~0`3Ur{JUu;H_~n=;pgu5+lA3)yv6+LdrY$m537MKW-<>gU zn|@}7%PH$w`1N$IdFcp_nMs(3SdOMT8wb6miWk^N^DDq9=( zHucL&-3kcxjuDSlGZ|w(csUp^zBe>LNh$~CV}MMlfN>gM$00=lsoO`zKQ8{|7L42!?BP5?nYqT)Tk4}!hoGg+SsIN_ zvHgGj^5wwn&H^n>tFGa|1xqYu=UV^dg_v}3NQ`PYrs&OR7zS4pBH_leLLfy2>aAy> ziRqv+){eBo>4Kh0hLUF;9~ZroiE^4$MqT&}L@_ax$!>i`Szpi0`8E{XpUI6dcq!E( z-!CnT)rmT@1E?D!^by0$XB435}b-GA*2h?x?k%Z~s&xe)&t3+A}qe6*W0f-#?9%uoe%9L%@|3;)=P4a|RM zb_dL838t0>!pjQL&gU%d6Z!Z10F)A#2^GyJ2=Il}RGC%#^Qkfus{O?wOof_&gzp$z zwQ-RRgzXI?egiV8y4I!LfO7<|>MSP=QP0W@2Y^C4mz^xde^_+PGwcl{5d}@BsjAxjBx; zjMDP?>xB~KGx1F>1c+Cr4+d*|cvHu$Qxb%M%s2-FT3Ql%{qpEEib!~V-ei|X!4?zu zDS-|L(H}^5g=1*XJHz;3maD$46=5dwMTjZ#Ao{WZP1Z6TmJP+J&O&`RYe5f*U zU#BahXqKL?bQ}M7`s*`|F+1t;)@iiyH559{db+nL{Yz^4nTYhd(@<&9{MWt!uPwMm z%%e|*k)s^>r(z`U&Mx7Qy`i&ek=85_4Dwb30nB zpwtVhTADTXSm&MaC7GBvX*%|EVx;3vXL-TH3wtw+WQ_QgVn!QmJ$jUnWX92dPS92e zyN?>8Dm+Hls>IZMj?)WQ%~;ncsc&iz_IKT^z-Vu<U%B`cSU0=mjB2GC9~Hfjp*PtqkIj9r znC+MPYf3ysioU&|2zQ$r3MVC84@MAQ6g2m=bf&sSH3$0n?aWEP9YXON;4krQ{*>9@ zN3s6&{8jvy&r$2Fq4eOa8Onl(S``Co58jX5b>4CNKI;LOmiVi05?=CCcv@(n!?5YV zA0#U^@UH)>Lq?viMJU=j!qT4 z$QGq`uky^79FpH0R*>D2i8t8r{l)0}qIEX$LnBIhR+-E~zaje)gDGS<6^vT2REQzNWmSsPKunP!J+JUA4HU zCD8zlFBIt~HASar{s?0>{sK2(2A=uJJfw409T6sjQ>8nx2gG@6R4lWVEC_P4 zEP6+Dosv;YEGiV1({+5X)1fhVGXXxmqYQ(nDPmBYmHFV@R}+u>pXzpEn5!;S5wV(` zG6S{u{=)r{M(nr6i4d%7>Z;Hlts-Y*G3kGi&i;N!-@uMwW+T!c0Ty~wVoZ|dTN6~` z<6EDDH#ueyN7YL67mk3V8p<8QUIs(vS-EOX`GCIZR+!cOceX6^SzEV>as9D(DwR1D zRHNU^><8G#F(TE<)~931X}={cw2->*BUri8L*j^~!qs<=H9{v{@T6PTM0f1zuyMRX z45en_2*Gb>1Z##hD5T|Nyi|DMx%)Nc3gn)RegSER-mHdBIlvFUGt@9QN=2*RkLqNP z!f*c%-!gq*?73m1qDs>xu#hcaG9I)L?}8^~vXsh0xqRhGZoF+i@wt7tNdl@%{iBU5 zXTpc`pg{=1x;93lNw4`M>G?q=6ppm zyfDeq0&^*ww9T*JDRP~Y@>K-ND7LqGNYTW1dkn^9!zFVy6-6+d6pC+XfXbQqu7Mci zCBa9^stiaMu@}%ZWWGl#F@*##6s`}^%x^CgIh&h7>P?r8so_Bn&W1Z?6vq-PocmUd z>kf_5>177uh@M@jy?2~IL9cxV@$tk{3lqn6r{p)2H{CpPR95*mbjrWdZ2F*RLSyvGP*k z72v`ld0kPhqsHSGLmF0AD`nqzlr1u9yAou`K)GM36;T89K$Ge^z9t~_&N31!uk1<1W)N)- z_uu;_VtizZx6U1kQD$t-eK_@V|2!`UdVxfSl|@bKAI7v>|v)Fn>?O#j)Eel>}OJU3wSRX(TX` zcZXMiqWpJ1>U43I_C8}Cv7rHVFqIFah<~Y)IvMjxQg*sS>)99bvsyw4K-rlc!I;x4 zk(iwO8ifwY9jTK0GxS4NF9s1@R*B*~UyPIoFBDr6$KTeFR@A=$8)=x=n2kca#FjSf zit`K*lmA{$A8fEaNo`K+h}uzuP`=ARgSqjN8BZY49dS2pfxg9!iD5mA#FfmH#-7V9 zh@xeMPu|AImW{aQ?* zwQDEsXUtZPoab|-WyQENqcKvmp`Xvs#KEU668PF(Rav?ndu8My#dI`pn*)8Ruj1JG+eLZWt;&yjS~-(5ph zYG7W919y6+8VFLKP?_^4rK^a$Dbbg@-%`|9$jB@i?rK11TnhR5*)~csY7Kq6nxT~d zzr_mo4Z1Fl0v!2A2-!i^^V$TUV&q8zO?e;ccJVuP_K;ks2w0C3G%swV>JTuHMAcTe zDea0oHp4sfke&4s^jbc>>#f~h?xS7fjyYax!`if5H&s1E3;?%7A11Gd7>Xdl7VKu7 zbLQ$T5i;fFguKU;r<4z3YlEKl*Mb>ze;J^sXgt6{N~8X4#|*Fi&69UCEGVU?a`Q${ zg>2hJO91y?fhjvQbqfF+eVM;~&IU1gYV3_z+nP!2^>@ZCt$^DlhP&b#fntrM+Twr} z6I9q{fqKyDBhd2~m;1sztZzusqJ`bL7pT_C9(otW@v4SCylY-sRvK5*c<|^4xTn0r zUNrB#`v~>yrCc#_CcQxxq0^`%O4PbRZE;2hIAOf4b$&82k_0#A3j@AWnxqWSeeEDf z@hEwoJo z)=lgZTF9_r?jswTpY7|QJk~HGl6TktORSnO7{-*bM7kll+69;;x+yt(z~yylwYdQ` zP;E}V4eW9J0;dy-SAGP#B`B`9))mKtiCQR+M^*-g-3hH)MX&m0F#@W5_!Lc10p^qp zHn&s3+$p;nn}t2rB6y7t#k{HmdKpP7mFq1luon>65Ro&E%lNwQ zGjL{8y~T#kOTbI2cHSRJChi~=JXwPFzb9t6{f2^8_117~hf*=E`PX;{cHsLQcx>B+ zr|lC)4~3xWLW_twXPa+$^jfY{PBm3)LGoq~j!;*p0nQJ+HXoNRUd4$d^p0SMi4?a4 zJ(s-h?)TUi$aLM~AsE51n1({(upvL}WkaXSgVo1AqQOXpmgkI|;VcLE05|zt1tm^E zcG7UWX0J%Yo8I!Q7ma88;#Kr}!im?d{Q9$eC_>JSdkn|VIuZ#MJEjv*nFCFopNl3y zR(U>6A{a3HU#V$3G>wSK_3HZIQ>Rid8@ZeW z@sRz^axc<*&tfb6`2n@Bzd>c{HfEk_wA{+&H8FYa6k>9`BX4rp^d+iyO5+0!Ow(R3 zS3jKg)UI#4Z}a7)cd~9b6G6d{*}*w#;~<#)UhY16O4N9C%4B-_bM)x;^}irNH2fiS zo!{KZI|Jny$vDQ}!s&0AjhizP#R{f#8r%?~vQm+(PN+GcIWM=zkWJ>BIO9VMy)-Y! z#^7Q`9+ERuI=fEJuqWq>fd#1A)v|;H`wT8UVQB7|+!aWH=_W$sYC%~uTivo!g3`GU zn^*&KW`uBNZ)0f~cHa*73&Cgxj=?yP`wW^BW-?o|sSyUCvfehE5n-S-^XnXeOa>FM zvcj{emw&I<&J9|!b}~s%8qwGg`0`GEkKO5#0tKNPo3wT+biFk{8Y#R<8GZHf!qpqR z#gDi8VAfvSck~O+S57EWB-zNyFRCGIdk@T zZ-u5@$;)^_>H2J`jbV4y+&7jvH5cFhq|)c29_-d^5_Y|LB>2-RijT(N5rkO{KgcLe zp7&kw?N4*uePDWkYb>C2gaqNfk%PJ(v!-bS4!I26mI0-0z|9k?J!4qW!Qu^!9JeM9 zLC_1BZGcJLlQpk4-)AI>xgXetJ<3Q{bG=^hsmf+X()%b#lnXM`XMV_Kr`9rq{4YU2 z#xe#Uf>nA-u3%=Z&jL$h5rbjIBjB3;&G$PyU$6P&9k?QH&KIRu6sr$*t-Mtf1?OI& zJvq3SVhr~-({{~T^sR5+o<~}X-9(4L&5BSyo@;3zV^xD)*B%CmLZ~BJkZUQ49W2%p zGT6Y!l=A7TxXwLHCB?nAEEY%4c{y{|;=kW(G3m(BhPvQu3t{5mc%(t(@oz?gf9eZQ zon3wkQCUOhBFyoi!3M4qGph*|0GHH)_S`u{EOT~SyTsM@U&9cqyqN_DT&sM3=KwSP z$aOZKU#e_YRef6s>%Bc^iOWv;97q)SC3l35NKU5d7Hp_HN0D3L=rV85IZlZZzhjWH z%M#va!U3B=nNY5!{C~kFzh-K&Sa4;1f9g}%ts)?=Tu@=(WCOZAE-5a#bp(VC=KN{w z0)d&L;U-jeTM_!R)PLcSKm3b83UEk|@%dlyy{9DL4)0u}g}tM5*y>}M^Q!5FXaHGn ztb<@`>hlnUtX3xwq0UMKa&d5--IXN2{MS%%7N{zkLn~JJmw~O5MRYQKkX~|hs)s(7 zHApmF z)sqSD>C%&a;`aW`_yG9Kq6<~%u_*LyPuN{B(HZK?X8dCSa@mv5jCrvThQVS0B(Gu` ze9(e#ff$1s^lSx#EDPh0BRo%npMef+2vcCdO^50&p{XknTE5#x5ZtjHg>V`EZl$;Y z<_A;&-~?E7vgXGRbJ&LQ=4O|8<}9L_Z+&n6wwZ7I`>ZyC-k830mUW;nzS}#if}j;G z>V7}VFVK5!1ZMRPw4=YAZP>uI=mOYr;{oi^;U@ewKA0baUsFn*aaI_4@m z_(o;3b9=)Un4dQL*B?IwOHH7^N`cLT3M=;Ju}UB5pTS5;7Fsa24&ojELUOZw2>{~@ z^uWE_lC!+=pZYjZ1y{7m$W^djM~)-rgX&d~Y(ZhZxojzO3>u^|82=)-LW}rJ`#++! z$1ILREBYorYbro*Svd0TKRRw9Z!=2HYc}RH&-}T{=84Z~Ct%{?v#f{@r+DuV2gevS zE6NMw+h0#sf&%&Pb73A9GyZ0>(nc`yR`DLO?-Gk=K^|tYqSbx1+mz%`__np}w1Ku`Pss-oznHu$vsB2HWSyaWb$U4>-;Zws%(bFM>Tg0?@K}|Evyx z-4X|;nZ&sL@^>vvB!JzI{?%E)%-T!+rxe;DSOR5`5CXwIey!_lne$J-qv?evT3#7( z8EAf;#hj-RVj9y zeHXA$oj8B^&&0hPdA8ZbM!WJ-G4TWAt#y_01_*-SQ4Y&qux-jZ+T?MmThmjTl6B3upA+%4rz1wk(NtxE zMtfKF+1K!ecQ{y}Iplnve8VInCiODOp8sBcpvAp>p1xdO!`R(C2+J4ng~vF5?4m!F zYdLU9&#F`zjdMJLCMjN)=L!%b)V)rVop+}OdP zFJObOfjPgM1KMGqBf!}mn6=Xvz-;Ztl=#NauYI_8Xx1|XNM{|^$z_?t_C?M$*1hZwwT4dRf02JD%xw)X z{4ka{X*Y&_yzuegq%`#BsJY^`0?bwg%)Vjnk?dPb5PTexwBPN`tbP3dLM;A^Lb-FU z14ad0-2vIv5W|MLIW6(2~XPvkAI z?aiB_d(pr6Gea5B7jJC^r`0!bEqmv5X`X-?Rbx3lLJHXMkd+1?33diDYaO~8XnA8J zba}e8f)o7&K|z`QgLi|IgOW@EYsx{6hI{>~fTkl6&_F9-;xmscC`p^)$S5EU(DqC_ z@xvFMzlwK@eGdYzz})~OxNexCOd%53CKJIwf=0|FruWD~K0RO>@Nz%@g~tLY+Wm-6 z09M%_Ge8611`Lh>v$-IXAw&5WebGS> zrz$+ox#@l2Vk9sejbnGPGk}mWT-!JZD4tNkCTf{7Esms}F9ZW+Wt_hp81Qedp`CfW z@EIT;$H_dIXhr`!Nv^fScGxLRsN5X~jT0AN%^VE`#N0dqAaBk&!@!(T?z4kzYi_22 znH`_Q2(TTm;M|7`ds4w;;hIM5$Wagtz~&+x!QBEZo@G2f)W{=0T5*lnh*jo8PY3)oBzyZ;8jNs^Rx z^c|x=zwpTWtl|;Siu36k>Zt4=Bp%O5U)lNzw>mf^(gSM)A=MFFcvS|>is4`v@OfvY)^T?D#hm}90X{0Nk^tzq^1z@ zIb+JLRrrhL(ef2Z0b;_ra#y_We1V=ipZoso;{{WdGn@unuY2SQKz->{Vj@p0N>pZ&(?h~=ro%H|2%%1z3#Bi+Z|1lEGRHZ8jsU;wkMu;8%V(UEG1AwIiGxd~-&73FF<2hA z_Z%Ond1HYF41C@I%~UrT6vy#= zKUGs89<#UcW#h)XMHfSQ+W!JaGfOdsKco#n_mVS0ZEG$f4csZ-bV;7i!xZ1Q?(w|= zqrLGvIp2Tqru$$YUK!1&uv~+`&yb+9Iamu36PAV*j$G44PzRsc%=*l_hQ%O@G6Sp? zQIIOnWwU!?*WK}51f*UVQpLh)nJw+c5LJ>U$vh>2&d+imLuUan7vXNCE^rP! z#t_zBZ;*PS$aGr`BD^N4E?ObxQG;in<6W>$g)DF{ z+shn=;_r3--N|O|8N^RXCRXKT^XGD`YqrRc zaQP~7m0cgo|B=zVWVZ0n>`yU`nA5LJKl9iJiGdeh7l}GjAW*Nm$8}w+W&gN+R|{U& z&m^|gicjV1qHGT_6zzC~&Fj zCMP?1tF;YQ_dQQn+-bQLEyqBV-Bvk9pYo45$cxb{U8cKC(W=xfJxc7 zJJVvsmN+CL?LxE68Cv+b#Q)hp`*xAx3%Pza+N~#7o#PO-|b)7cC}>r8FV%Iny2WY zJIb%rbD#J1SUCBbC62nV`72UyvdJ+ext@{^>zvS8=l;z5v%0%jFofJ z#HZ^u^|7KkH7>`nc3FkJdw7eJ_MC~QS>;&VJXaO|nQ%pzocrxE`qjn{;&NSa{jOzC z)yD~@W7Xm1$y_2F+4g#XFuJkx`-ZwmWO@pguq|!dv}KGDKWfqY)>Vs5L{3x(8|}^- z`$Q}}FFI|YXm#e32|cg$y`{aNm&IFIpBnvpwpGi0OB3Q9$GisC7+#Ls>egOOA9;h^ zc9z=Sw48*0tZals$>>6Js5xxF^(8k9&_npVi8kr9Jz=FQs$29{*R9cVMdX|0`|p0+ z@mS0sGZs;Hrzvr|j^qF@JmpiMIz|rz2~G~Pb>E{Y0x@yylnl+c5E}LAelPP~~ z>GK@#W0%_9ZTqVAgMN1tzqXOIGT%jD>TqpSOZ*|YeQ|{N_2G^ugtWB&3#ntVe_f&* zP+r8?p5jkXYl{C_KpI1@-4!0Hi%7eA5|>tfP~T~)zw>?IiKis5I`Wni1=|cA95T)h z_-%`;B@1pQ@#iy`ch>U0o#^jv24*C*o^U1&^i_9DroTxr5;2U)^X|88h}=0^QWZ=d z9Piu+$Ly(!YkbkQT9TF+a93~1hrrQvY8gJoG=Pwmg?cllQ{tLJRVUNagoL=LN2LW68xeHDsGGLwIG9$^~shP zlBq41-?9EUFph5k!&&_rLZ)YLt?hDKz0Aa8@b)#@712m*z4K)PEg8*6l5;O#x`e+} zb?76$EADCK_|*sPrtQ_W-Tok-=${&Z2KzQye@ab9a-yPRwkE7nmp%u_#N^1sUrPG$Tw14;@tG$Ihx*Ow6BbIM% zp?XQ-9|dhqBbUXWj)}K(ur@HSOw5qQ7ymVDVnB{y*^;qW$h8koMZl3)%B*rNQ;%in zh{j#hL#l6ZJdlnMkq?#D3%!>9^Up&55-Vc5OL6rcCkrgzu4=(()5P1@?U<|_Dk|lD zWR8BUU?D+?kZfMp7+COqAE)Gj7#o_907v?bH{Bd>dTJZ3U|ba+v*%$`1y4{}O>}B) z{dizN!}ebU*%CancqQ$mg9MADU#y3$JC@yg%KB~ng-U*268yKPDG5ATFsgi8af(J= z+9?(_wiGLTd4|fq5uSGq{}t&N%%euQ9Sxaf!h3yD7@WpqO{D-f#xt&23 zb}20(5FK^$6SpIsh@N#k1F+bvc;I?a@t`s)vB9wF9x-jSs7 zpc}7M9uE#IwbDPif?IV&z^lcR;DJf0Jcyv`wu8$W&c9Iz`JOiRsMt0@0dra|*D!ZS z-Gz=d4S_-b_;~hOP3+u<3^2s?z4f|8iy;t8UJdR3^Q{$G=S^>GYx3j98xJY1<=b`t z({BVOY@dANL;15TB9^t^Sl{4^&$t%c?598E_@H02wbHisg&sOT`Vqb-Fz%#MZDwIA z?bRuSm4Q;)rR}@t#U}uKZ@}287d9rJ$xpOJMv45)EkI?I8g0vVGAgSby!FTy_n+T# z2H?oyP$TbRJ=$S(_FJ_6?&!+DEJtKp-}(w`ZIRJv+AFSj7TWC8UkDmcuy`)&A2T5# z1MYS46JDXNb#l~5R0lF&!4bFjkox^J)0FTMk(nQE=KC9#(9n&~z$Adb?BU4# zgAum4Qx5Jjy32l#0KgTUjsr=+tQTO_K|e9MZzzq?0sb`BN46zeWr{lLfVG!c1!i8c zTh`=OZI+<|xKrCLyvLh-uskpjZYG zezft;=ox8fDdRtP}_Pr#XZv&uVAeED$iyhKcoGYiv^<5M!Y3ZgztA;#tc!Z zIvuU9;EBFYOLcis6TKW$sl-nzBRK`HsejvEn?{1jgkF1os9WtMK|08*FS9Bwhvwac ziGO1gi15Tl|Asfpl{Ikt!|nU;#{duJQQml3V|=gU(AMNeNoWIxd-q#sUBjPpn6@|# z9GbL}RYD{67?;}Qg3y%84Wys^H5R&Jo_7u#>Tb3CB~ISj%35}2a-XnQSbf?m zPvyP$9Z!v&Q`&oGw6%$P?VAh$VuP^?t1k&ye0_b=$r67BBoGY{`>u} z^#?{v+o?bj z(a|X5#`Y;4DYO>^A-+*p11)|_AHhpf0>$+Pz@c!UM`ralt&Is?sfSrv82vO*=Ydte z1xg}TCMLCZ(4{peC0rN$!J#DS55uz4mgJ4E!~V>9G+p8^WY}{=4-?;q`j{S=X8!6F zsr9Ldgyndi+_{K(HV>HkWn)V9;`MARdVWTCVu>%QwDUc^hUopEB-Kz?2yoSVPHVdM-dF;EoYDnL4W9t?Rwilqjaip zx-76W(2=LWy;kuW%7?n9rMcmAA+D@SkJ%Gwl<{z^Y-qegclq_oECUOBnlL*1+0CxP zqy?lc=e$O@84Xa5F2Whv2IJ{3$C331Hyi=(^WA6 zVPJ?>DYNuN4QQ;eNaL4DTh^jeN?PHQLGW9=8>KPOW%Q)oK>N1pwe%@;!v4yvZ6;ZL zQUQj^LZ58YV!8wRD}Dr?TT0?jMRgg*Bo5tnV*q=AwyE*dKJNxH+<*47A?<7LF#?EC z#yo4dD=`P|)i#xiGEk*A#FGn4_yNQWB(}G@%a*n$|4hzn?@fC}DoxXK&!a6tXTQGL z73nMy$ZK%ZtZrKk*(M=w_^Cw2S<86phT>PvS1@~DP?9SmFI~SxLhW8X@V4RHrCPgL z&)sPt8bm4ch(hm#LiqaD#e;~)Swu{t-oQ|9k?&MAO5?6>icEveslG!4U5lcVn#9V- zgRQ^Vh?`}&iORnbf7{sWUouvzeOfxreDnw5P<-vPR~yOFZa=CwjYC3=+$tP*{_Cco z^=*gPw!+aXd10l-UfbUZp;p)?-oPGJdV_RqTEpuX9Dej{taxE`eW1?III5yty;c{3 zQbn#`{JE;`x)n|SZcwsuISTV-G_nNm6s&%8?DZqB>k9ahuY<-xa4~DvMsfYwMGCRm zn4FTQ;G&P22u=N^QTcbfLilZ0RX5+aJWJ?R5{?bKFw!0W%00lsyYFrFB^?=*>YbkS zlmwlgYt~(RzBLOKM6MFTrvy`U?6&5(O<&mUJe^zZW^iWOAglo;XOOik^CZrG-;+>6qjr3Ve&qWC zaVMSJ!})fF@9h#?wman_7B*3bm)?F9fW-`1$s8kR1=!>aIrTZk%C}m}Y((#nW-}Qh z+-qflQ?8+{Ha}4{)(t2#Td;o*X;4OKbYC7PdY#h{t(a+Dghb|^4qzRQt$KdK- zHLO15p{jn5CTAkm^sVV!KJLnLHbxD}YY$A3fMf98HjS0754G(T}`_Z*vSjX`}enisi zbA6ey^jqXmiGZMEdGPd2i_j}cXGHb7r4$@f(7GG|*#tf;-^X>m!sC)0QCYNq~7~O8spkNDN0o1(}B$U|bf})6ufS?4V8Z7iKO-kI#16zWi zFA6A4L_md5q<0MhDosHIq{ILLkxpo#B;PyP53225AyTeKW|innUY@fmw-AC_6{8l${@Y~L$yI`!&WWO z@EtV5+Q>)25-*0`I=!&)n}QZloqd|Ffbb3zXe)Xl2MH=_2=)^?s%!AXm5mMyviDDa zhVgAyB6$gB#PMBgE`nSJy*M9u>w0uK!otk;E=O3{==WkUXp~MVpAH3i<=>Z~EgO0| zXm%CCrp(0bQoOA@f24up4ZvQGWq8cr)T^{!$G=M~Z>m0B2D&(~A>ifH!1V|JA@Hsm zpz4&miTbz6pdfD<&9W=S zr*PE&5ZDW&_q0*ik@^|)1+^IR04!bxE_XYQTQDyVECprJN6JG%Rn~HT{rAZ1zrE)_ z?GT)(Dw?T+VWuV8fsNzCUfNRF7M!p6?#Tn|mg23f+5~1|XBquFLW?D$p?x>k%7Bu` zqe}^(b~TiyRM-KZY}ImT{aLz6O_b->GLG|4mYsZ->R$xnGXc>h!2Ev}{e^2HJ*So* zBtR9oGUE!iW&w}~*6bIB4IG_6e&Q!U2|*e00{9N9Y(O2d-h#OY#$@qn10|9G@>(dC zfQ`iBW5?3=6uUOv&H3P7!Gb)3BFs5we91P0a>|Yh*o-X(^f?~oMR;w4FD@*mZ0C7) z)%oK2|6SaCiS9XN7mb_gap(W->|e`p__ywi=6^IF&t{x+h2lB(9$g02w|ics=j~`u z-S+MNa`B2N8EJ?E)pdU_p9d~7`M323sB?3hB>(F0yzT@T(9+Fk7Nt~E`s$dar;F+& zU=4aBP_pC+DBNGfLQwCr;wh-`TiA3axC6={7s?#qW49Aj*DoFICb<6i-w&j{<6i>a zlgq^xF2DJ&^ncO1+raHvM%3PaQfRVfw`a8L7K_XO&r5TDDH^~_Ea4$7KUqxB6-Y>q z)6Zt%AM9nF(@(w^QxPvY>dbPkrIk7VJN?hCC>-Ddg#*j&fI}NSZ_?R)$1e{4?%C1irW6M9KWjbUdikM}pWH!?E0#YA z!9QqHUE$-NoSzpTm;b%qzrRE~unJ&jhJNx^nE&v|vm!}kE-qZ-V23k{Rm?w7|9@r& z{$~b#Lr)bwMTssEup<5^Lr}m-1){?H3sGs0crfRmd|X}{XcAH^;U|-{+U?KfF-CXl zjPncR%FPE1Q8OdbgP^1eT(??Q0>}j5msmGudGv(ErbFfQIiRH@Fq?e`DeQEx`b_ycG$*6k#|vh#L5st+w1Ughj=sFZT_ZVPG{U zEiQgJ^vWenVFSXybxG!(xyum59Vi3kRHg>>}qaPnlSymj~2fn=~N7IA6i zvNF+~wUqQGyP~z1Z|+;B5dKZA1e}3igw_8~(Etp;FO8zW<)~$9{M6z@#ldl7X>R7f zj~;;2AFBD2&+^aFz|T9*@klP3b$rUtlP zPG6z2IJQdtWZH`EbV)5~M>Vtj`MF+!pE zD4%^djT~g(6+6Nqz-$UVpFzgGbz$T)Od7HItZSjKZ-s*Kh3!f@pROm^7aNXZ!&WwK zRVoBG@L(5xmXq=4hhi>y>j~D?TfGfM8lIc?@?~ZdC%`)!RoSwu-hp~?5IgXL3kC(? z#svO@Jdc#|gN8OlqS`$(Gwi0FcC1L^Pr`-~aInSXL~#40jC@*{KfnpZbAUo9SXrF} zSIWRoUq2nk-!m|XQMrM2o^TxtYkkRa-BC zbYvDIF`n~7P#9BL`~j$zCTzHLx>x0$4dLUScN}U4nJIUd;tRsqs@bBj$f z&s~!n2b0!l#MU3|IiFH?cZRU$w3xdSz#h^+1b;6aX2D+q@pG9AHhU8vODfyiLK4ST zyM(c|AA2&(qBWoy6g(Mr`D8C?yF1I6lZ7sy>~R_X-gf3>M$0F&+6E@GW={5K`D7La zYjR&OC$m^SS#r6Dz%!=F6rZTR_bQ)$F;1F=Pl{n;doN|LFF9^ zJ{+$H>gF^bdoy@Ze7jOuo1w{0jLZ$GpJeD$&;k#6_U1C#$hcUv#+@mM3)y%!otq_; zy-n1`v*8V%569R$WJ7etcUW37#VTwiJCV=+RL97tz(ie)c@pO{dhAaLAri{;R}pLc z>cRdlIPYv?#f^MCwF9FcUIxM~OYbzMGW_ots}u@5Uy_D!}1G1@5n= zrU^Vt#K}7K`bOq|PZ$=hXQxKkYT{Uj$~&+mP~Hz;zhQ5T`eO-YKegT=8Z$Jj7m1vlxr0Gi>*cy^_Ry)=KUylY47tSD5{O?PI&3>_rfMus-m|=vCw+ zi;4;gWcGPcRf1z_{}PgvOd@!hh0d+We zdC!zmKaTr4O#aBPFd-(sakF3x$A*}^HM#9z{|`uze1V1{YUR?0uY5b})=f>(@ZQnR zhHoB{>fHID-dQ*HI{ zz`I6s8*+Vwo?Xu<=eQRstOOPRo_cW(ejT`hY7s;fPmcRl#Apn8P&?l1M*B5b43AuC z&^UbcF?;FHg6&>1{8sEdUPC0U+}l~|3daaTF*aO~AIX!{o$Dj+ojYSo-RCP=gcHI* zsN!g?GRxCqo#4)+il!rH8Zzr&?WZ*m!*8cpmbXEu0^kE!Ub7re7T zaKRLgpX0zYq(;pnjGL9N0lB$u-0x9=g7>onu4AoQR+2L_jRY?Dv-6AsTI!x8!Tv@D z@vChX5ubAWF9Rw~x;x0@&0sxfyM2ZJgOlfE*C)0{Iwh=wnyK1#rKfmgv?CO|#@!u5 zA8$Y;fGE5F63UY?i{C)PC@^mP86@ECGX~@dvxv6*EK&(vT?%ypL34=2-%6smj(O62RTQem&F-8U1JW zRmtM*WZI3vnqu1m5Avt0dMgC)%`*b{1D&T(5u$|Dtn-8+znbH=1_wTz<2UrIyG|ua zdauKi-z+Q%$YUHrvSV}>UX=kXCPu$Q^u$_fj#B7p6_z`#dsTu>| zazdC>!izqtLxi6pcgONTalLi%6W)R$#M`GVARMhkSg4Y*hCaZ9Bg8WdXU)EXp5ZDY zV*P$wdCoGgqed=T)hU#XR>kONL;?^>IVgx^memjqxmUCo#pf?_g?0UKa7` z7VnYJ;u=*PqgTj3or|mfsYP!rQUW68mebNf*KGvho(6EgcYq2BP+~E24%OG%J7y+? z%RHX2r7d@IcfsseGvQ*NtLRi)!qA{l)it46@w1iStwk#tgRp}aCv2g_7<&zrPb+wx z2No(&^6(OvZg7kADs~j~gOZL(tmh7NZI^)5lLLun&joU9cJaiXN6l1lrZ)*=4QU?k zmRN3Su>up6^%}@Ye^(#(5{g=6FWW(+BRpFDU*5RNdx^G2FD%VZ(q+XtoB#8Y43xT5y;$?^RrJ6uBM2YL?>s+djs*qXLvMFqE& zaj8S}(Vd=8zCq`?wi^0jCi7`RMa90ux0Gtk{K%KLO{xExNh~`z*`832=T;K(GAz7K z?!XnUP5+CKp)3a=BgbdRS_spaQumNLxK|s&RUDCIc*k=S@9T5z2H4>p!IW_1>f-Gs zCF*pkB1|JMqFZpE+zv+kjaK>SC`jcsul}w%IqFeH_0=_}D4*oodjY|f*nL(|ZqN^_ zl1*$!baw5X%-*li(wXvy6|`NM+!A}jFFjLRZ*p|}$O@pxud@!!GcH1^DqlrtkGIQ# zT#qRuuZ@ZZl=M2Z(CUq?2nL#w-r%N>bucA&D7AEIUbck7kCR*eIN=BGE~exTeDl)# z9XL*N;<(Gm94e$yWVU2{Gyi*9O)*bMiAN}9r2bE(KQC`=MliHynx@sl^oBz>0;+<+ z<0XU``vG!niKb>^sP)M9;oq`A>qpSo^WA;SDZZQf zcCH58bsR4Cn4u!13-Z0+W)j2B$xM9vM4mi7=3nJaNrqA%vbRt< zPXxYMEAQgi$ZtUAsrbD=AMN4aCP~T~nnrKG!Pkty=OgKg0jl36so8wKzuf`40#Fwd zTcR~~10s3SY!2rr%%pCsJo0EH<^1<~8DoxHXEg013m(Ix>#Q+*Xt&q<>VPBvtW+gb zq>@D}=EaTaOB@rt9n@tfxsLM{0a@lp%;gN^HXFML)pSd`W7c<5aW4n4lPELEl7*cW z?k(LQ4Df!h9Ph2KF7}l6qCN83B#i85A13gi1Kwfu6q>G#2ch>XQkuCs{<5})BEc4y zob-#v=@ho~WmDwytRU)ee=eli%7>JiXRM4)H>>>^mM1wRYGPOv@8~jhaWV%AZNN&5 zioQbfcK3U2DC?k3j|4e}@t^WQ^=$*)VKZi#&7)v!4N2SCk*3ovx0x?G_po0H z(QX%+5Q^b)U2v2c|ekSr%4q& zO%rB_k%;gbomTSeamG#82^XV`XKriKheErk+Zf~J?Z*C2*c-w9DGe{tj1X9~}Az~d9i zLz>v9>a@^dOxQ%|)2fGb{w^_*Xu^(}n0>409a6uown9NPD`Gp+z{D;%9uo$iz5{Xv zv(96tk|m>-sDh@HcwEOxPT=h?HVLto5Xny;BgcJ^ntI`Msqz$G1A=j3=2NrF0b_Ww z!@U#fq}jvPh2vKq6nmk=y@tx$JxfL^U-Nvyd8V~;;%2Oz&$N^1GoL&yDSi)>a*pX0 zT$%2*0nhUWgClqRq#s|smDjet4F=?C(uIS8#6urXun!*74(!h)mIn87JLYei__5P- zB)3L_c=o%xAxbP-pw@Hi@Z;jphIOGOB&bNQGR*4&w-xSD&D3#z234q3YV5+fN&~sj zG&ern`VJ&@kGSyWwy*RX;o{lEX_(;w+;)j(I~h~zVIaJh4#hr1uRZjMeN<(1^szQ4 z2tzs|SD})ba*Ub{Ts%c;*Cz{?3KhP?Ox6_hq4;i-yf#SfyOxwu5O-ZVTX4`{ z7S&Bhn(_LQ{WHN87qHZ{kBGsofZJ%f3nqHJn%fMmpzH*}DCqQ)zm~AN!vZ4tXn;HU zq3DCcgT-s2y$5JIVQY`v0k-yHxC)7!qUTPJCV34_Sms$mY7YnPs9P&uBfNgV1tL2QnEe z23(ns?+_%&ohG~9@W<|ow=7;5ruch z<|H{E+tWUlMRP3##@-LlEnJThSLA;YgA~+F|Cb^cXx_KjgSekT#OIfD;6kY9Z}LU0 znmtf#Aa&qbj6RuUpur(F8{1Qk1ayCJ+Ndk(Vf!`tbKa9J)Efpf9k@mVkqD2R z@Ai5{A8RkGZPUlPahx&MeB@_4Z|F20V)VAFq6%&Zu56;ysTF8RO|DQ=NrhOB0h{NT z$@iSwZSC|Tx+bR4$wl_88hl(p^0`S zfnn3TDsKs{t4EmO5S!JvNixK3Ps#w3WUiI8$+tqE@4>%JBqviC-TuyeEvPNk5N7gH zNR)oJ1dpGChv-NfHXPw&W^-3(=FpHxr82v6@&9w!am@Q_>sqSRd|d@ zxd}jQZ1N)y`eG(^%r84B`Ci;QO|f;|LEqH%rf_{dLOL>aBEb|IOT^)G3DUAN&C!g} z)9zd)KA5nP8)|fX>C0?yr%f)cM-;B34D&W(fy4FcF{d?EY9?qkFa)d>gbS;=I{&zX z3A;w#_>Sav@!~WEpDH~*apbR?ZNQL{khsy^gZG?+#yrj^ZC=$(Kv=yn%Ekr z&`b%D>z;+qrI*j~&itTxIgyWN6XOoXfOUj%S`T9m+Nz+Nw8QtVKgEV98ut=7;SFfn?7Wi%=VLK? zjeC<`wHC{}(-TwG#bx=x9?S5YHn)X1M$Hun_E|B!f>nVLu4_PL*gc61;wrlVjTk4P zVR=7nLf@bXBx|qaKp3h4T2%?ULtwHlO<{NKL*awoudYj7?)3!>DVr_piH~{M-doCPsi7E|22F|2h!ycRh6H<>!z5R7cLg`-dBQOfhpbg` zKb2*bo#Wn#adZ9s72YP)Q^*CsnXBhCFh5SsgG9GPQwrMpf*9%Rz=w1MtV3E;ncUDt zMfC3CvxGS6fXQy8^9)j=eemcNua5v0Yk}ro9ny?kBy;g@(dQovswH@ViaS;Udz%-rsCKDG*oLQF?YI486Xs2Pu3$e zOLE?0)0CSn{5Y=10;)l9KYE`V5q!iJRUl9JSbRO%&A`>#aOCjF5CS zWW~O$FFCniQ%6G3YUCI{$zm3k%+{#(7Sk8V>Q)qM0WUE*Jt022lS66;qy;3cTDna`Y}I!Ns5rJCr#of=MKd;aopUnXRp<}yn!9`3 zNQ=#wQ&y7NC9m*Hi5~yfl_1I}(z`l);kBtPzl4?1xxd{C`?u+n4NX})dWevkQ|5LV za8t5%O0%<) zA7QZHO$vYZCOoYuYuZ1uloOD)!%}@hn%*&-p9rZ1m$7x~48_N@hXZVpxLqZwD)+}R zTpdabT0*}37GBA$9&ad7so3qa1AmC3p5kTu-d>yRIApc+I|v|o8DpOic#{SP$hzb) ziZC=~od|B+`$JSnwE??>oH1!p#dp}GGSqq?EW5p7mD>eY!a&s;N>N_xq>l}kByw+i zV`SgxK1@h;+cLu0>nb)gYu&Hh1`AG>h*088H`<|hkZWSg%<(j$Z}-j=`b>LLIW#oU z@X$MV3^1Y%(QWD!gIR;NIg&x_pz-{z28DkyJd%>CCw<0BOe|DT1*L08vrt(XAn$a* zTODp~2m1paZ!vDuSAx4WP}UCH#y`@phOKztZa`LIC@@O0$3;4o4k-xaPI7Jawejbj zd^1uP%%J5%TX87wvLAbDaN;#mVkdD11W%Q9hUR}oCq`nC> z_cCj5lLK#sM5>{quqb+>R*0WEISXxpISo`r92n&I%z`Dl|L|(<(W=f zmGnKWaK^>5pfIH=6oT#PuVN8R1`ln=Y@U(OZ~<)E^no`^MW)_goa(}_ygx>hts zL;1_E^R{;t{YFyM&+W|dbQx~7nw{kVDHTc({rs@v7u!R61)0s!;|&)}4yt7pwn>3Z zM@tCvm^6DCU`<}w#~_Wx4A**oy&gq+(}(U-^eN@EDq>uU9SPFlm)^kj*Xd8pyxS3D zlmbGakBY9!>qre6C;4o>_Y!y$-6Z_2iBHiVv?sEaybc-oJ5o%_4r9rAN=P-ODW~Cd zIz3eLOLVw)8Bz1YG&j9plX_>jHa%7$J+()}mvTCY@)c*5Cw9ZOx!2|s0kG=>i(To+ z@estIKKB0CA9|Cvc3Cc)$j)iRTFQqi|9X%{1D8_vu>`mr^iDj!ntG4ql2W4hRf*@| zPMvHJWZ>v=XWibYs%dUmdr;R0T2I&NEHo%KkUu{ZjpN3qj%qy|rw{wlDn)lDiwfFl zU(7$6`>KCTs<#Evt;h}CS`NL_S`faK<@u*9Pw+SaU3>NUr2~z7vukj}(4FZYUl9yi4Q_@uQNIs|>wDb%#d=x` zH+$KEGSmkG+eST3-IzyPHYuGd?H?g%iq=R8NdU5e#ykg;<4xU@!AS7G{}th5bTq8@$rJh;hj9-*1L)t zz8c!md9}0*Zx9-~vs|J183X1{a17wiTsQ1gd~>4JAh-&bH&|l)HM34AuvGa zVH2@ev=Av+bIs$^3;XHE1j3LJjXQ<-xk}xE?D%>8&DG|RG#^zrC;<8d6zNKW{VQ+5 zcA_pJ^|p!fs%FOi$|vtt8r6BDSIBBLc7b zCu#gm2gn&34`wQDbbo@;+xt4K8|`{=^Ec3d47jQ;_RR<-p1MP|h#owK|3wW(#?{e{ z-&p+~^>Bv@1d-re(ba(WU* zVSL)b5T}_#4CtORVL~PxpEl`}cfPPsLqy|2>Zb^6+nXIHBry-;9=l8 z)f?ZgzACqcBJEa3hDu|lEufZAIPJe`r~U*+=B%ipHB{eoOW;s>&I$vtDc}hP`&Y~+ zs%y9f20b<`bxvQ`&KoOc3R!xsd_++za~`$YhVh^3Mk^@>_j` z3C^E)n=*+NA778-)*dt;v1tU)6#*X^prkAQ{*s2|4uMTVen;k8-GcYJcg=XRwr9bt z(x_vpTmUp>a;hpN#ioX4C=aN?Aj--O1OwC#31nL)u|c)BdqY&{pkvBxLCN0VvSCuM z&Y5*PNXd8Wp-D>mk}uDrcA$}MSw!mXc(wc8O11;vMbDOP&4Z`po}Y3?qVmbH!o?G+ zp>`ZVhFl$-bhDF^*DwA&ndceYjTQa!4b~7YAQ}K!pZ-qcI&65|YVjfO+gV3_#p+0xaOPFL{oF_BdJ?C^jA66LbWBA-YdZHAfQd~Edg_owd zcTYX>{G-bC&lVK#-9FiO5Aigqa%{oHna3PFM_C@@cTh;x8yXW}=x20@uj{BN0S(Fk zj%GMS=(_DEnGU4VPM51(>(!oJ*^OM8!Ge&Ec%x?aR${AouzXdJ>^&yXzjA}kstHtq z#DKP*rF*pl041}#A8!HA5o_;CO=qD)K_qZ+ob;$VjVex9?q{_yGR$$Jw-({|H#AtC zcpjlvMM(90b=qn)-(21PcDC}14WJN-nU5>CgQi;EiZFCFtn^hjmhph7 zFi%7pa(pXSO+{#wyojKKXEP!N6Um!s=yL~2Rz2jGgEfe`u%_=23(k=@1CR7D%F#i4 zNWQJ-5!9W`QZ#V(8ZeD!VxBrh<9!(sIW`alS#DG)ihJvSjQO<`__|GD>(fGWNI)!0 zS(sPKefnbKkvR0``DNHbUS-7FG_^sbFo>eEh`V_OBU|@1lO4jIKQP-?tx)}NKA6VKVg)(*Zj;GKiCodPHl46F{&gF5yASgx zBhNsutAP>&Fw3PEU|RcDK5WA(tOQ#U)GVMQWkh+s?~5PJ!0l}& z37Tf|E~aEX&`0L9-s`HW@cJc05khbzB z!@K1;U=zIt?7bZSQ_wz7ASshL+kjm=XyR-1s+eIjnSy!e#pVrYz`2tAT`$ME$`|9N z811|PG*ffGzuxvH#%<$lJIt8XKqRZpm@oGbxxW~YyOWiZXGU}y|CpXm`O&&1ooO2&jOeYfZLol{1q+d1%&Pl6YA;fB8il5( zIR`V9r+^azYkHSxOrw>Up&i6Wp_HW;#-=woqdj#hFS?^0!!0|^RrFh!M-_w`FbEyg zhQPURTs=Kag08yDb9-DK7_pUxAhw$7HzuUk=r!6KldG!go3tEuldWBbek8qfQ@M>- z)J<(kja7$*eD4XidK~KdF>p*!$p@01mgvLh+VQ$>ZV6(k+OM9Ogcap`l;@JeL(4r3Km+qZaF5Mk#Q4%B9Vh1_-$Bq8HyQ~Uew?EE_=Gz#hueoVM7;?VIIq?Ib+d#Z)lG|M|Avg0Iy?1^0K0#H^ z*OP}=2!aj?bE$M0tBrksp5-fAOFb|`zdG3m1*6Zy56H1>{%i*Eg05dm^~;mGh6T;F z2$GI4Qc%1tCN+nOjk)o_IKAmFTh5QlthfTa5z@9b79_JkJPD<=`8~>Y^V*>3PR>U= zX)H)aP&2|VoTN1t&{1}9`lZrpPSnJ2#d%($jvhQ05G(XqCay&mU_ojVy&kzVZqf7U zZrYUE*=-9OGyaZXPtT{1-)PSrGuvLA$UAsA!of}7_CPpg?BVt&LSSl@!K}54@!0&D+ImoYZph>SOPV1*10@)Q4Qh7UW&Z7sLOQ)c$av?yfBZ z0(MhUMmE`GS}DgNmbrPd=a!MUV#j61WGKLkjR7{6IqEoOcxFKY?mk7fMSx(-c}szu zyeR2&-_Z4DrToyekL$Ts$juEReyh-HYgec-w`1C*TLDAL^W%r9s|_s2ANGG>IOmao zE~8N9WwkJfDS4gqzKwxI!nzfICot{TF{@S66cI5IFPClCG;1f+%(N64yDE)=abt@% zz?_;rxT6Fkql^rlbAO9L-CMEAoSE}2WaMre4EO|EsE2o?J{uW1P#G1`fDck?BPm(S z`DriWCm=;Z1hzECUol@R$~W}^`o^^9dj4#zKy#c!o5Yd2D>z1Tna4rP`Mm_Syv+In zaHXcMDKJt`26UgLjwhz3jx@a=jdF0DQt-_AKrJ8%ZPheZHclYIkG6TZq?>&Ecaw}rxfk=R15d~>t8?Z92y6T$yAZpsY}}-rSlG>a z?Huw7i3N7DF&VgiD+#QiYcxIer2V1$oSB3W;@yeMpO+H?XE5Ci$W^_3*oOut>fi3a zvx*y1sfa|Nei{@8-yBc{G|CmqXX0#$P{~t)ObjAA+Sc0|kC~6c=Bj`|KCG+-Xwo)s zfiEu$CwGZ`Ju+BqA`VTP*yYURfSQ%L-D9s0R3ABHgMDg1)yOoFlqPMdFWmkZo2vhD z`d`zMStjQfRN100(%>(sw^Oh6)1>W)uZ)H(coHX!#U7jK}CS<@e~quag|)Pm>hC%*5yNhN{0L29(iA zOWPSk^?Y95Hw1Wt>Lt5PQld9EQ364CYB#i-|Lj{0-P z?eTGJwpsduNny#?yxLs5v2XVM*Yg?(6Hn=~bS?vBGQArB2T4Ei67(U^x=~q5F{-D;u$JzF@$MdqC)ogvntgrDvl}eC0|Cr_f>t$oQeB zcZ(+JHhPCa72Z+-fs-2_Re`ec3n^buxq$zwO^7JwYyPL zsYq^(((@{+WFRibQ|QZJd9RSBu~(Yx`RZ2rE!7skQW(?GiC}|Zus&$P#@_ejgMXIvk|t z?d0x$!*QmR&mdX4h}s@5G%XjTwV>yiWrrXU11|biyY#&!f{NJOULoVs2;-d2A-tq< zNi3A)I*UZKb!`cte+Le=`3Pxb474$ObvQw=6h#R5T*^hM9a333ni9yHqgV0!Q$EW z^dapdQ+@ap&1z2h@S&e+1xhQv4WTwDDX9@y42l-p3l%Q>xo-Kk!&N~cDySqQbI2?#Hj{1Eibn)^{PBid)-3zKfpA#47hY?o)l zo2SgR!Y!}SGEA!n7Xb?VeMyT+JZSB;ia&yBJ(;KbM5>6(W^gz_A3Qc4%>3yu=73em zZ>)tfJBM+AfRtG`38S{-ueJ@YV{>T9B=f$2GMnFv=mzOT&@-v8o7qTbj_sfq{p)Wm zYZE*+BLiQYcG-DLB`y3rp4rIklKq@avM*A~|6B&fX>V z{K1w`0(lmxQBok!NtwC*U;g%feoxM%`q)eImBNj=ZQ)gc68;lzW0rJVp2S>N{~$D9 z{I&-|2LgIDe#HP&*Ym^SqU%&&=DeL$2&iycQ$w7@I7kzHeR7`Y3Hts6br#UzIB)N_ zPc!|=inWt3C)cGXqU)di;`g8&S;!J{$RLUYTR^I6*}KJX>AnvAKkQv{_8~2y31%PB zg~fxP!*5E;A4l!T1XxEeZQ8&s;`H#LPIcYw(%Yx~cE~bY!m$Mg&a$=0g6&n0L-Kwy zB}cr$-)?&2%wPVCpN@6u9-mQfK*)bY#GVB|mli3Qxcs=BzDsopUzo?o9MX!fMd0y) zCFs^hrOtOt_Lg}t2P6fJ0acy3JcavGGU={zcsJwS_1_CYyLq z$RmJ`-L|?2e#D@)TQVB~F z6^r6JSZ2|?@E!9RJJ1Ei{__9{L5eJv3zKY-mn#(AyPd8w-mc@IRq-Z~iZ1*%HRkYe zuq~3eAz??snc2WEbo#B{#B|-}FHJMYUuNgHUI(W@FWpNQyjTlAQFY0l$E_H=eKV3t zybxetKVt2|f9+7tOAO+C#}=wAhu{N{JS>fFN=T1pc997D>IE|R_Mc8tzJI@DLrDLD zH|sf6iGtiIt6h~SLwSb$DC zYiaRkLB0upiO@mu2d^FbbHl@X3!C21Jl{KOHIM6SHd|@W{(Ts0u?LWKu}UiGoaaZ~ zlx{mh!LbSBlAa&~*V4{;N(iY_AGVaS2#F=26!Jy{LWt{bmd67CkNF7a`QZsB4skNq z1SLkFbtj)`t}`XXnY}X_6pz@a7x6fopR1(!5T6o7tWw1LlJ+%8+{bGa3mmp3$qS-Q z%RR3!(fYk1S+Xq;6|c!s0y$bB`8h5Tr;9xq>v$I}n2=MmV(2C-D+10_Ya+4;rIE$e zp}n!vHr?dKDF~}4V|*7X4Jy=&&^7GI&p7$4ofE<)5_&U|(F{YpP={qv+lNOmi$M~S zsq&?NVBCys(b>cK2DvBsgU+`AF*?dj=fH}00HjQVyF-dE7>SXBhs?+Je)2;d^cTv1 zz=MCAbzQgQRB6rH5{yBEg#o#>?Zd-QW>o1y&ljU#jP9?PQvoMi5fy_rPT@l8y2+c$ z{y>faLAl3FdjV6Q32@wYV;`(rulew%b}DUzauXBbo$Z!oG<-R<`j8kMD-CWZvzI`y zKNL<-C~>Fn4>|rV%jo`Zb5oRoTz0Z?)wPtYmM6K>nqF@EqksFh0;E2G&jSv2zAnl- zjAlDr@TPEVdWsNnUw6ibf-8xvY2+%A*w*&pG2l*;eI`9bL)YxEbzTOF^*X^S{+4$J z*Z{1zDEt61dn{L!p`sm#J-3q~6FT63Mk&-Fua?hM_EclXb6L6>5vS z)7YJ|(t>)woX&BP;KR)Jn9FROOA5x#u+pY5TWvVw+gO6f7H4vD0a9t_y4z#2TBcIX zc^XtiX@9b==2kI`(T%?0E~ERLOLg-+kHo5z!>f!5QP=szbl(`1*5|wap4Oq_LQ?rNG6O$DeCW=GOrtckME)ybpWs=KvHpei&e+Cn$N#!a`$pHPC0kw zs5vNSuNaS1zrO;rHR-%vhjDq>0|fg22uJ|rcgV#F82h*%+b_9}PrS<5#LP?ML0DB* za$SM){%kEhZ>y5zcdUl2^L`BIgy%i+l;uASG$XpJ@tfDEd6eVLJ|mro_Md9|#+*S* z|2wR$vTp6uX#Uj@pl_!A=NoYJtV=n6lgpzU`^9j-fGnVCyxpAal7}vGIvnlcN;FFIUJ&;#q<>cvC{Pg}oZklN z5sV&v+MGYThkm{;g4Cg6s>u2JHxS913Nw6wA^c(m?=Qw96P@N=Q$(e;-EE? z&a))u`z|x=4m%6oPFAa0bj~Qo&EducMW_Cu?|pKWU*kOq=(Zm}e)Q>#oy$hYs#nTw zKj9VVs+&N4NPOE;>fo*xR-)T&pyV;iRA{@5`Z}Z0utuvx@HCv)8@>`WW$Qy7o)Vun z7N31GCX~N0wajKmgVE-!`D1HniYgG1z~?yGPYh;+Kj`&!$af^DV79acD=rX z8fB`eswc&Ft>uVCvi)b5N2+?>!j+u-AfpUi22B7|T7uFNkKTQKHyjFSe3@g1$3uwC zQ$a>^2O=X0jjQO5?(p1q+qN0WYGAZ-ox}@dkwkTHv%I%ai}cjg(@EE_P1wp@sMNn1 zml$d#j-WP+B~v9@>T(?mDC(tNfu=5TkBvnkC}jI7zc^Q-=go~E^m;nEc|HyRYY*a; z*jtpDV?4|sTz}YK&54ih?&>i1fqkT|LhWUEtLV{7J!gqF`MX(-U~j&O1EpRs#aF%R z9XV0dfVI4BnlDjYkC(5oCYfR*+vV&3b+_iP-XYnS#ivTYzYMtV*In9BTu&9E;reaM~N>L&NYi9=y0oC;OKYMLoAyWH)_oX#NX_L>wL*XSC! zMZKTtZ5k=&t10#uHzMTIFNYh8C^HYKLCoaJ+?mrUx&xPTymYUCS5!2d%^!{^>2d4^ zMG_6_M+iBz5$lwk2d=TE1P#C)t5aFF4*41m4)5EmeoHLgZIhPT(JjJTY4R+GK573# zGDOKNixR8K;*LiBwAt|ptqU{Yz=33%7T76#iW@4lO;9}hb$f49`!=fNoeGnwFAFRp zptkU(ig^e2)=zJ?B_X#m+byqLmu!WyK5H#4O*#KpwqWEIl=Cz}FkMA2;S^NC@Yk=j z@VIH%P@`2D{qTkT*sHFQl^r&^yZCx1Jyr+&4XiS&{beaQDWFz>Kkhin5M^>z#4U4@ zC$m&KBkl^ub8W%2ixlY+P0hre+EW@mWNV`l;h5f01uJCzU1!BmMUUFN#ykt94QK<~ z(iS|KWKhNV%5`@8sj=-Iv|DY?lrz{J!q@PYk%_*ex`l~l73e(gKu2$Cb_ysjMcbZF zY9HK+-aYS#>;?hQIZ`I>?Rx23-a2hZrn*{t>)P`hKS*b^n?Ms9cw};QH))ESx9Crd zf$rBXYd#F{tU-VAP#=|tXuH#T*AsL2T*qXSB?GT{B$AF_cA0nW8q$yay!`%}8ORMIcFqMJ$_Hne8=F9LJ1(}yc z>8*154BxDwxA}!}9#hbxxGdRbs2)%OH!sI(?Wr`_#9N=ttN48?A)pz#c`mFp%mxax z<7(aP%mp}eG8`jm(E+}k0T*<4rAU6K%Yv@`W*WTN_lz0a@{?L*0#=-4m3n|6 zR=?R+uFYUdy7}qaB+geIBV0|T4#@Z1CXv!6-#mt2PMW$-T9((O=PSyKR`V((DUQ8r zG};eZrhwM~Z@vMg5)?|-di4RRIOv7c{$4SB(^EsU6xy!6l(nEs0`WZLArs4pcrrF5nEXiX`ailrh;!U!=~X?_y^KiSCdEnYWKDmlaHy- zdBPf?lwBZYIG0$TnQw6GqVj&2#01LQqIOi*)GX&_MOE+?nXZjK?}?<9L-*!)$Ho$J z)%Dq7TUf<4=OQ%3Au+BDs{-!q8;Itt<~I*ekV8b6_y^~&f)d&i2Dxr~C{7QkC2sZc zSC73Ft5|NMZ)1Fj6lP7+D5uCk^U&# zF@k*DtLN+--?+i!B@-mCfob$-nQz9px{-(`4Eqr^9b}Bk*)>w+MJ!MJGJu_l=1h3V*VyVdk<4 z3t6mW&>zE=>WYllexS57pyRD|nnUDEq{NXwWqX|9_cC9qSKRtrhePmREGRWSPAOg< zyd=uX1u_Gbh8QGfBB&`17yt>QJX!27=apAo{tYird_GB%GN82c8osLcfm$f9uvegZ zv-N_Y+@K~tFYXcOXZZ&U2{~|MSufc=#pFXP?r0@%c*DL>3321-&Dc7r`L)iQ_l1RO zpaRk@!sl0Wicg*D{-}YRJKbm2Wjr2kzrHw!mjk+b z-4$zExhbMl8c@PPL6J%{rKsq=Y*MV#pUh)5)TFk4u~_6E=-~ z*x^X|Vz8;G+2F`+JC$5_SQ`LqahP*8B~~3HaSYxY@Se|4`e~8)I$J`6A2yGCsck02 ziYFlVZb^p~a5eF*e~D?6{+6?oJ4#pE*j<@F0dpE|=?G*#C$&&{qx}8fUOM*9sWzv{ zd{N%R3bt|6-6GQ9?VtIR%Ffs>9Moh3R8n=FRnfNz(++VCHA)$i6^@ZzDE|uZXzr1r z9BQ{n1J8dw#$|9F^>>SRcBLhdf$cUuR964E!4X*bmu==U;p2K7UD-0`v?VdS@cz6; zfJ;K>Q4ZFwWFn1q@7svKMh0NbHOpDa~k|4o0>=CbG0WILwXynl*Y9A^NqF?Na^V18NHLtSu|J!=rW{;N#6+ zYbCxG{}^)SZB^`Xqw98j8+E2BCQAh36XvoWLv;e^7rF&~dP%+<@GuxYOP za;e@A1@$|&5=2?|Y;FV1mt3ff`H|~`4!gT>*B8eF18d5iEc$1R9tv}n6~Ax*G4os% z`BGTqJt+1C-SgmL^}p8emM&J|FfIreKOSA8tymxnjMy$6Xb#^5RrtR^RzSMg@5(Qf zI18m>0y^30(yZ8P(Rx^TNzYTzLrZQtce(y%AyuIGhfx&@ciQ!C$_#=SMd&_&Ahg-D&4BY+-cZ4g}*;O$=%i*GBop)wF#=Z!a zT(r0)+JDjFme`dAI+?|$Y*n>raZAJsG@|2px&(0iw*l$}L0Y=etXwP;9R1%Azf6}e zSpc~4pB`&jaFBiYmdpciy#)t)@%nzu)-BTji#WenRV^Cs|4g73f$NKCf|b@Z?b#xL zd$H`p2=+3Cx3Il)MIzqGaKCT>BE!mKu~5`clRUS=PlL>B+9fQUdm>~3@?S*N5`DK= zb{FB;i|Cw-ELr+!5liRLhX1uBEJD?nEMZ<#&DFd9&%_wcvfG*2y}V;ItOxhsczq)E z?oS0J@Q*Y}pwMEt5v2Ui)`6<0WmPOdTn8e`L0=eX^|*UjJm_k)w0P+gerTle z4eMUi@{%*=MX<%UuE26YHQHZ{q(b@C_dE6ZXWJ&-hdZXGdDXn_A;pYKx5GL=Jh|ki zY%iSq#`yZi7CyJlyJR;OnExT{`kMIHQ+4B^-=)J&`Kn7bURnA3nZ4TRMiG9umS=C) zgzta%@o(sEnQ3eH=bpy{$u9@a*VW6{>lB+jJ)e+hWobE)r8rxB-Y5VpV!J+;3qJ^M zP)GwS?~e(CE4V=WU^KWHW@8SCn?n~tF)Mga5|qKe0~(wC3FFlIzB*FvDrfIAp{G$- zboH@&fd^p?$}jjWpo|z~);Wo=={@S_%!V@q>2~K_z_aFyM<6S`Pd4H0xb0v<7}~3n z+EH=nSfp8xs8?hbIXQ!P7MOu1U$$Mw`w*;hjuxO*SMfkSjIQ;b)ug5p@YY_(l$FBfOS#}Dw<7K$(go9=Rrm>8A z;f-K|jpfY0Bx;A1YFD*9T+9UWIis_Jaay4nL@9~$zsq|+-22?|{`Y{TKe2$~K#_Y$ zq2)F-*WxF@h0e?+&qWOLVHbC6Z?746Ta!Xl@zFwJZ1 z@=S4!oT>cVXwk|W-@PaD@Pu=>Rb%tmrqazZJ8q(5fT_p>B(g|>?EqL!SuLonG^41! znX4-=utshIeGeKy`PxHZ)p0HBOM_lVH(X~}+`~Lo_Nh}Pfd@5%; zvb-6wXlr9un}oY~vR5PuhDtbM%Kh#83abzvu882BHNHi7S(9{LD>JjU%<$Xxp?<%g z%;EfYd%8{F3UUkG9DWz2Cl-k&t=l+<&OTILun)ZrVme0vwDWjOdq}FW{=f1x4p-ch39i^r?vDk^nYY*k&C5S&wT=@GP^Ei$36N|{${Y$5e zxaz+ndy{?rHp?d(CEaOyWg@i$Df>DftxM_CPCB2fO62_7Ugt5?w3~jB!LRA_-CEe!8j>G*w|x_Va~pmHF;Q`4hqv} zyD&PC2Hgk==FUk(_p$@p5vQeni5uQn$Cs~#(zuobs$e0mE?O;Ge7@QAK2u}K{RcZP zW;jTR&FYlfy8x?3o>G@4&Cg6aUBldNNMRVG1b?}4{^n_t=g45$8{U?A5 zm|oLlkbuuUgW>2(bGfBgl$!^m!f(Su!<1K~8_x)zFfd~j_XoNwF&4EWolZpt&3_o+$LRg!Wm|LwshjO5aX=CL^v#Mn^z_MTCr(1j9Fnr20J}uTtu^S<&zG&)zk9x7ds&qTv9rt_O zkHrA50KKq)A^7c!Dj8RY8|qzOnB)$9t2`lL9MNOMwp6w+m{Lt>hV{NfP}l_>Ba!RU?OMZSCOIG79?>PuKpnK&ziYY>3+viPW2VBZb=_Ox;;ecp`O2`j#Q-~M-vgzNCSAo zP8M_ItT9KcKce$dQlwCT4`Wt4Rxo~`Nyo1xSD_y5oofMSh&1>{%WSc?fZM@1MgYCd z)}$|GJ0=HSC8|vNxcJd0dO&|MEaE)6wmD$6FSs{dz`)FB$4o8K;$5tPSq+0Vm~S?BGyc&*!brbF3}(Zmok z@l_ZYtW;)5H~Ib|tHcy>$XXiL5~?}tyI3pH+R$e&muU~$?+B=nKyIw$h70ryGu&>e zf=4JA1uT#wNXEFjd&v?_{^wV z(8>T^J+}i>g^D)j=r_oGj6A($5ettz&DGkVLesN+Zi{y3{Dz1Z7Paygg!9b@FV7YN z1v5u^((*X}KwCk5?O3r$JR^G8%n{=KpRXmQgtVFr#9Zp99}?wZP=w7&Dgq@0Z=j;X zgoT(xj(*ED#?V_GzvaJrb*Dp*Px~4Q+DvBtQXRx;U_eUCuLIWHeaAmW4p^8KLc6#3 z0Cm_n9bq14i;=d0ie}xmma#E6EtYH-y;_+BK^iYU&oe zR7SzHEQZlbAM$nJsG;k_KM9HsH-zR0QoN&Ym$U>=!%^}(Dgp(83=3>B~I zdM=*f6{gCJN{NH!r-q#A;gt-z_X&4m2puvL_ha=1-ln$uucCK$;DmCIEcsq3 zJ?^!vXTQh5O!yzeC^~{+e5#gy`#3ll9u=U^>8CG&t{w1X9CuXjL6nda3fsJIkvO+@ zB=PRH>2Edku%-*#H#@k!Lk7oTK*{8?JyKoFlijDmci=8aNmG|=B)lBZ6)psP}{)H%4H-_}*8 z_kKmuZ=Wg)9pMe9GR2sKtF3xRuv=HH7;vO$dz@{zx5Xq&dowzn>PySiwC_Klj@%Q12lUZFE~ovri7FD5?#G|0ylIZegg(~Vi6&}zqg90i7r z-t!m8EA<@OW_!`NF{5gKL;rAjPJ}<748A0f%Sl}7b&vZ`q=4-B`lZLUS^3=`t2oOe z-9VQ@U?mo7L$zhS3DPG4JCZ1i2m2K!p zy*2H;SUnu_N$h^Hmb&roD5-4b$DH?0$l+#+$=PUEL>!|$hk~DW7l+!ly!&GMPe(&~ z!BP1-j^;Q;nO^pmn9;I!^}TlNqf&1!tcK4cQ)>&ox>Z~qZtaS+Yc>$@^_B@9*|CGusIo_WzAI;NyKGs4N9_xy2dFojTl1H+t4^(HP)nStPqh zCRhhwqQmY)qu$FyHbJJe3kp|_;K8zcu}5%n5jE7OwYIif^P6nKS4;A8Bs-6D_xr&a z>B!SCwt4UYiH%c^Ss|psy<(sBWHeVTAD(>2iA7~8H6FUmQ7Y?hkDk+#eNZm-d%4ue ztB8g!s=AjCC8(ak2yi#r3uRNOzR1& zI{?60JCLpv;unN{S2tBk2efkxRK&Dfmkc&HdF7$3=6!ve6_^b6)Q8@;fY%c>aU#>O z{Q^%vE+=a^VwZURZ;Qr5k7}s`+grtZR)m_vgi;osOvQsi6mnL%ce9m#aaEjHCaKyM zOqwfH6Su+~DQH%VS^1Pj>Q!1t3*sG*D4GCJMF5tCo3>@Q7rVYR`xEmMLAlS1C6@Y5C(Wg5}q+kL)ra^(rT( z{-i@4lO0(oz~z+g(ZBj;r5uz$`bnLz6$ZCs}X!qH!{I?&wMD5w^S=a{7QaPzj z5$QpqSWBc=r713qT6r-Su*9&exw){aR3nmLALu2Z*mo#vI#Wr&Q=?OOdz>t9qLBi( zv99lq8N=w{V!_qaVIw{3LeM#O#H|SunVjueu$qA*7x!qi8s9?R-8S~PG4(-qO0sxL zS4egkK2fy;x#y-D9QZz;Fw2uOUFpZ(wk^+&buAEm3(HKzO_c17HFy+Pm|`C(!699s6!O7^svo1#YCZ=mB1@SGON>G@)nQ;UmK;GbmL14bnusYFMU zM6Ky1ZGTzf*iMO6=4}1#95LKV3-Quu_}btqd2x*6_EJADr;%cz== z5+2ErDD+sJ*c$@o?KE0M*L}dHNza}LRAcuD)Y!1I055f<1I1e2WSv&)mUo~VJ|F0J zs~iL43o!o;BkAEvH!44TC&7y`5%A;kptXV<$lb7-OB~Q$w88OyN=OwBy>Ha&M7*F| zsH=E&G+erPU$Y;cfv)nox~dPx%6|%LDr{geG7^pKmGIS!Uaqte3q`flGw@0#Zs1@6 z4XC70uzHkV7rxB_wdAmTOZD_Z*MQ&qpT}|mtO8g^g_POydYddY{ut3ITg8~2 ziEF<4t|v$zP5>@|;|;Myr)ilVGw*b-<=gcX)SCgfJM~O4ezbUO z=YXPbEZNu^hg#~Z_hZDOewsA_ez$h_(bp223=I;vmq<3~Wz#vOp-+iYpeIR{hczl? zNS8-AcGG|`sSmjSff&GsR`B?GR?A7?2_Spn%}d`KkyMOFC#5S_D0r6*hHGgiqG&Ti zw_|tw9B{A-RwE%C?^D9dIcSax(;2~6R!{f#e_HNXKyVr>G?8sL$I0G(wo2DEgHU1O`z>1)6zA=kWpu;~>0h1$@pt(wQ znd(W`NYIh9SB_4vbE?v{*Eg5@7n3f*6%Wro&a~lSr|WkZ5jh~ZCL{-j=YdY(=D7X#+&Gav?WrmC8WFcTt0RDpPJ(&JqKJkv@WM3()sci@k`8r3T86WKB=oz_L zI6#D?X>@r_ngFCypu)2mBS{no%>rd0TLEC2G^&8%M6zfjd8TMN5Ro4wY~VM8(!w^j zq+2H>)UFu2l>lcAaPDEut_uK1EwF&cXR=!N{ z(rsQE{7y>* z`iXW8SOy5L?JyE8$)RtS)(0FFeaISZ$-rVhJ(J6A4X?q?FP#?qnNt(*;C#KJ=vVJr zF>=qdl2b;f+la%@Jq4?+NuvrH;%X9Y7t6Dzq?9l4ZcZQLTzFjIcLHvp>vUR!$ca=^ z3&&y|{IC&0Le{Mw;tt{|r2=;HmwZ+bvGy+eV~gRjU;=C%K9#;M@i}v_Oc^ zw3Zd2=IJ{Qv%MfhkN^tAQ^B4}ZGQP~HXmjzsBuJCB0Dfmx>7W9kPwnVquZ)V;IliT znvaAF=cn~PsT`D{+F$966|#AIrMeZ*v$r{0Cx=Iz&8;$QXNmQoMx(zq?Eoz+o84gRsZr4old8nZ zXnvzwlVcjab^XWHw}z1_JE-D)^<6&KMfnxfj<- zkub`^tMj2TfeZfpb2lCho5yj~uZXB58pQ0}GdFoloIjOb5a=2_)S}`Rf2pn>c7kHh zwGEJIVs6OinG#ox1^UXOb|Rtl{iY!20-TXyk9CwSdL}eO)zQg2Z#!A2c6_Fhto599 zu3BABd7u9oC=xZ0lm?twmA|lHyZee%*tTE-Fp^KwFE%|e!t#3BaE-zgZz?X`n2v_y zWckfs{7~cL`a~x3ej5s2n0*wb0q3l~602p)Wg;$dKI?h0cQcl#pS4YA>erC)8r>2( z#9}2^gjkc<>Q(1oLpW(w=R!#NnDLf4yPlI=4N}H``VH61UYF={Q{dqNuE+W`pa@() z=&Pq}5maF7oP4vem=I?RcNUv4C>?XhB%be*w+fmzx@dF7@MCe=6MOMPuj^vpl&fYg zo^`lq+FNdTu|y%%=2xV%G-cda?)u_e)$rsK7yS1a9ZLvGxM)q|%w0O`e)*JFvY5C3 zetk?m2}di8F55NVy!=*jE6d;f&14;{pTolZPi7S1OKvzSIua!KvrxhNljUwPjKOL8wn%~~1E2QS#9Qywy$MjHH9M zpID7)Eu-FE*$VaRXpS&DfR90 zDFWNO=pmxX1#rMjMMYIi$Uoy+vVD)sRirkwt zqQ0kM|0Ba;tgvm2L>4_$!-ABqWAbNhtW1BZ!JqP}&bd$W((d^e@^LZS`t0}}4{Hu; zlh7LJFv0FCM_!fODqWsD8d%Q`TEH|D2oECe?mHqJ!a4o8VA?Irxywe}CH5t_EK|$j7D)2Rs+b&nb*OtsR(WYI(TJq)9{@5-j zw9XW#CV8c&H-~lg$MnO+j9cN0mpmZzohVfSpK1orbG&U zN0RMsQ32l|G4C4dxc7aGzC%+86uq#h>ssME!1(RPRXz9sbd`<6(i13Z60uL3zxr;gUM#n{Mg#zTojM ze=6?-)_8kXtY|_nt3;trxw6EMw;EJG0AKi1J>;rxon4P>8vwk_&dV94ukgyE@Z)Q= zg)7YA^T0)PnGc^-CZAQY#$CeWCAS(J|AbBrbe4HB`G=ZMhnMQMTXO@BC5NW=s*)6n zA_zqeVYtR3MdCnAa()9k%z0YMA5=*MjGNCK4JiYw^Iebpw@)KH=Ezd0$zGLM@z-2W z!rAiO?UI?5ZC#7iL=SC`r}Me{N$8Mt*JU(XhqP;m8D&e&rE(_))laowvyARp48P}+ zP8J`qN9Lw_G)VQ^Q4){8*dok*%H#!)yM_fQVuK>mV4a`2XaF$QBmL&^d|D<_af8J~O;YOKn43ONuV)_$mN)jt5XU=U4oa8%f?RLG2%4k-r3ak+ zaZ@h1IFHv-{i3$b^{IvE@NoZ;ioIlUzWq=*@LA{j&y1oiAX8X`xBCp z@k@D3{F2)XDta44QiOF;H#3sJ%~gYi2Zu;jn5|GaWQCqj5z0yfORoJ6+Sn6xxZIXJ z!ku1X(sx--dHNR3MntLd*_ibn_}TJEQ-sKL>ZYa zsnj5cz8Z|kP!%+)LMa9*dkwl&Mv_~I9{CJK38JjY7A?e{*~g16YqIg#vt`A#fx7E_ zr4pbIYd#sm*%otS1wA);b&zu((02)#Cc6k~WI=s6dCP~Avgu!aj7Z~_L4GAHUNQNF zXSgWQt`|vFy)9UKDr{fV-ms{exS{FNypo(H9ry!m+V_|R0Fm%E8!t_0b*~+tWoBNd zlv_=AURd$Y%%=v(4!!VdyLUZBLiExy!F0h+rpkdzxH>4IMs3Id{94`C4ytvgPD;-xZb!(Nr&xJ7w(g>!?;9P*lbb;_xY7X>ag3 zwgNk*zCTQc~%*GSw|vjB}Gb#filXB=HXy z89yvc2&|1OSl%wHE5Edb?%gk9Znh#uq^7QEwJ*WmuVipQ-)vB4ffLxY;Sjb`o5BG~yYM@6VGXC`~t--_2gx)%z z`B1UTMI&+Zq$9aWh;~Q+w;1k}ylWK| zlP)*&>st8i`)SW{#tx&^(%9?b^4ma_C3F&DSN%%68Nh?n!)xF+#(#2lXAGR>xi$F3 zDtA(B99t3)gT~jxWy0E{*wRi%LxVmxT+x5+^t`MbJ9zlTaHYo+DTnJ_FFY#LCc}^t zg76U8o{AANEHUSHz-?l?pQSJ8!%96ADZ1fduEX01boQ}paD_!%c zMIEuQ`_>vJhjo1&XnJ#xUn`fK2#{6^mis9u7UuUfyxr%Xgf#dyqUMP|zW5`=bR@l=CB@u08KloDPaWq1zehZBy;y9k+24 ziM-j4eQbA3@ED80j3=Qs%$tLSn5rqgC;|Vo-+FntLcI9{{lhaU>?{8A=S)N`i=*8U z?fD5BN1R<|ZcI9~HVEVs7SgDgrLGcP7cs6TzvLRnMa~Y>Q$va`-a1h^IqM~k1|KUd zY9ol2pcF@huXDG~s2KHWW@hrcUvZ!Hid)!5&$H^Y(zWiIJ$(PvcW3bI4@_G;E%`0d z?#~hGTCU6NgBDI-nKGsGiekdyWOuXy^1?N zs2TMIDvuW}pfO9M`%tt($<8MnK}4*#rCKhryICaCSLS>PSDxkpo3eH_LnQsi%5PNc z8Dh&Vz~aSPclQi7f1D60$jr=7F7c7j zJWo)%G9Rj!ZLty;pM^>?idD=&)f-Xq;k4+YH10S=@n*t_%Aj3^X%fR#D5K5s)A_f9 zCK!Ke$Ts+c#3frEiIE;**Gjv{ro7y$&13F9c}wzK>QcoO(r4R`otqnR&U_waYb!fA z^M06c7tm@8*e&3wrZ)#sU#$ZIEK}UU5dMsQ0p9$+VV{Vs;g z&S0df9$M-66-#w#dO2!)0gM8B5i5~`kc&|$kFHTBjaeRj9Ui`Y--(gDYSWwOf?q%^ zj+K|W1^}>3s9+JkBfq2|=XRudzolVFaoJ^2-R^STFd6V=JB2QJhex5gInG5=L8&%o z_Giq`m#BI@efooOcGko4?!}vroa$*9Yf7nTqfSbcV=5gj)dPS8#i}&+8?;}Oj3$9{3p{Y;FFa_D_l$#M}E;FU&5bXxldnW$7`t5 z-;y@JknyX$(Jy9ZBYo#Hq6QiDFH!KX9N&%9qHkPNA_ph+#K+&4^VP}_J*g{y6l?w6 zcp}kgk9{sFD_w_-GACP^FMB{I@^AgW0=!Dawo|Q4yT{pWZb-imlV4+KU#|vOtVQMx z#5aeWjj4i(i`>(puknXRu{zPOv~^co*tNQc1u&*#!Fc}Aai;&Wcm|5rBw zk;66xej{>g(?K3A08v^f)#fG(I{a$ckVLFyAOCYJ%RCn?p`@$Nn8F4*)^ioVs2~8` zf(&XXkL{mt`NsGEZ!-id>in$(_3!g72+I)`inGZLSR{Pul;%o&(KD05_uAbY;^YQ_ z9WZNSlQhhSyy%MtEmBR4$KWFQy_EfV=d|#%ee~k7Px$LQ+wO4^J)V14N`?fCze0Dp zJzp-B>;X?sEkG9opo;~t%1O?Aujk~wI)5ig5r~5(4XUHf1CZT;OleKnYGKUY)y>}V zMR)x|PrA!pkM~@WK*ATJ%th?ao7BWi6A{-L3)CniJn&=!|9DfCMgVu3*KjPLELRapCNXvh; z6j;?ggq*_PVO~gs72xqd=sfs8m{S*MM1<1&21H4NaqTF%IQs!FB{*&@acS_Wmy=nM zz&Hw8B_U?HQnKyr90^IA56cb8vnp{2Xy~jzjZ4c+l0zSN{k8{ z8%Vg{P*j`P*vdhxQC3j}D~BcD{6{u69?A6tCV;#{yb0cRU_E&ngbtuLfUd4kg-5{n zaIzy2NA z2yAz9<$$9ipODhB!)in|e8-NHHXW+V<i*y*KBF2`1*804Lr* zBERxa)@Uj=xxg_jq}ZR$IK;|m0k&ps3%y@7r5#c1yHRqj_>XMk!!uvu->l%^BW^AkLo39 zIald8RjCeSbwml2r9r;N8jnpR(~~ap&b>onKcK>JdRPhh+n^3z1hb2ctrJuypY5nr zBCHs{0`cNx)J6mpunwr|$O&lj@?D?dg_Xa}aJ6A+V3 z7iul$&Z;&6*%8oIAkme39MF@rV^a&WATGas>B~J$*@4FaS#Ri*`i%bf;ZX2WKx&J3 zg9y2Dx$V~OOJos;nmr-O8JYRfULY7sJeRK#fHafwJ+53@aNPG76r*K~?i%~NctzA& zhcI4LDn*Rw8tMvn>J)||r#6kV;|x7U&%>gTY_hkeNfz$l1aWSxuTFkRG=B0*Q(^~q zbD&8DgXnpMVeytJEtZPR0El;rWt-jZ-bEfRO0e{Lfq+7X^y95ZWgH8PDZfHK7)-d*bE zXdbb4o&EJ3hEi`G{x)3C;#}a1sR@yDW#G(3FPFiJ*`2<`N!Ew>4K3wjFYO`(* zWf}gF)BUO5r0PaNug3 zR660;3OU>kMH;~eOCAx!5`y+16}V-&TYGo7?9BJc+m4@>)WZ$R8I>0lIuLCoe`AGz zAW2=ZLqGiyBU%wBD4p_j7Y^Ytx_wWkdrWqQ@+EJAvr4E}-tV_^H86=9ty7{<05Dg# zR#yclqiB|LC01`7GSgn?OOUiq_`>sK{(Rf}6+1x6N`S(;6nP3;#Hj&HmAV=#UEsJ$rC zGm><)F!8820XC***D5xskSNLa?tE&XR z2;*b&+bjDHRzJvo(nQcq&8MSvSDwm0=dWyuR6M&2BYt38aUPcN9-ZUYujPLR7!}|X z1obMkOut%^ zR2H`!c5g0OwkOZJLPtI%ssU@5_aDP zaqFOCXRfoEnMZEWuY1Gt6Qw|v2t|FXL6W71W%aQd@UgemAgz$BpG7w6iiocjsgEwY_?){MA>ntII)TLn&oQ1-K66QC0|WTtuk zC~oC2c)&SiL%ycaBBiOaHIv?5ne)tUC20JjbUV3=_P3(C&l`EH26xs_wxF3pQ1f@QaK0Mom2*bUNRE#sm-CS}Yq{%|rDrcpa z`_xz-60p{zCh%>ZnO^k{Ms+`{kB>VHhRBYkIiL5y1pRv5FFv+w-g+Mh{soer3RvzA zmX9hQ2z^%Klj9}7U}zYxdo0?_rM2Xf_yZ8^RM0%@T^<*a>{wvgyy((b*`nGGQ-N|v zH{7xH!RkMack6={ir>FwQaUK}z*zckN0h`k*D027yENmshi%%cJ_$ZzO8Z! zIwj>Ju|L(T4%pQvlDO>($~|;%qjoW>GuJgZv=P`F+VvaooGxeD>>uj86zO zov6qe+Z$DtBbluk4|nKB<+{CoSI>)RL3>AaOh<~NX8v~(MhNx?HcQ;AgTsQ6k*N(d znOvaO+)cyij)G^R747GL$xzmGo!KHlHkepHkM-mFf->;!m%}1XxocO z=(OoxPOR&d`{QBP5-O|p{JCWk$*tU6{%1M^|Bw*X4$fDz-+75#-Y#z*ow z$1#$N5iN4{p0zKU)Ki$Vbh!;0dmW!j*a#_Xtcdg$^#5*_GKLx7^!jk&Te?yAI#%U; z65)T=yXqpQBx77(S~B6FsO?`GqI~M8yU5f`UnG}7VC|*B#-gcx;Wkb#k7yBr&K+yW zJ}3fXV{>aMNAL)#J*t62+p||oF+@k(%_wiIP-cB8GPoQk5TBG#IVl>oFA;;;8!V9m zc+!4-6y1rvI+xmg#z+q2;~?ZT>75pVew9C|c5*7}Zwncur%(gyIGvyAbwepC-Os_E z$b631O;Hb>!f43nwq?cw5xxXr>w`UAm1wJtRos3KkYD`0Wr@vu{XQ>r0tY1Z+)GhBBD?xj?2p- z*2dlc(LoV?^j0$^-^0M0bn~oOu>99=1)oC8NQJ{ z!#md@``0O{)kmrwSd(UQSbvEjHq~PmEZncTe&(1kEmKCB;zb?$dIO)r`md}wXXLlV zU{3TucJsX2%f_#>V00e~H{5PU-6V9`h2p#`e@kq>c>2W*@m$H(`XV*{L6p6?)9XE2Skl$GB>^iL59VluOm6%{oM6 zC1G-=kozF1wduW;TxT}L+DQ3$%enn9fVFEiUrmkk$As6v{ix@YyLZ86v5Trm+A?fu z=0G7lkdL6B6ts4UDRSf9$%;V67}m~3QHLi_Qw!JQ@-B{$C(UyYe15dE7=IAe0gf1h zN3+w)f9LBG2`Eio8oX{j?Gy<@k9&9SPlV${BzI`Rm4%h|mmU~2dSA%}f@JX}#7TLJ zI|c^tO75-i$JL7c8c~OEPgrbjnxLR{55S?269cGreB`~N=)uIBysEiT38^7=5!Jn{ zJ#v?;A3q}!s4CP9%iO|U+>t$C#5dgDvEzs}sc>#N9PCk0e$h3mM|iv64vqFu+!A((EseGJCno z3500r%w>FRmA|}0^$#KvgyaW zyTk2Xji6(7L;%1Kz9|MU4RGRo2GWeguVtdl%qobk z(nGIzBfM#pg4jl`jDXU(rNQ?NMauQD`d$UNpCsE}&2C1IMSdLb_|^YxTi-hYtc&zC zYdv+Egh34(zf0a!-L+^Up99VTrPE{?@WbbWFfgrIRX>-Ow77~5-49pJFpzO?ojn&O zBW9BihG{HPs$U%a7$Iw@ZElfqFK8~1AY|b>XRdoer<^R2(lM?Trm8l0p)Dbh&)c>` zqKIo}L`@us9u|;1;UtZ@&5|HKa0kQ(o`R#!OWj#4j0kQ}UQpU-;TTP|mOcgH-{puU1Cm8PZvTM}z zYpbsx7J)pvJkMr2?1j-7Pycljw*2)V4)_MTj{ys00De-iZGSsgb%LgiP7~oA;+S$& zTw=j5JHu&XvxcBb#>rooJeHSPgVzCANm_VD4`i+XBxei52UbWL&0Hi#e!ooUol#1{?_6Wp_ji}j$4q-DF zvFggs#Xe7}B(fq15VpBUvHsmcn z9PL%~A0+c^^sBV%(XP&c3GFBZ5Vy3g9V#FerO|Qv0bDF8OXw)G(dM)l;2;vn>#>>t zAjb;NLVKGB<5IJBaf1cE2j8(Ye8Qdn%slCkRmt0M8czs_g|&w?HnvWMJUB*lb&d~j zo6c9yS8a1FwRMj~#`K^6Q=iDKs-bm34|m=T^*W8us=5w8Bpn<@n+z{Aj&yZyonE{# z=PruyokAJWR(-~s2N72!I@%p-u!ro7MWb(ZnMwW)+5iV8ELLRzd=0soAtWqeu4t(3 zqtpMmPWH#9)ToSB3MQpk+%Dee&FT894c@8U(sxL>iByY<&fBja9tktvn=qsB??%I_ zq=@yq5`=~MPTT-=0f9M?Eza^Eh=}OKzAHx&F&a(0-HoC$f1wLgQEFbfB9;9Yy(sva zuB%OY{T#(c5>iqx>my(;I($N6yB$1nb9RbRsr110D4cf}2X=&;&R{UQE;8e^QlluA zhHEjXH7usm9*wo;ZpOb&;G&RZ%DtR8e<2S|0!mWT#~L)5oS8hk}S?33@n@Hre4z`DXe9h^;S~AZcvFi&#e;*Ps^&ncMI# z*P*+$RRc)}ZDBwz)EW%4(e&iM#1BCT2ui%$4lqE_b?=`sC6Hy|S$4`tPu|-g&H|kJHB53{xvpWC5Jn7)A14c%+^`>Cu;TyfAr*MVVi6AjHP3Ez zzyYN*8|SihQ1e^KT*pn@CSEyj9B4Km()H32=!pBD5^f!d`wsBB0gwGR_6Q2UsqQr1 zK)hJEFZ8!e!Ru3r-TG@b|J-)u2tnd-!I}n@$_Upez;&1i^;O@)KL2J0$8Uzeb z)(u^|jw`>4P4#mVDZmG?V2V5IjV>Am`JsBiFPr=&I15?v^Kb@sb@QF$bVqVJ$f{9b zacs~t*d~azKS7rMr8r;*DnO;dx30i}fa= z^@ZqQ>>oN!;6Bn~HPDrp2TFPkpaI#~cGSJ4NsWRCFZ9dz-}A?}7eRDlWrV;3-Oqys zX|4$@OqumkoZ3BcG-45yzk!Q)emvpr6u$-rvax-5XSmku2#}ABO%5a?LqGpE&8VDID<(u-v<3DSO*~Nn%O}>rGRDZG8{-b6SZz7A+p9O%4tFo8R)9h z&5e*N^cz3#zcbRmoW}Y^uyu^lSDBDI0BFYsP7c_#wH=WADazZHHTKIho{SUQp{!Ww zrtcGLoiR2p0!8Cv2vi|hK9vLWsSk9wf_`D+ze|ae8ojy>(tr{7=d;(c!sO7^9gUyC z4e%gAS|K+BGRS7A2Xxl+!u(6wh)+aT2Ys|9$|H>N2GXICJkD!0yE}n} zp=Lj5ud~t}aQx(}UmxBOv`%Aj3;Pgln)nwhEcb6JfnE9}TKIPb@{&pF{{Vq>v~p&t zQ2zV%tOz8a_Cp`0?|-Uwd{9@vE0F&)n6*)K2TNPrb1JP`V;BC%juL>y{-t1ld-1>C zNW;c&ne~$JSM2%!OzHgpa*efB-d`zJmi?1E6t%uFKHiVEutL56G=twYPIupNRIQAr zbwc`v?E+9Z0c$5!pi+L)$^Wdn0<2D-Vr8yDxBXwVj0()YuPq}q+RFnmnxRmewgb>_ z{PFof)&GYs&ZMgBni7@N{~R}mUhGjC_6&5qf(Ci|%^l-ybJ7ugi!xp5DSOADd6d>xutJ!yFB^qJAQ~a z0y_S?&wrnY`wQ^Ifr0gx-UKcwR2Bt|z!vafNGULJO?myl;!ws6X`R;mz#OuCnhy`Z z(QZvN%+Q&m?gJ8JAn8>UEI8?|GKXcQ+@ z(G?o^hL*=xO<6H-9A6zj)VNo_xoX^lxg^)PSI}K*m98O6GZ7!gDqTE@E~1&Fj~R_< z5ohCqlEEu%m?p&A6nYf$*Hh}m@}{awMENG62-<3_6$fUdJ}e}A*qu>g6FV^P9C1py zGb}faZZD*xL5mSmSE}I{VuT9mB=`km8h6fFWFb+MT5Y1waP8F|dZds}z|3(()AW*a zn^e{0pvW!Mb0LcazY3#aUr~XjTHO&LM|qT?bfe=+NnPA@Pp$5$q-EzeH&jop_AukJ za?R;gj~?n^1}!YNkdE^Wj-Y|NU75#=PJf<62+at1=qqmnlgSaX9Z2t|ne=e^CX0Vb<C}G9DVTXk*&;&-dkd6@`Q8OTs-GGqek1IUO=~4;H&V}}< zjTh8yEng?{Z|V~8#7-VG@Vwey*ET9HSt@DJ2y9S5>25@6rIhaO?v91DASoaqDc!j!fu)pmvuKd+?(*D= zz4!b6&Uwc;<2(PHGtT!1xt`~~=RNb9*E64xcd`=L7$g|CZr#FuBPl9>>lUQo)~(y= z_ils#@LHCD@3+t#%7hLyGjx#`O6!n7o#fxxkQIdvf8v+$*}~$d}9$o^0*1aw1dr z_(iSY8@@H!uPw3+lhKn%{v(WotOWokBid(f$%-bqC41MP(S@E2{?3x6Qj@0 z0@4S{QY^Twu|M_p^sU&t9o<4*IhBU?c+k-DSJPYuZMYgU*qQf zOe~bJ5Y?@zsmtFdCF6z2MB1563KFuuMR+a(EFej2ity(^W0}zO-sn9Y_Eosz+NfG? zZ!dN^nVsrhJkZeZzBzU{O4i$H6)bb(0a=e%I*C~>U2P4iqIykuAASIOd4Y}nC6eYA zA7-A`C#$bJ-_H@D+*b1zi$$1T3l&{uJ(kMS{%MfM;7GkUD`ld!E7bb_4l$W=PQkcb zFhKHOSTl(Z0cr11W(uH$zV*wJsi`Wemr+b1zP{w@xKitb&sDoceDR?i19hCfS?`Cx z{!zfQP0?=yt*u!jM@Lz)N3}~w39&sh8U_?jhbcVBKS}V$rzRIdmQ52*TgRvKZQbVP z#(L-lMMV-T%PZX63hUIs77z4&q0*AM9ZLsfT7vB_H+@dzDfX|I3bgp*OEepXosI@$ zUrxOXt*e_EP!eN-=EzEr1jaBfXNlV-g z&F#(cG3gavYLpsZ_=8Wq(J&lCWW@>^Cp2LSclsSUYfxRiw2NOG% zC+gkFJv)lwvpdO{(|tZhYc4P<3Q^RTY_lJMlVN?tCB>Tc=EDe&9&u4cr0!0udT1M8ADTfkV5QD zEg=t30-zjDPgK@-_4}ZKCK?XY#jzMCx#mljxtv+~yM2QDZB-W4;_?Xsv^R%`0!#h; zH9C4cKe*o!@%h(JKmPh+SMIq8zG2fnKjvxko%J`FJU1S~nvVAP5#L$v^U0xJp;4ULvK&cBx=NBBTdF(YvW(XdnrE6kvc5!LzmO(qA6p|_ zROFF1NslR0UQP?Kam5lq3ugj=kY+|8X8c3vUp$EC^Pq|6+U^$pPP)74?ym1W^5xzh zusdJf1sA)Uo%p@CEFjL~f`HY7pQqg3``gcZ3$tjJmBB5-92E~_G-!n@U7SkVxLUGU zhczAok1E0h27Z7hC}U!;rQ)8#JH#w!;(TcNXDg)k%+%f7dx@+M`W>%}p`D#Ouszj$ z;}5X?j#iz!m#F|eVJQ1{!3h;mkU9a%m2P_{_SlGxF8F$_9^v}kM8oS$V>6^HC^i>Tc#GV~FawK3o)9uc??~i4%W{<~68jUcM==WlGPrT$B za{LjI>n$rReCuxsdlupPNwux2%iC|)d#5ILhvz*x8WMkXGn}!^NeVok?k=)6+?h&l zs!vSRmidtmp@4|TA2&Bo2le7moWEPj!j*WtwB$g2@G&YPER4$Md2c&iP}j23;`b>2 zto_6&OeS9a#SBh^R$lWi>J`UFv zcHf6NT3lot3M`Kk1M!sDTaF#LxlSLB^u3Y1hjJ{cw4Cv&QE|Se=wIC>IVQ+KhYhpz zijK{%6p+A+Hg1P^GfOpJ9(*T~DWR?CaAb-nZ0=s~$%kr@Xd1eCDM-;#J;qlnGvqXW z_NkE4F4pPk{is|?Xb2^#@jTm`D1S+1Bli0DU!Fy?MaPVb4jyM3vW+ediI>w6TiDa7 zMKQ9wMSW$|&~S^jVxJI=f49VKGiy!&fg?GJ%GE3qv&usfY3MZUWQORHl!xA`a1~{n z#mbbJz44F{5m1IFA~`bO(2-()@yEC^Yx14*3P)@DsLmqV*1wo;M@>sEEyp80iCI$0 zqX-^_A%8UW&q~W4M*Rp!vw&(2dRd9jx1zpHdqwG!JrtMEeJV*7CW?%@{ghSUFGVvh z$nj<)HCF5v~^|}TGQe0V3nsYMjtYZ{~V|x z$qB(4%Qb_e7FQbozHK^guAa6XI{dvnfwM<`{G<2my8x)PFgozCeoA-DB9*SS*UUU@ zTyArpX(2D|ihb;z-}7IT5WN@gmzd{-ND$kmhpUG9f3wr$ew3MjRRi@gd>7jzA|vwA zA~Dx8fD=2f_JV7Kg19gpE#@66#J>l>*Vx-prl}s8c3B9LxNBLy@dXscCQi2Eo^$+p zjmKvrknF=MYcrwO+>~#55 zq(>M+ll}q3`j{`;H;K*fg9md`&f{0j7grHiZ?yQE?}^rKtRuwV65DQ-65CR5cMa5t z=MZD7mY6Zc_OyC6D8Y5Ff6Zx+Z*umem-CKP8r%K1!f`mbNaQ@H(wXcmds=(9=P_Fa zcjtS0i|Io?J8!cek#PQC(JctS_jJIiB{j;Hqxhj26XbZc@tBNJLk4r$D=JZit0G&C zPGdlh__`nVCKnYB&i#`;%uJ~~CaHHWOC+F7E;<{l>rCXN*QHnMgX#if6@LgI5bv|8 zV8W-rO*Ku2M4_AQUQ#OXd9tF6#6VSZpa=X>j?Crl(9N_?@qf;q>-#gKnY5Apx+TdO z;(cCpZBgA`qDkSthc%imRgj+YfZQSFW=B)y8X|7pD`5I@B^0+{K ziQ2d&u$jI@m-5i^$rhREiRMtJGFL-3r<>8Ye=InhHu;KT-0A4NiM)&gdZKqR5;b1+ zBjjxUx89a+4XYhra9SVCSd7Fr_M6?(G-26laH}MS)1AG|#BwmW)&NpRpnfN=(9(9Q7r7#4#BPzOHjaTk_THz!ezv7UB$@UpJRlwC zpAbwJCxarLmV60q)X7dJeQE~0vs~E9d27GhTf_nd?3Elgs$GsJx4rok-D86*y1#N@><0JQvT2C&*Y^ay2&8 zUv72Tq7U}_t!%IwZNY~*AM5ZmFnrjH0|+ydefbj>OF(4bZ%z7%lw{1w)RL|#w*3GqRm2<0A@ju{oC_?H)CcHj||A-^ZjO-e=qTByX{0~3>J z#tycT4lWrR`00&Qc6PI@R<3&2!#B~pO%bgmyJ@<{DKql?{cIS?(!0F}T|}h|7_N0@ z&FS|#Wv;&Z``)CP%Ow7&{#9ci>|*TXKpmwu`Kz~thRN|zRpr(7uJ@mo z=~_j2;N9i~=w`Fz+x2_%4jjEC%-i(Q_|U@I;7civFvFc!LB`J%&JjtwNg@U=0pSey z$OgoH-iNsy>2W0plHKQp7HX6nIS*xaVAbsLt4|Q^QopxiN{a0vIuVnG%rIN$CXN=L z6<39a&<&F#X~tTzy3G02GYFjWbtFcf8+fV+N+}8mn3LFs;BzG4C?vO-CLm z^>wOmV@1Kq1aM@%s>xLHChsgCTh_!78ix=^@<&qx(GCeQxLHPU=q^CRp0U9th)Ovgy`cX2OxD?v06Sl; z2=5NjG-{jv zNQHaO$ZNunvT>sOuGN%FL4{0rks`E^&xSQ?|5ZDWG84yr65C{$z}dx9ks_v?4C39i zT(hm57R`IpuvX#W%Dk@Rei<3h*6#i3ExY$aqMSoC@ft3bitl z!OYN{9x}JNN0T@7Pj1$3(X+J+YMx_qa-RKUS!iXL-jslXEYjErAn^|vV&eu5 zVYv<)KXZEWX=q`@t7E*t{!#pAI$}&udB;(ZAWbf8^UqPNZSe_4q#*E9P=P(BR(G zRLR#tzS<6R$H80CHhm4#J`RrNFKDt~rNxWUCU#i0ayfW$2U%G(o^Dr_<*C_eVHGMu z3ZYZM12y~N`pBqq@xhPV^h06L&9ZFDxJ2W;e}KU`PuGs@zbTy(oeWwxINvLv4aB|lLT7Kw#Kz8)ilI103B51mQ` z(%kPnxkCT?SHraZge;mmvBjaLf2;Q z`wi>MH@lv%G;DKq*HJ$j{tI*!tu?a!>&c@vMOgv!Y|lw8pQ8p<*k5rCG&2XL3M6g#o)tABu2QCj&pM$^c@ zfJO4Pkh&+))Fj~r=w+z(!knxj*J2c3C08V9O|%&_|9}?FL8@TFiFL8=^=-(@%RSR0 z@eIezeo1LEJMKsI;h#AlDM+YKc^M`Cf^`85E%X1?p}V{MA(r2_4LMT2HnU*sok@ax5HKzM3Ig z`5x^L@JN+R&71K-;3FKFAITU?IAu^UUm0FL;6?WDIf!1{_+D34%7jB3@l~O%a1=XCAF^z!Ig|yk zZB=$ir?To$Bw|XX1W4FsKraXyz#Blz@?pl2xBUFaQh|^F9gcO3+0J^3noOtEkMvok z0`obXILk~2_RxYIZ8}$F6J+esr%LCsuj;(dbrBb{bp!l$h4$@|qRVt_%8@Tu3J*z% zT{`_cIW1_EiV2Ng8I5$~EqvAax@tiH;j&O)iba96&$(vz=#x(VMSOf|`TgUuRi-Yp zpLz=*TMcwxeg+d+bLHWDnACBEK^tT>)9ESpWZZQZHsbmn>QAs-49t&cCEUGMa;2`5 z^K1f*k*H?S`{0<-Lu@}fW5Ou3==qmKs8Gf^@BRB|sFwV$G(KOF^osJMgz>83mi^_^ z{bfT#2Y1da8Q)( z@kJPuBUfxsvkqmWq&oj!WKtaq3#d`{`M^Mm8xDH zi%M>37}qI1Rt=VH-y^BZ^_^4w%~przQuOXQ_SdY;cbI78j5qq(Je(6&LCvxHydA+ZNb@UlbvUmyK}J zT!UOQ^M{oDx53uwCRS_haTZTW&+L5O7U@Qtdl*z&$xQEkD7d*1zxd|H)7CK_O`@+7 zWFj)(?#3ho(lfFj0(nU|Vl9T-$TD3powz~a#5T9J6=JrsBz9`AW3l=JyFDJY1dX=s z6i-_AfM16!stHLn@P_AWTZsfRX zTGHfTT~M;e^_zn*!6`1T3#rf#o0-O?_W>U5RBaWJO+M$hwK0k-YEl$!Ua1r5@V6c6 zmP1e|+Fjring^S%t%3PST&Ul{YKNtuW*#EJQceQp-~}=2Y-Df7-je(NVk)Gy5{faH z>kR6Pm`Dk?-N|arJB<)N)Eyi~o(oR)RjWza_=KXz+x$HowG@Suw6uc+sb>oRW5;2@#w9FG2V=h?% zisZ9JRk^(lOGLv;Av=|)$juhdUR__AmbgHSov36`{H(2ktnaN(Sb9_26P=B%&NMV` zPEcF^;?N!B1IICQGDV;+y90c#5(l`HIvI(r0$3UixO))<+^-x2odO4c0|Q?huK@Er zlK#K-65jQ0fbaluYeoi4SjjgCJJ2{L?`7qfE2YG8{REZ%09a%7yTDLI;d+R(4B28H z)JNE3T{u{=0X0-uuF>FgRPYsrn<2;xaX@C$&|+QzhQY|>%SZ?Z|Ba%jVb$NU zAVaX`Ks`k9BWWYb;^&ZM9DFmhn|;xto1+H5T!!aL!iHa#>RFJSoi;qUuB;2rzZ~tW%V@264i~+m&`M=r4FvG%*oKJ>5 z;73BoG8`kC@VyUKmH4IJaPTQ+-rL_gk{Ztp3=DFm@|9!JwT^lwzeRkv+XBy+Sg{n0 z8n1##lc!b6vZ7-+?&X1|)l7^5T8L`dxK6|hw=ITrcD4%+k&Zf{*90NV^Q(#M?o!}W(P%@jHXqFbP6uwD9(!-E|#aJ3GM7>kKq+MQ}pE<{bQfs%>-O|+~vbK zIeT-OM3P70X|NQn!$ z=ug1k;cB-B_0DH~)Je^g15cQ^9M%fQIP(6LVgNKtJzlZb?q81QhP+~&_rx%!k;Ahj zG9IO$E`)ttsu*+AXVZNJx9o;sj0=tE&_e%jSqts31EkFW3?k|KGbPj<$D9@EFi^C$ z`g-fV1cZrn7CPn7+ZdTAr;FH9aYx3CD{@8jJfy@2*|7+)Jh!G))@&T)&a}uUt;n{O z9zv(g26TpCn2e>d3*)frRw5Scn)Be0yKik7%Mv#n<nL70+6H|MacOb-a%tI; zQ`*G&hH9HTr_|_D)gn>pIhHGCb5|8l6W9kB(H&*TGU8I&laWK@dgD@%w^vr$%4A1_nsNlb==A|f)A-iMf^`Y0=5tk7qUA$2g)DB$no>dP zEY<(oA0xl`tYbL9q1(`xjW#I^DR$8`eti$0tGiqs;`}A|-2|+NLOT42kA#I*=nftU zOCCAOyZzB{BPN2pwSYZJCO_-wh%|M21zao7H4^v?k7%zI=u6wLT-{mq=`??rvuSQrTFB;{dWgoR-k@T zCEh7WP5Y(Ei55G*F$=5GQ~zGLT)qwu-kZICacKXenT;U!cXb#x2s5r`=(#i_(UdCXdF zZ0OdS66`RlQmE$CnV6uYF)B9tRiDzFrt`G7zdw>0^3t_#2TQSx&-UZM6K6Rvdn71H4CGF^JzMq{r&I?;j{k?UdGCULvU60OJ%8G&taB(8toYKCz zq&1wl!_J0+RG2htooiRz?Ith}jssFEw!Gju>|vZXUc*=KO`3|RI`9;8X4;f#2g1+q zN^nA!Ie$wA+!PZ6WXzw#ZX;G~kt7$r?i!Y5m0#m{_UA2xCn-vDPgBMx9-$LWb~ClH zDbOb^biH1tNZne0qD7oINt8%PEKa~BZb-l_P8h@HLJ-sB?Xc^*qBS{0+?M#~%6zsS zNl@tYMv3-)yrPq^)}xU%#is?2CkCe6-34DPhYY~p7~M!94S~NcHHt1{&H4h>Bp!ru z57x$t5=xYEUUq`Tz>iU%{_6ycvRA7-qvNZl2^xhL2)!u_wm zt-ebMQM9&tT3;6xfN?k0$1{7OZThITYFMB=Jd?1Jp;I&}Op@Wib#hLlf&E6OLheO7 zmvhm%vKdTShFbC?Z-%Lh_*&Vdu#03(Uz zfWxh34k($}YvgSb)NS25kn5|A=xrZ;L~Q6K@~kcj|BbQ1uWU(Ac0#!&oUe^X+JC}O zXX}ZG(A*+XBXH_aPd3yV%#X>H`kgClW>x*EkLQ`2PPb-P$fqjd*n`(gCZ zX-Dk?-Ke6iwEc)acm#9X z>ct!Yna=-!Tkg+jNrI^h@2J2boC_LBv+%)Iv1UE8-?r8P`Iw0MOW)XSI_e- zv> zeiTz)6wWc{pZzXS4FAZpGXvm1NYix72XIt%On{$nL$(L7#+(!o>!OKdH&?UGWm+Qw z41~n{XzLoVuz4IMA@D5(4-P~DAl;uZ2}cOi6rOF+$4!=-BL$+>V?#o3U@iwFJa)pj z?AjRxojG(X_;_tFYDC1GZ{NUgGsK=@+v;&xN{Q?9C;*DFAkNdiZu=ZSh+B|tay9#D zyn8oY6pCO_ZZ}8Cpx&oq`W}GabW)Tp%*jy_gW9!`4f`)tZ-gKvrv72n?)^8i_3Zwq zF8~d~e_UmTU^PNe2R$-F{JzsP&4Z;n>l@XS{H^NU4SJ{VglYW;%m?bzCmNeTbpwGM zx?TX7v#@}TGqA>*|6|E!m{ySQL*PpfpaMC>w)ZVq1AX&=N0}kY`C@1B{sGmJ7OWig z-k=Fi1Wj;huBkA)Zga+4Eb5;yD>;caLWA{@H;g|TKNc-U*}U}9lV;aNl6`^X*pR;z zGWX!i&`z!-DhR18qWQ~1d%cd00ZX++7B1tyFm{F2OO(*t* z497`L4ZAiaM?`LWo24AO16yl?shdFQayZmKGra9HC8U`5PvmqE1*ksQ9U?FF*OTX4 zsP6x2%iFEo$IXk~c+&U9 zlGXE7eJ)j^ZI|)Q;tP>M8zyExI?8$3_7d(65&ns4h2y?i7(SfF?_QfY?Nk?ND5g$) z<#^wj=wiVArPZbc`CUKn?IPJUVk_#gcAIIoVI(6}BjpXw~?w zJ8ljV9hZ$r0U843q|XEI>ZGc&GUnTEw%duX^>?3xQL%3qTo0Q40Ew{Plw3}5Q0-72 zcUHCN;HV=Xy!|6y?nIh+q@ycampaaU>oVCV<05()HCQBN;JS-wd0SgXQM6Jo$T;PK zJI(!si2U-Qz?j)x@oVQn^=0LlJ};I#YUvk`B(QK_LU5fY@kBEmbp{VX;-JjFJNre` z?#Tu5SM6^XfsYIB5~_!iKvT5FU61`H9d+i>3+_(-$xpMwi2IgQl0wmn0d7QfaFiHp zKX6mOI~$lQ9eaitE{{%E1^sSh9`F+Rv_BWmCq({*z0imzqjqO!t+9pTr{63A`K5E8 z);B)Jq9L!2a6lH_dx6-UJK$bkZR`p7{bT2(PvGF?bi87~+K&boTp#HZYJSuVPYaqV zv*yR`$uNXpDvy)jw$$y{?kv;sn*u-``ZasUQuWL_(To%XHG-7*m}s}TL`yRJK<#;{I(Pt{$ze)s z%9t||bWEOwhMIMgxsP>97}O`hU6JsW_ntJk;+ZKe7(q#88Ukwbk2FSyULFX2)sZtE z;&piN1=tWE`ldI3uTIbd6B@<(asa!(2gYqsb4RE|ed-6)Md^RT8(e~uq2xd%$7?`r zfgJ(r|MI|R_y=r7Wxkq*aD=R{C1&kIZ@f7Z{en0-*OmQ`W zs~y+X1rj+Bz#Id$sVl24{p`dEO^eF(z%pH~25K{~CnuQZKrO;D5v$iQ>v{NThr&~1 z2EP9@A;rgSozcm}&!$#x7syX3KC;qn_1-b0CVs8 zDDVDnPHt_j@QJ)<;nn@#qWvSNv!`zQ;ebzyw>d&E{NQG3rF-m3j!B(+<7D7dDU$ zdX%znW-l+_m!bK^sy>RwNN(K%0(r3N){>A1oC*irFxHZw<+;BX#0-%&aoC^Z3gMWG zS+SYp8vS8qAkQpaJqFu5Y&!qZ#r$<`K>6VcKAdE?N#JO-`nUn?63lty6?rhI#q2;#4Y+cN{(Up@#|W^R_#mjxMk~2 z{)eQ$TVa%W_Gv0J-us69qQ^PR$*sa0XRzPxyDAJ4{QkAVhD+Z@b4psGRqx6Px#nA# z+hK30lAUyJnVn(m8vN&(!Aih=9y~JsU-eg15&_Y0<8I&{5Pc|0vxNc_XP`$E!8MP2 zWBHqWvv>FA;Q#C4@c+^BGuW<9PELs^U#K!PFc3Ma_Xa^akGlh%8X4gf;QRfRe~JAC zyL?hu_z0a#r^ytjLO5{&+v8s15*3;->A1e>MB{SB?Dgaq5w#nFLnDjUtqC}6{*f3& zq@<)zo+w@w19HXxv^gG)xyY)MfmF>!1|#J2+u;=aRJBjBZzotS*zDp9>uKIc<_Z$G zw}e@`zWvVgX62b;In%h}c<%EzPhTJxI}L{<>wadwrCwllOK?0h)QaBw@>QzbSk%K& zZict^V+D0IbAnaLIKk_cE*5DcwFq(GxWj%JSYz=Q?ns@aiMnh_w@+h5D&n;zN!JrH z)jUkwp<}HoZSfaWFQ<_*J_Qas&mNqJB{q9SQO#DhZPysdFV>Ixw|GJNb$Yz=ngAOG zPBDC62fFFDTKxiy>vR<}25g6(I_UJBla^jk`PP)wUr!hzm9azzUCG7gWye zjIm(B*3=6sv3Yufc=d?%3pVLf(E4V|o^t8dZxRFzk#{R@G)hLDcAnbLemp`}fV*a^ zvfrMFZH}VW$@*!A8}PgGZs7gDMeu=I#%pP;HX5%svdJuf8+X(VT&CDw>}y#F&o`p2 z+F%EIKPDwzy(Z4?H0-zkXs%LmCp8shZNYoLU`h59e$$Sj3-gV^TAoIpwaoKFUekxw zd?#YJ?R5OXM9SoWIpsX137W>N(ImUdK*TdB9jqIkc$7v`X-@ocF!J4_P;~*II zy?@-Gf;e!Gl*o3rC(4!X?n6=PY9;tCh<(ZAT^oes?1#+3klXd)QoPTdwCgf}{_FZho3J+X~agQx~CPgCoJMhj3GLYN$-2 zL8-`J4r2Wh_(-{hRQd(g^%Sz=8oJqdZ(7Bu9h@Gk=AZ@-(1comsq`PlfXoOzLwC{p zqwPd`USDAKT5YEv#c|*6|-AS9I>LylVm(ZMVk$QNU8%A<9vN_6ji{Tsg z3EdDr{k_Lp0;ScjJU^l79^rA$7hIHOO%~HI8Y9jLs|D6|5yBc}iM*1Qc!U`xmV0I* z4vfwP+VItw&#qNuk4RWR^^;RH1(8x-R?jT|tdXs8s#^c*XcIVvOHtk+2<9V_GJfm4 zO@9!24`bY8b@CS`!&r6o8sY+$8SdfpV*X@m9DQX)S=H&Mhgh~P`3MH90yBm-%^xRX z?!}gelFAx1aOmRNC7@F(j=htkg~YulpURfAd>%FeYc*xkZaJ4}{rEG}!h3v@(s;&CI3i8v*2Kz!cF;{%-nIb=wuevP2L73c$1 z0CZY%F6!27oNh9zB+Ad8-(|UYbPxc;v~=vc|6MoRzWonR0`>BLB*5FYAoOdpKybi8 zWkluf&)10`3Z_TGwb;q+B|Ba;iHH4|E+5EzTy|WK-QCNnn~j!^2HK20QFN%~|C5yB zyjpozgTVlCp64Tls@y*pQ6oq+h`)XRXFS^E@F=GkD8$wcY)3$?OehB=+*@n3KJPNa zrQ>&>5J4YL7ikhVMM%T`5#bY7&Hn`@pxna5rwGl0Z@>jeRP>`deLx5=WFj3vC>l3q_+xeYEY)~bE6OaOTKXHV zR)a(0nOW!g&v+T_QaWf%lCEh!i%L+Fk}J-np#pp})`Cz}PkljNt11mVq3a%mfNo6l zCi{P2`>(T#G&v73hKB;L*o)m;Y7CY`d9tj>yWf>TKPw0OOCEaa;!POSP)v6-wz3}# zbOZj`nX#3nu_8q~*hrbHJfzYXA@=Wa^8asi`0!@YyHe_dUZ3V-O!hTM6FdcCMF<<) zSn!R!*n_TeMfp-YmmSnYOGhgIF3`Dcu+Yy)P<)J58y$579d)GCC%ueXXp!O{hInhb zMvb}pSn!|4PJX!1x504Adozi8SBdX;7tCb&DS7$)7qtZR`0iTcvcC?zD;8g1rc~sX zuSXljX3;&i>rqbt5H^n`%DjT9LjGj|no$pRfp^9B*Q#~-#9X9k(o2ht0nW1>_hIGl zSPO#c`05f`ufaQLW_Hph9>NSf<@>B#-98mNp9VrIjFB5suify(gE`cpsy>2hT->P z4FJX$)$7UlqWPSxv4?<3M$yqAFa-fRVnRq;%BnGq+&}yR)+v9JAS6o0=s=&z-{d|j z$>Vb1aYJST(QGmu{#H)5Bt(1q7ch1ny-QRC<{?Fe< zOQDxnel|L_f<@b_Uj=*?=JJ@_>IDwy_fi`jk4G0X!1JwG)qTKxeCib)_M&CNmewbI zD3?8QwY3F0>WI`&pv}tz<1_$amqNpw`tOa9krA=P#k(lH;Y+&M?+f=l7~r-QVcf^s zB;Jv4OG*Fbx@WWHF=UP?`R;;0%4CzeLF%zN)`~HMY8da|0qw36l`SOB0I@p_n)*9U zqS`z|1`Sq3IZxxEG<#Gm@12?+d@uZNS;x6gX@)penPG&c{+jsuFXNes z;#Vwrc6SK9?TILGGZ>JxK;7#=9g%zwgVCC4vgY2pwPIK5)h>3#Yz2R}5)~}EBjmNl z;5j@X`ic8Pm$$`i(x?i(4dUG-^PkONPmbgs6vy`0k5WB>s}k>Bw#8G;St1D^(7bb? z-ZpchdH{(NspwZOgs`?+@vflBh6KEmtD&)uA5^%nz5^H-+vd=adB!k1{V|rAYMwR> z`S7yDBN6Z4v_W=+&@i7R^II8Vx%?=n@VZHhw^?=HXk7d?`g%j@q`De6W&MN8+dV#bwRdQd0(OJI$O=@n+)k~yYuWnRiYL@_B-F8l2_Kb_z z=g2R9NtNw3>4-_K_I0SiGpcqL;e|el$?OMT4 zlU7@P`{@G-_qMNrc@|9zdW`lW^%}CUh%UPnY<`#mhX5`M0lZHA(Hfll=N4ewN?Auk zIrkD(nQRuIFUhS9(vLLFP&AW5cWr=p$ z_h2Wj^2+J?wY1i(ug2#0Q1M0pZ-fH{~rFA8# z_kOxV?+ch8ssF{i3w_Vk?L-~-`RRCPD;==@ z02|wC$fhE+?y>4Wwgzg=Ou1R=2MJ4yifnFclU#~rbxOnQ=-%Z*kqWL|1`cnFPTcmN znd!r$ge{LY4t4zKt8)rS?-h{02Pi@V~3}YJ5APamd;`3mykR;r9^%50h-ll zktqo4Mk zy?O0E?NIsGpKab*kJ=4F+Vf$s1?o8$V`XHVhF&@){O5}p)EMxm-up*ZklW6#J24c< zrm0JO5_z+9N*$l|1q3ybP>T@xcSW_@@ve4OF=`rxK6?>A`C0gD0jK5B>QoCCro{~T zs#fik@E3I233d2vaQ{;kAm`cqOhxR7T#NQQ@BufmPnL?RV{nyc=gLlMqph{BvX=(! z4UuyTn}{^L%MUZ^=(8k8RfY*?U}jWR&96FXDl+_zmPqUr#<2xEK1<_( zH(u#pB!o%7WQ@V@__Zjyi>|bgq42rJ4ihrfCn*cO{5=AO;kM>w79B_O`+q%oovpZJOXpX zVqm5g?~Vmx%P$n)u|U5f%Vtl4+0pYvczDVVjK&^g=<^SC^U3J83->;+9AA>Ngu2><+oTO-}gE_8R3 z{g;0;EQfCDQNA$KQsWY4t#qg}(_*K(V-Ysbso-6Uf$@ndg|)m4cB#HREyB2zqTo(c z7FE4B3gfqCR)?$US16@DU4oyQR~T{X;%Z!;I~7Q7VU10~y|1bW{ZBvIlh(j3cB&52 zcU~Bon;>R7ixRGTb!EUM@}?P6&fPElq=~Rji|7@WFvp#wRLdie9v!RxlBuNr>^ei9 zn59yz75`kYZ)<`}VLhO>;dMG|N@l?7G}&Ki~*3+=d0+cuA> z;W>g#@FNk(zS{G}n5eCKV?Cw`j2vlmL&TqK;?nk)gYvWAkE8OF4u>Sv>pRTPB@+@! zY|rcuD*Mtp8AUI!*m6_XC%NvKA$l1nL?}tA88F7x*`@HxMecjhE%sa8r#?OVSRDk< za4<(64<3vbZ}0_D0UZ2{VHg1`SXbUbz?gr+0``BplvUNazFkFb9Mle9CQo~m=+JE| z-@Q9m9}{X&pLMiMaNHacUsW^$hSwN|;it(~Cb(EiGeuNY?7B9BOXmZG{NcT(ljo#J zmj)ibkB^ebu8N%@r01A6DPHq&@S-LsFJzvZg9dY_3|`vvvNKjYjZKzSC`{lpIz z%<<>H(=cjBp_>+cKN_?q-^$@9|3Ueu(m*jF#ry-(uw6Usl9q;x%##>5xTGd6r)y1# zN{PO=aBcLq%AS6O_k3*hzDJY8rSUj^*!3BexFcdM!=3aNe&j#WZ|Av zCu}I;3(Fk3RZ+h8W|HT9$Y zzR;Jya3-qkzzrA7`trQoVh6FL0r?3#vhupftGIx^w{ELph9CWe@wWtOv(E)L79p`z zan7k&bai4V!obXZXlD`AHw(_4r$*q_ffO))!|@&%H1-8|knn+`wR$@GfMgU4(e4_# z9^zOSUTvBhP-vbVBwW0N1C_Uc9K!_}ki}waYho&@?nV<;4r}N$e;W;)(!w;a(|Uwu z&g=X^HixcN5;c~p?3offMWvOpM-mrQfFrF1Ib~_J0{rS2$59RbawO9kYB@d?KW+j$ zb$;>~S}5Z^=5ckq)9M<>`qz*B46BAI6Q25==r3n!oidxej^sL`wIO7IS0efvLIGEI z%n%xTF+mtPf~FOJDc)C9{{o_*`lUQQw}sKcumqRAV^1Q;k@PF#-#G@2r%WR6`=O<- zCG2bc8ckeFk5syU@xoB!wbEC0($XAft!#>cP+5?->!Lkyj?RYs9|HKIN2v=T*Um50 z;N;R6geeTdsu-_<(aXj^lEp&{t@bDz$CRn+C!oj8uz2vQwgDm-%$lA~HVDLZjbSN8 zl7OjoxxCmfY`SWugfjvcTAOsH7g%kCcokVyUeH=m1_4fH_`csdjLYy(+Kj@wMD3Ot z#blF9nu6symcjn8g$0l(8U)|ed@%#sEf-hqObwvU?1 zui-=V(sF&o=cTyPE#5oP>LjIf9N(q{cGz<(%897g-i=!>DO;ze(<}wzW&>E`x^6|Z zJDpbTKfl9k+)>@L!KnVE!JMI;>)qv>mHsg;rC*`@y2=_4l@ioRZP6!1dc3}sY)HKQ ztDafRs6GX}+fXX#XpKy^+HZMXH9>0TJ143F?fb0JS+X5grpsuXsh z=dS2~a!j1q0sFH%kZVv&96*CA`^mF8fAZ4nyr5@fE`?}K(+)`WU#~6iA2q>3gxjpH z{39lav6co3s4`p^XmMW$Jbu1d3^&L9<0M^#txAyGa9Aj2LNBP~_!D+A9W6#1{vp!D z`!iHiRfx1!FcZ8sL;wg$dp>#NP#fz*8M6123=S1bWg10F=H`eWN1tapd+Lfx>}p4b zVN$UgdKA34kihDMCFhOvViA`%vD$`yepzYCSTt*K=wtuQa*t|(=3jfSWjw*0z=iLdEVv^-w)9+yO@j7cJ{cF zTNg!rwCB%b9{!k>J5?x^KNiwDG*WH#;3v#u2UmPLpkBxni^vyb6afLj!O%vdK#y%PUF*n7*cs=MZYRNSzo5v5Z= zknT-JG@YzMtp*KfmYvu5(@I#d&ky zA#V0}eb<^b^O>2?%$)D2JQGq~`>sAUZokz}mSa8f;K0);E;zqP|M|%ES=N&|31C;e za~OCL?&vCLAeWE3#7Uqv*8d9@u*#&FmH=#s?uc`#Z>uhNf%Tkdf(6UVjmeK}Y$Eb5 zKGu?-RL#{`$`AQ{;old2dYy^~EEAg`Mz!8UBgmzk2DCE|wk7xA%Yqlbe&(d1h)Q#H-g`|txT zkBWUvNm6VUeyGtFr6*;9kPVk*8;H6HLdi2i#c!3c4(jkuUU?GUr}sj9^jn}1pf>|F^UobTauSO8iDV+>Ecthg4+lSx3Rme8T%Io^OlXX zOJZ}>J72x@F(QtX1_BuKY1a6|}r>fjuWLkvLC7M!6dCBGQ}QtyCkZ){RL&n z6cZKLfH;{YKW}YLP2Ep>sw`r>_9Vrw;{sXa#UDi;U~9wIOd;%rB`6xI*ZR{MV#tpK zmKGD1AJ(2>l^-w48o3B2wdhb58g8@n7*f6X{AWDt+}-5GY&g{cr^@M@Nt0RoRpD(hKauR6S4sUU>R;iNEZmn}aR%XGH?fdDE zB5;!0go)nNOyEr&X0>Twf=b2#nGG@)xbxi*EzBAqcgyGV)bYLQt2LjSr4)YoGxiab zgWDh$Pi|`a#konVGAP%s)2s>)XTAKFEa5_CR~&y`REBaaGZAaiImfXH=d^nfGXzfL z?5Zc_Qp-X6OY1X~BfPVRRZLYT;G?#w;edBJi&)-o;V%Q#S7 zlc(|?NheFa23d;_I52hwI-aZ%-va9DmSavYpQ`mTD;rjs;}_~>c$V4tGI6hMm}>_W z1~D__utz@mu&n9Rt%HHvTsG{v2_M5SX_CHaz|);w4l!&i+s9UQG(E^u3V}U&3#HhP zD>km>=#X>lJ?=6LtGMV z6PH^urqTg}pKg{JgAtAQREWP?ewVY-$Zm=Pmi zbJfZO-!H1xY0TvSO5`ocDaIu+e_uQk3E;Cbu&fnq5e@AWqC-- zSXuOjN&U#IzhFm~;xz{Rj3YSPQ?Q!E*2-2}ky-Kz`gMI8CVVr;xKz_Jyv_{hz?5(O zaB&S`|NahGGy>KctHd^IXBi!)g(e_7KcEYAY=kg+DRWwdNUg5+GrZPSojC>*i zooXx<%?gID7iBdmGsJ2|#k+sU@DwYt6FP4jRPTT^CWoh6^Oa(k)EX+_B3H(5Y+DK3 zf5}S1gP0D^h5_K6jKU1aa-KPKm37~{3x&K=7BG&NVt1Z;7o@+>NB%L9ym)KFB*%@i zr7z=;Wj5xR$gLdd>n_jb*K#xtEf3Rdt7R=9nQAI7P?Y=%tO#IDyR4-sd^8P+P#O_pmH^c263ok{=wr;YP2{8oI~>8G#|E6=Bq zIK!TLM(J4u(EQ|H1JdF{47Mr7E}z@wC0MG?*cBdSCLwptuNMZe%|VQ1C7%g3lR68e z_8#`IA|0EKgA6sF21XFdQ6tRP9z*8)9S47@!T=Nzf@B@9<}vAv{lw1(AghCc1%SiV zaM+atp958*rFC2*sV1etvy0c0J%V#v2b0}WxnlER`;D)}7H-ytf7eRuEIUpBt_Qkv!dD|=2+;fG{-FSa^T0@e7w^gULEPm+P?Ri7D=|9 zI3KiuR$=jM0AeN<0M{`Mg&b+a)#>cv;LfN3(i3S>kX=DSK@ELuppCb7IEae^cng?n zM0G;0sKC|ClVUM)7?jF0P<~DdaT7FpBm;g)Oq3WR{>_sY7yKO0jQnzV3@r`Ubc2+e zP4D;}VO3}E3iZDb{%4dr4+rhU-?+H78K?02otpS6WXQ3P?^`yAiLn+wsJrZMg}aZK z)Dt%iX&4FK+m;@lv{*vJQ2>|s^#t~q+^Fa4mK;ZN-YKueZyPmkh8yiF;RS%S7{Sgblnc zUsC{z$~SP z^)VinOmT)$R@Z!S41ShSCEkRIpYtxBMmLd3%m*ph9(qcF2%8)`@q}OY*N;~Z0Cr`> z=a~}hHs$x#V6khCG02&I=1Y??i6>x=@FPWLO><=C_l+bDS+3cjyaXIj))@PDNYeVp z#OADX8z4|Sx5&c&%2=cSzs*={m?1n0M}8?FT>F#nI8nN0q3X*`Fujg<^_f{ftBm@g zezP!RGxqa`p&TY)iq2mL&-TL3WETz3wnsq{*x*xPeU-JN?%tHwhF#r;YQi(arQ|{r zGs&H#A)yP4>3dxJtAxt-cp=3XO*+1Nul zuaZF`(>(ufsKYSTManv##u*l1Gtpc=lzzcyTtMUKdwIt3l&$a}`WK-UeVj&~gQH9G z?9Za60kW~N3VUyj;5B)F-M=4a6G{04*C8W(K-jqQ^ZjoIQ)k;dF1}`i$S2c|JYI_c zXJIz>{^saIdjCZ&HTzkF0yaeu$Ng^$&V3fSG zz_-Qz?1$cU-XbjQdCa=yx~qY9Lq^`m6`E=oG<*xQ2cfTbzP{w=;PB0lgMyl<+kT(Z zVg-!zVZmXG3P8i0BVg_lzLq@iT`0{P8WZ#{ZR>#hs3y_F2|AKQe)q;Y0A*Fze0g4t zm)S`3kB5C+RVQ{=dBM2cQb0R!y!43$l4bkB?v*V~qAV7nU$$x|ve&>H2~m>bVXG&W zg5Vb1y#cxW<=72+Lz$FIy;gkrO`HA54*6$yUsixGl|ms4BZf@MyI_OHybsB?S7jMK zAn#2PQW4NC6+L;0$C0gb`!zF?PX^uqNZkn6GIad~lCURl;JLrt{GQk`L$S+S&?FpL zZFmg9Y0f~|dUYD1`v@I_z;t3E=7h-rr-&Ie#;6(MIOxUQVQe?>nm4A%k>n#2d|0z^zcdjuq8X4D4+bT zznB495LrE`@Kvfr&7)+G<88UYPMJGWWHR?-@M}Jg-M%|2emY&-d-4)K|L6@68jyk4 zT%124FB7z6Pf_x_njZ6@b4`6q840>4-O5PP*{=o^3GL3eyR8|S8F_By?%ehtbB;qZJONDq4_Lj?vxWMpw{Lzy@g{|| zH0DI$L7>)Npk4#VlYE1?pikHv#eSS*yjfbyL3ysS;%1m@b=H=5#99=*is+CY9vF>2QW!sfz7}hD#5u5!PPTDb}O1bj_s^&p1j}xv8Y` zLXJ)OwDWf!txcUceJ@*|}^tCnZRK~H| zjY9psKE!~Uf%9tS@}6h>6{>xIit$zbAy*=;2h---z*~{ zHt$lhJ$v6e-o|AtG$wgz-%n&)1a=~zBnj{-9x@EZwIVIyq`!cq1tvsoTd!P zM_{e*K-Ss3I2Q5}G_(77TMb+HeNi5*H?Q%%5Ca_Q$Opd%t3IbdS+mUBf&zK*qjz)L z0s~Qb38JCZXDd85F?VIV&0WWL-FxZCzg z<%80w%^9?A=qJT59hCAmJjO!Ub{<)xyf{~;l=&+!Kpfl{COjAF9O`N@cDHl9nWZgY zV?OhS&EaKrF0f28Rfj0yB8uio(=lKY96Np7j2W2vX&GOb;g9oA{c`!e?s0hqD`t8@t*(2e%IhoeQB}(i|NGc#ZAfVwZ5Yk zJEFQ=*tzkA35G%~A<;%RgZ^C9nsEV#q%Gyno&3!qs$=#ahOTlVXiY{dODc4Mu~tGU zFT{vZ5p>o=?cYFQ83xgdClz}EqU2;Wk+*P~2VlK%J?TF}EC((|T~tJ>mX6BkEH4*w z%4?AalPZouvk-cpJDvC?xvA8c9QC+>u;Zu6*SFPd15x}$+gMD&MXv_EVKiePz@V>F zC%>Or=AfY>O%7d`7@1i}bn+lri4QxkT zXDJ2JN-CwTc^&}mPctJ2NZwq=qMty=X!IP8 zWhbj4wNGf0$WsP@<*^}tz+21z{q*({odS7r{;h41yN@9}9%P+pC4H^HufdHq)MvEw z1orh>)cW7=R1U;cG8VnLeh>hG-^|{cAtu4QA;m+qBaim(ojW}$qGa2fS>4$(02Iy+ z!Kl3!#uZp|;_h6t3f~sPf@hmy?-S00%WdTf3Jag!?R%GBn4fLgBRJds@@U)t8Ucy3 zhu}xb4rtMTX-3a=ZGA?Uue!3Bxx04*Y14T2T>$Ci&fgWP1l(+dEN6 zNmZMKymj2M6-;HBYvhIvkVjrSuW>}-fJd)k_caT=HK3fhrlU@3)a};`emVAL* zz+2=wLJ3Q@Y_sd*aC1SSE9e>P@%Gy8cmVc-mds?wQaN`LuW=?H-D%q|#goXgjoh;~ z8|LwlI4lzAy=%>fMu!O+~n~5FtQ$c%vr-lECv36H;HleJi@?@O9 z(=nW4v&sI#M@-#Af$#lhDo3OtmbzTIuB~yst(uj7;kfRAi>-6}k0yhD+IN-#{U&}l zV0=KS`UQ#GAoGUv8VA34QVbO>PnnGYQVTSm0OZi;iQwNKOToI?xuApl9DzNTXpG}Q zlejq2bFId~ALfXga8Lg>{B?^JXLwFC2J9rugNG2R|4>4W8mXM5#nE}RzhR^;e1O^X zpL$3G^pOAE@LUb@YWaf;OUV~qT{7^C4&)txT;I8NrB{syqpD!%g6`gdL_PRD(TJ6T zBN#7VX@m>ufh9b93gVUj)%Y<3TIKVZz$RF|8taAy6(?1*FovU+OeP9X!6#!JB(A5z zko!x2KRk?mYwTJ_aQzr62ynIkt)zZ(I$#XKqLZoT|9X1>px}W-17f^+oAF3-b80X{ z{5K&3U-sc{@juN^JaQuvh@Q%0d%R@$c4ZzCZ`&}Vn%}sG!V_V8Q{W#G1IWQ99S`n9 zS-a6@zWROm>#ShV^VM4yTl-NUg!Pv>WogUH>VuJHDuKk3VGC@>DQqavj06v(2i80D ztMIEJmjAU30@YAW3vG;jk`3Be{DG%mkq-r_hks&A&LWIf&P}9<{NJDbw64`fgZXAd z4Lr!?DVXFjSdcnO#JzBr0V2M!;DbPF-g()j$@xC>UH4Uo#w4n-TCG3 zA(YTC-lLpVFmvNn^fGZ8GkgNere4sD!N9<ZBG)}+8i6j5P_J(YMgdddCM9( z^~3!90!!wzP^{Cu&~Eip-vmp(=@3Jq@;i#TU%x)LfVgEJS`+7TG5fhXkOg(g*-ocx z+C+DZ#X<9GKCAy8dz#Fd`mTRU-pD%8zApHG%I5T)UJ*i?if`_{)z8)l{EnP|trM`t z7_d@lQnID6h^THb{lJ`a#T1qj?HrX5D#|&CZL!&n>~}sOio?sVV^Ul6P3R$U5>42o zGTgR9R6gpsa3=Zeic|5ZtkwQI{(#!2AtpOdikTFBNg%BkIRruc{=1|*Q%V?_nSP?~_-HB=kKXmQ@$Z+nxmqQ_tZ%>KJo_>2<&X0rZca@<$BlKsbV zhPTAtSNP8Q_wpo^ggLiQTa>F4Qw)7S@=vrkk|%U;oQ-6)_41k;eByyE_^$1ZVUPVA z40~!{r>SKh;WcsmbPMFE42=$9)X<0&n%RoeN+~Z8+}wF|5bJ-q+_V34420NB#_+^Z z$hTVqz>TAcY|TR&dCf#jLdMkBIvwaw-f1}`=#v7$A34b6 z#Xf0S_O(L){pH%3duIB$>^m<~DFfb|`7Qsy@zPpZ1}G210?L*`IkPH+Sd`p}J;u3? z0{ZzcOVNa8g0O}mqa7GXe4bZAD_^Yp$8ceUq6B=^9_QEKG=J3$tKPDSpSp`~OGFcv;irifkr+lHrFmB0nD zBpE`rMijm3xq7-K2zWhza9fFgwHGte3f=$z5B=YV4l28h{9ev)`*A8Mq78^+L$Sg9 zFOEGJ=n3-pxSDx)np}CbR58c)B0A^c!FG7u4qHX_00U zI=dG;t{#@P%{z%xjr__avqg07M1UJ|gG#dMsTaSoPtifpN>!u4&XeXNHfGe|k+c=* zXgDW72xU@h)u2%SS1*bqh+pCR2kmy5%|#;FQWxowkumJwcM`}u*v8w7e$C&_(@W6! z;A{Tj(H+uWtRFaBX&V`Mo8-yLR$?HXaB0sF$}RMH7c39;7J@`3NPUS zMrJ9Je<(QbYG!Blnhz0+L&VN3zbh9W2R~0nzldaauy28XTY-{oFDsc}wc~OO1vX}A zx6Ylwf>5k}AG;k#N%@D@iZ`y$=PP9xNpA&<7j_J?{ZnPlw8P5F;d??{*bxrgC5h2; zyE}xz&m$kf`3F_@%6W1Me4eiJ@uOb{ELo@?RUO(P^k%(h#w2$H28VwnJX3gdZhwo< z2>Zs;BV{eL{}geV)+__DfdCYXN%~9KXSd?2e0c z=tYh0;=W-6S=HZOT)O2wQ@MHq{~1E;B;+E{GbN`HRt$cg)@Bn8%@~yge1mmiJZ-q` z`iPf+F^XYKLDKV@>_LNxF1d#0_q}G+VtEUsna}su8%C9iHM1-vxqgrq zFWENWOOZwL8Uqt(Tq)X_CIM<8G;Sfi$sZ_I(v6dAAzEX#r+IZ1N1@7`T}egftKHEl zXJUt)+X1<$aX>|ZBDlAd0hWlYnooGzWW}w(la#t?;^W|I&aS(~Vrq=?X=QytfO`0< z@>c2v_V4P~sCB0^AZ;Wo?^Mi}eee(oC`PzITyG1hWP#&MW#6V5vtwB4b6AriKs9`& zlcdVkMM$#ELM>XEFoMAAFqvisxZN62l`5V=gfG<)X7n#;uYjJz70G4Hdt%4F_w;Dz z?5G!(B*Hl@y(JbSFSipAW+t6WCmG+zawF^Ae``TMLk|h|0N0mi%zA^ zKZFhm}l3IVFI-E;8_?6?=4O6kw<+dSGavrjUI+I7D+cvbg9LotpS;h#qLk~j zk38g%8yXR!UJXJ?2WWp8>5rJw*Q*5q%6X&>g?{;mCwLvDxON51m_a+chseap#~~1N zehwrUygqBZl@Ky@q}($Dh9iQgoVv;N+Wya26tF_zBP~nM4nv-ouVO&4ze~WD7F!N5|_}di>7>c63zyA-Z@DRe5TD^sR z{I`v6asRVppn-or`kj0rfl@#J?fz|!+)ic{w@~R2<;L3)+ldB1!hzI{#lvo#!&VVfW)v9546%zYe zY%!f?5`HO~(3*n3(O`C++qAiwP9}ffS-yCOTV#E6RQ8$g*y_U&k*c^^R`kpU z(39)@lJ0j*(fMwZ-`NT^h+olh(LI%SdRu011>*)m)MZ%{#MM#v*k$+C#jY2vW-a!P zwbjvp(WmOe#teuE-!0{%iE1(CrwiG&-)~HieA?QbZdj;qcAGym4G~k6UwW$$|8u*K zkdBUd!ploGPDVD~yDQ@IY%+z;Jp2(!@%{X3_CwxpP!)v1;hnb41}DtXD%dM8KDf6Kol15qJ@uQDdRd zVtq-am{~kSO!PG2T?ye+C;9-N7h5_9_E?f^diwfa^O3ew-Wx4oSmlo`v=7NIiLVTQ zh-c~qo(>WQ;JQ1AHPg*}#GJE19PfO++&n!vyShkIm+(EI;NLfW_(<%jlFBnsXNlLY znqGIg62o!6T61->JvQaDu559iL=m(A;GA~6nh7pJ8}Y0Uer4>0O3tx%^3w$BG}Y#% za6eJEx0LMt<06_DF|G-ZgcCpd+HL8)Wah5A)_4y z&YQiRUzTl!Gk>}lf!5pCS*&A!LK03tad4HDKmAaxJsDGwC5U>)Ejx36zu^TNsit7A zIcB0y&O0VX3fp#Tkv&7TK42|&X5kz2xLDZ7vt?Yd<_Bc2iFMv&f=FU!14x;l?*?C{ zH-jd&yM2|9gpWtzJP%)_Nyc#+Z|IV&oD*M3lW<~|R->AcVpe9LVIPkc#C#dfx>Mu- zC_!@NS5fwr^di;R*b8&rtt*9>uY+o)MX`_5ZRM=juv{U}eA8BO*`WD9q~)}-U!zPJ zzGS3-;E5SdOW<42jS+&9icFFDJRvCtS**P!mLOi^katX~f+bC-F~kQoQ#F)wR-edf z%gkAWGCnBnA4n$`btlogob-P&Yf@BcHkv{b)E@m-6T~Y z|2rI~6vSkPI36iD4M)KIe3tdULtPx)pp188l}I6z#Uz|+vi`lFN@yI=Ae&V}ccy;o z*b@BwEG+=Z*egwvl)Spd1|~Gc1XQ*fQHY&pt`ig)4S(|1XW82_i~u}S&gmfzw~i^( zqvq`WfE`5}j2{nw-+|uhZ-Aq;IaIA}!J)Gl>*YdQ4i2fj?N0^xy%WZtV z$bTVYjJP_S4e9-I_JXDWqXU(^0~kquM?T{(v8Wkh8NW9w`tCp@e3|XlvunN6NUf8N zLNB{#-4vYg?Qb(C^~4@k5vMQT+`Q z_~Iyx8~MbBp2xG&urV?Uk24cukT7re&j)6GbEx;hcCVxRi7Nt(Dpo?sTn9scfQBen2+dgLgK zdX+L|qnd9|;+4;^4dP7(*yXHmdIi^=uGU()vbP-g0-QJX?%i5GwDY^rpfqvj@m?}* zEfiSdm3dQpna*kO1b@10h+`$p38U7VMIsIFM(27h!QD7cL0I7ng;0O-854QEH{|IY z`htJ}xQ=ge`0_qHxMcMB`|6>8Dq8Og?{-c6j`sImspIoG)*ebn55k3L20Gs>HR1@S zD7oWtc<@hCxV;#l#S26>Qbh_|AWC^X3@gb`wVfpBeGiT{L1*Mzl_3QsA-!4hvLo5H zIQm+Srz&@OVWTZa_`BDr?o>vcicpasRUEBlEw9|%y(ujEdZ8GbqWJZOo5NHEC7lxn zBi|1u45;f5C4KdQNikKp?p0e=rPV`Kn<%E=~o#njPY} zjPii^Bo#09q(M2_cgL!T&aX=ljebw!@?zQVY{wS9@P@UV<9z*?_~@02MzI@)gD1p| z6Ow_&J3iEhrT~GTz81Rqa>;ZF>}S~w*KcI~5uYNA`VGEO7p&{VFPM=8njie?38Xep zUozW1i(;WH5;`yp6LIAD5c5Fsgwd}LD~sJlmWA?^ug?hrRV?`)?}-43();K7gb5Nx zOkft&sKan#UBL!%oYMYBF1+LBUXfgL-Zjl2I*0HHgPH+b`hYP=yi7?`lp^F=Q0Z6f zVeUGk^W-dhNtQS;$8xqv*m}JMN`YThB}1>m;1PBTCn^?=p9=0Cfxj1F1!E6ty@IZB z`h1Mjq3FxmdVl%{Q9lc)JllReT5O{0PE{#Sty}%>FHKdsdaNenxxCsxPjGMQ7kGt_3Y@w9u&Ab^QkRX)0rF$5cskVf1{T&|drR zW};iRTL;l)w7+jo)t_%?C3=#=>z5lN+8$W{uju7X$kS6k<4d2X@(^|-Zm-68ks40p z-t+gf!HH`e4^D08YBP^*3Jl4Q2$37t!j7Y@!@;G)(8@Xmf*!h#An#G)rxbme=emoc>nqx#p7J>b zT-Yl&YDb&8lb=92JvNl$vH2s8W&WnX z)o@{uuTTBum5g4WT13zRJ9x+BaAwnrU?uH>EtNGS)a@C#0N&^WhtPMVb%DvsB%EO~ zJjR;bi74UkVxaj7vT*lCSY%7#Ss}M^$b^AJJ@opf6-J_zzd(CgqcsiY+Sven*m)O6m9NX>OF`_t{VdI-6w{Rvb``9zvl*CCm ziLAD&zyFlway%cd5PmC+%07(bP6V4Y3wiMXwnE$MU5GU9?X%QiMP{jb^n|WE9dB-m zXE0&D{vnkBHi4y4k)9weR&*sOBR+ze!rikk+(L(fm6740Z4QtV-toU;cAx!ZFk-ii zKqzU)TYMUN?o*bqyY?{`6hxELE41XyfbkW1?^9ICSb{K?lrf%)usk-#?4I(nUYI79%gyNGj45Qk%Kl= z*W(hG}!0YZ09L|fa=?*RGYVJW6H%w=m@5adgrt_oT*8oH5b+vps}`A zb^l~DZnPfLct*?t1ugvIY`h!c#SE%@Fm;qqShIge#gj^~Z#1eqQ}ZirEE`c0Eo++#Vj$}w2lEq4_1A z%L>O!pPnfLd#qdqJREDtr(vk6Vm9Hy_CqwDegpMLl&ugH;tvt=k}quAJtzpYn$y32 zE+O{KxDl{Xjnu={{BjLJ06iA2A)Wv$(o--dW! z?awrCI$oI|;3tf2p#S6H6LNmH(cqsmG%==-!FZ*ke73S2E;CX5B02}XXU`PBXWM@j zYKhPeP8hgTp0;oYn*S4#hV}9g;?1Gmj~^FbZQA|YYy;PJoGuQK7(s9S-E?&JMzddG z(;5uYkWu`d0~OT!_mS~C5?A7{B`Z@f+r7QV5cs)I=Pat>?k6ZwV=3UkHU<5HS&VT| z5ghT;H087tS2{{g@GE9gu~CwHUc)DGZJb3Eawsc&Mr^4c=c3Wr4EJJSE4QnnaZntA z>Uy8T(8NLP>(M>Un%GE6b3~l<6lT`VM)d;|1ECb(2PqmstO4~tE`#R61mYb?JD8(Xxw6%6`qt}&7m@szI@>Fl547Ck?H z34vf-c1X)z4zUc`k45=^3fH{_D|=&~Fx(;}dSC4q%ynv@8vaQ(kGM5saZznbArY&2 z&025l9dI0oj>_C$1Z9@(M7)i&U!mQ( z^M|&S;ls=`O?K|HcLwy4M5^0jEt9uzOYqaFo5Y_=If^x&BR20!*%j0(wSNpUgS*={ zgB>&Uo3AG_M4C3kB)XYm-)8}2xa8&o>akQ1_TCQ~?>|QcHY!)%ezIq0J(%5&p-Yi@ zLPS~&7ymh+@3Q(+XOH!{ETDMJy>Vv53-I0^>3(%kQg^F8DuMbm3ZxUzN?Xsn^^Ekw%0u#9&riGywl%s|YCmSnipM%Yvi-W; zVU+yZ)1y;pVWlW-7x@s8J7(G5Z_xqbH0#$6j?b4-3XBJa9$@SHG~RJah7HS?s9%2PhyXGJM(g^UYZyDOa0BgdaGBG3!_nDqQ?#QVti zRKUKja;x|KLHzVRoMCQr@;4}YR1J9c;=1Jz{roPx&d%b`F5`KpiRbHNUI&b}TDy|E zddQ=Wm0{uxpZa(B4g?c&8UEhYKZ@m=Ha#jk@W&0C?#CBC2%;C|pspk5s*>gqn|;~( zuy=~<=2Ngalm>3B6vQ&y;-Z$Cs9b32yLUf7d+S7kWQd&cPbZ%HEV@$kcV`*3{M3cV zaT%9z?R!h!;>hWPbs^&=*Fh6i?|2K-xQO22aB-i4fXHiAMM0haOs*k0+~)V#cY0j% zJt>HrwxnP7Dyql*alknFW)|p`67@)DurwN;Wa#$bIBTd7yvTA3!r~du zuHXKis<;0pPKfg~N=5a%=jyFs#XRZTw@5s>rhhQ{TN4jUeD|$R%v`~~=D`C(Uk*9n z*~!l}!DWe5crx#yp^9U(Ju5+(0b9~m*Ud%SD>{1o19y1hzsdoUkX4W){^f8v;Ca%P;AF){38u-10lsy zXi*bnr#B;t>YZHk!jy=v$4k2g7`F-+z^0P3mtP%5R+db_!yIQYPxH{;0*wYbN+3Cv z66`p`Z`Aa<0wPO=(e()3yY`Md2B<`N3Mr2m@PbQIcYnT1#~`F_;d1ft>fLck<2S}9 z+0IJwwYvH_+Oe-NySRVqd+@=yu7s=r->zZ--NE98PnWFB>eljX%k|J(*Fpneqh8>b=LD&bdMunx;&_|lSKApSi7|h5Bfw#TJe&y@g^*}HB4VlFzgV4v+ z?NBkwxorN^V#ZvRm`fWjqulfgl5I*xjT>Q~JJlb{Wbf)}#08W8gD?`)a_O(Nda}oN zUlY(&Ns85xYM$S-)JY*W;#Clq;tYYWu5Gb|${L2m@Nu@IElQ{76G5ZDJv}8E&qbai z@tyXiNncYT_%{N@UxoCJT}=(F_04b_a{0o0GhD`?xgc%QUhm8MDV04h4*Y8FtkdAX z&NRjzUi*2=wgWl2?Z+huPU*>!h`f@oqJVAIj!1U;KwZ1CjzguBA2h{I`P+)Oy9V}# zN#HIEvpCpxmtk&h!wLg(H-#cD6LmyYnwN8uP~@r^{U3NT3-Ek1t}b=}4L~?rc?y^O zZ+M^rN}W9-<(2pzasKv_tx~RlafZ3ZOhtTzpx7zCk|;r6-iFTAv5Hp@`YGeprvBp&^6i0=H|p)j&jO3v--Cur*F+bU{Q;X$ zy6oEQMkLcVe<<|!V5;(k|M>~;7tr`Xsa^6#jd>Hr^4vzQW>^BAZo4(~)7uRL$IwN4 zp}i+%ruJ5vSK%SrOJ7{4w8e%fTza7@5ewq}OS#Ci-LV~@Gj%U;o;^zo6M{fGv2lJhEg0*WBQAA>2cCXIrJF8w@ zq!bw2pOTOLWcYnzZkaa$$lOT+UvUIYU5xbPmhCXoIS8Pl^lbICn0>G;+Fg#cA1HZ- zoLmKGg}l?V2~*#UV%M~(**=mO>o}~i%KR9$u|=?lsXR;hLd)oBvp>~k53YLjb(-ek zRoG3|sk&L!{JA3^#Q6yYu%5BtTyg*0?PlHwdec3Y&w0!C&AGQ}Cr#7(v(2UDeuugt8#JiVvr`=BXCR`Vi-3h65PQXUIXYUF z3+oQUz8-XFi!}6YvYbiT6FrJyM8K3~n^fMG%^zTJF1{(^HXet+bY7IQRiL0=DYj&5 zg`Ks~dX;!+pq45%uqt!^!+$C{<1^mbJK?F0J3awn^_1F`zWHRu%xO`Q)tl@=W z{0@JqIr|6+k&c+#C1^j5Vxu>@+&=6!S}PU{w2pRIKaIW#aqBnQw-VwiCThpurXK_`+P~DD&CgEnCKsH3>A=BM^&9`?tJ9xg-1Qu*p?XYLVM#2D z7LVzC7p#WGKTHF4L0QI;>>Xgj)PQ7|$vNf+kSeI91!G-pc7rx3kx3g9L`z8lu+E3t zgVsU6ItohQQER8NO?PcMvHay4PHqqyUo&Twg1CTUwib_0^{R?MZG`!4H-zxyF@GOv#+>YZ0Q7TdpiSpgxUJVmOA4&YnCiY=5Jj&fKFyyW5X~Kk5P-<; zM~3=VdKVX{TzmfE+wy{2pUEC->{ETojsFH{V4>PL+GF3tsL2odRQ*j55?(XXZ4~-A zXF2366lPCoKSVN=K42fe9Eeebhw!Ua9$Pxlr*L6cL|on+x3+-5FJDY~2L2o*9{nM` z@x69bvi7dBv!qTEaW`NwpQ6%5?&V zaS{{zqeLoQ2W(=rN{4$f>a}%5b8F#>tgU_jT<(aC$PeeVo!DJygk0nS%jF(!O5U{{ zb)SiGnyhDG{VM@&2hG2zm_3s9WsWl@*{-WOrXe!ba~!+Dh5mzmc{G(BN6c$KeS_wH za%bzi5e^&Z`PAiHQ&H51ek0fzud%=p8x7_+k%)+73g(w+1$m`w`Ga)2pFFA+x9}rt zyOy^x^#?miwzoa_GU~Y5p`x{F8mtxsY0u{$Gf@1Y@2fV{7KQyxEkbR8b)!SrSxHN4 zfJ1F4_=~P=!N2C2OOnb&AiYsNTng#a{Gyxj;kqWU$%LjcXq@Q=^}zIKo53s8&OT@j zZegh3<|irM5s+*ldigG4J&tplj@TYA?P1BrQ@h1L6p5EW3ub6-u`(#Ga3BnxBf4uQ zu9D!j$%$rGMZzT8y^~-bTCLkmR1BRgI~ggDbAO`ZA%*Sk`QnD21@>|KZvnRE*&HRi zBI?E1lg$IkC=~BH=5Iz2_VbgUM#as3;w0NC@ju4Ls-)YoecTBtB z+d)(de};?2g|r$uj$9dGMz&ZT+7D-a-BFjT!wY`m$Mq8*-koCcLVhWCbKpfiog_&i zc^K@bAN;@}N@?66%tnqZfpE123>Gi9-ue7cqnDr$6HRj!q*v?y#pdge)as7*IXgKF z+v`4%4{tM`=DRJC!V?>L{9FJx84#K6h{C_j3@ddDmvw z)?PJWe`xYol`A$Jl7>?LsDp&__JDf4u}08>2b8d!sPvaRlrg^#Sx-Pf=eB7zsD|!# zUtxvgc5~r{5OxiRKB-<~5aZ>9$>X`aG{ZFQrb9(vS1Bpf!=CCtl;D}I+%~cJTf}s0 zIe(gRGQ2ko56|zs-=`XbeP>K16pUk$x*u+5mwP{kJO1tv9;fR^6QfsNHHVZXHFxT@ z4sXkY>5?3b=I59y!geHdq1dP4@=_BIud42DGJM^ zRgTCVG(M4_dT2NO1}9d76f($fueZ}>cR$52v@@eqk%%tNYz^l6&a0%Azf^?v zSZ&xDyg`qOAc`Xb9pmQy$RGGd-tq+uSE;L6p01_e7sy<5WI7@|wRHt!roF^-;ET2J zjIR96Jo|5=!BBk^MuAwNS6Fr=B|GJ^DSkO4TeC(!eEnizl7zr1Cz5)6K=(M;myd;a zI_^y#3PiQ^JB^Y(3lls#`}VAa8hQt65b}yRjP~r#t>`aJ=UZQ`;#+Mqo|5MWPog*1 z($R_Nme6Y$CBAjSfBZ&5{qsm%QATz{cK6>{jB^@y0`~PRj_edn2hOZ(@V!d~=0zLr z%`>Nrmv1X!G2SprL(vLm6tnLvxVhMf_Qe^kH`|bc_cXJTVs!M0)BP@H-t3Kt0tCK;|Zhg^8h$ zhbq5(6_vah7iu#vPX#+a)^vGc;8c+PjI1MZq@)!oLExfjZC35jLaJOhn^6oD1Pf$J@WD?EbIrzAGxKtlPS=T7rNe zQ9wWhp=rrc5efwamDDXNC?H9)lA{z90um&PfPjLu5|kv81*=MO&N&qs$rQ1G3jaQ+ z{dIf)hx>5HxMN(NcsRAs+WV}v&e?mdIVaFfz8h$#SSF^E`yA*Ovwj=Gfl?%jau9GN zbpL?my)o7|#TZMNj`_aIRPuVY0Tjx?zgbGZu0jeGH|+mNV*hKHWA@L$zK^D_LHzxH zdZm^@1Zh0VrxTBNGAXlVxUzcD@15&zMs}U={O$_3&A|466ZYLs``C(ejc->)%BtZu z5W@h<*ZmP4c~Pxbx-$NfqRPPlkE1^9p#tdB<&!`wdpG9{FfAif>A4%0sfR}}h@xQv zm8kn_%-s$S3g$M$FgmYj2TX~p2?tQ5mWAN4TC(XBRMg*^1yv*I%Xga_ZxkLRB=7v; z*IZzR*)IkyEtv<`m*OdND+=<_$$9-izv+fOR0cl$AprL$B-Z~4aDZlKzgJaFog-CC z8ew2h3=DBHX|QFGs)vSg!Zx+M0;STA6?;HmJ<4R207!sCC0BFkylhMd(s7m5ddxx$ z?x_(r?Fx#g^~X!}O>P)NlLlY55LWGzHp~hUa@dRY1LSamPoVZ6s5H-8GvlkV8OJgS z!=w692Rheb?bl#Y)~>V%Kz3Qr56~2>{VJp*KqL8&#@v0GiPYFxP{Z~Z4xB_u?RT#AKbHZ` zA6~Cj(+HhIpr#-5Vh5C_Z;lsL0?JC;(oeD?>QAy}SNuWaT#337^D5zVM{xc3yOeWE z3^UzjQP6ZxV`MTv6BhCH=veTJ&YdnQa5~my+2gB8M^q>;&_T?TsLki@tP1d;p}_E3Lf0p-hC0B5v4qJG#K{u=qjJmFiJ$s0Z~ey`1+ zsIv#siegWa#Kcbd>;ndKwjhtr5s!O~`N@;7xtykE(u=5q>&A>9h8yJ5NLpWx5me;_ z$jcv%hrnuu8z4BfA1ks4C6aEf4&64z99^~N+6U70nWU!=kDI@o(^4L(seb$!CN#3m zKiS(#Wr+bLPglOz)f;>OhLs%t15x{9(AV4v%1mMf#{Y)ZB21X`%XW_giP`5S+DOfc z0_RL=4g`~EiBeBe;$aFPrWtEGv;F$LJ2AlHs~zTAD#MF^!Zv}lIe$i$(u-C5#RALW zKsS>dQgDMW&|Q1m|C+TmFN@PvV00)4xxZMGRz=2j_y*bz6UO+F&NI`2G82~oMToR1 z1O&~z3}Czf!EkEj(-9a-fz8>)BvBY$w;B!~cEb)EP2>d}8f0h?n#tuPCnMA3Wk;OK z^Ms%B+6=UJL^!Po^aOdsl(8zyVt&^NuVtXd*+cASCmp^xag#z5w&SCH-WKDRMF_@z zGw)mEFZo`fyXjcm5a}lJ>_wU}FS=4eszjkx#(2GEa=s=yG>u6)yR`32{CA-6=>~s$ z5EQ7wP>^MGR=H5N45g4|-o=st6qs4MrDUkRF)TP7Kg-C7693X-egLS4+a4qp(`pb{M@>4--NOB1YOg>ZBA;`>IyIF ze(N57BspSz_IlLF?%wch_yXYd24 z3Wu`Ek0YLyVeG9~Gt6|gKkeASseMrw6Jum#n`b#R)?w_Leg?D%)WVcM@adg*-b2(N z%s(pIrP1)g0t#jC{kd3L&nP1qDXW?6Sk0ps`W5%uM#_N4Wl{ss>4$NEIt^l(?mGl6 zCqMpYLvU2VCWU9D$nbOL`J;di-cZHPUuNQw32PGIJ>Yx_@UQ%!H?*6j(e<*|g2`-Z zo;cJZ-ju_;h3>hi18}_ncn%T5#0=#ByE7PG008Rmn0^AN3qbA1(x4RZH%9QV;Kyj<&ey4Bw8{vEa!P(zl|JD;s zUk)HqwUpmpHG!vK`CR~*!gp)KMm7H_klv-F2MVo?Q4kH(3lIuSY=_(&QgPUI*ExjB zRn^e{z&~eu0b$F^u_B|^f@?C%uSMk~6-`o-wAl0nO8@(pyjtdYizHigi;sx>KXuki6Nn$eG_s`u7#mWD)RhsZu);P|5Y)oH$8Z}IB+3Xe;#{QzD7_m~QHdmct2 z8Y-c^H=7?Td#oVeEajbJuA${p-J~HL^v64k4Cy~t&vV_`FP>H?FgZ1+;BGKK%izGd zRLmgi!fhCEcYb0ar(VG~&fbNaa4^GL3C<0Fqc4v*@@CTB`a&ylGSD8)y0U*P)xJu+ zTe9jVsi*|6K#W_8S~`iQ9eB5(U|^FQ$kq0-y|EfP{+3M8d3y*Mj|5*T7FW)NwQkwj zAuf1Z3tuRGUK>dx@KHGN^qX*Xxm6Jt?))0}#n0`p>gYw140Y4lx18XnUHSU*QbqRf zjj?xzPnwLg6jVg8V{469eC1nrXBCV>mTrDJKD<2BESNlEcP21%UQWA79(24z1XgBj zLFbvoJ|!7H&6pqPab3d8mz>lVuag=~G!8Ytb}WxG=?-gbS;g^zNri0_Yj|>$fRuSq zQ#Y-`nGtuz_?mq$*%jFyFWIV0#GSmt@Y(u2o)O@sY0ds~q1M`Q|DnC99^u40t^60airXK)x0WL%XM)_5w=&H`I z2L*U`&6(lxUPhTV%r-kT z1QqA8yH?(?ghq8k#-(TG<9e^4wlO{YH`P|~me-HGqZ*!4}uR|g`0 z`tQh2gZum+l<^IwAorl;0b|nd5KWzgb|O&5NdR123~wnq@*WcM^A4Jn-B-vK11R}# z(&EX>AoQA)@7eOISs#bB1K7ATRhvLuOM|>CZP(WQ`<1-UC39&jJy3 zUF+28H2Ba`9@~CJzQ~^IPPW9Mm2GjYkEBwr&fXZRdhzQQpn_H9pYROJPALuX;`KzF$i8O`w8d+AKXRV{`Z5AD*{LHm;GF}g z1F3CAp-qq077?45zOV#iR+4IfKPr{z@CvOU(G%U*lg%AMagZwbRXs>}J?ycsj(D`{ zHSyzBw5qpyIlH8FDQNXhhpkp*JxtWeWG=GJUWG$}kFI{hKsjQ|vstf-G*9ZGxO) z-X-tw%zUwiZ%!0#H%q6*IzIJePpBfh{xRdID9FN*#+bG_B#m~kN3PVXb=V=2bbua@ z03BEb8u9fyFe_x0*H`R4!U3ANmxi28Ok;7WAop~;ktcW4N?U9i19X3D^}-6_l5kDv zoyM$nQPN7CBg%QQ^TSJx%Y0z&-P8~*QPh)?B*@JE1I#qi8;kN;MK0>ObNA;Nr|!|A zXi+CniDos@Ez=*&wr8Q)4TCQn0g>GLMBSH267MdtZvaV5a$F{FwHJ=x=z56fiuPl} z?&J672hM8;fb}(o_QqaQDP36f_WA(NUN4LazPXF8Y;brg%80N>cs|F(Aql&EgG?bo zG(*?u#y-O(^4kS!dX;kuEw?opX+fBChP_zu*&W6zlaS-c)5~#>mg1;V30v~K2L-U{ zu?>X7`xih|p60Ied|otaztKeTgRXKM9iRp$56+{Ay<*OHWTHe^z_#kG*=xs>C)f7o zrpdB|hT=ZyzhsUROpF70X^v+8%G}+0sjB^+(l&C4RyyYb>v^tRxO4YuCkQOo>tLB_>%uN_O;U^el?cdU+(uZ_s% z#Jgf2qc0`BzOO#-nEA1Za64+6El7rcu%~0Dd(92)M~3@ITQ9P;`IB9IrJuDsbo=K4 zAGG}iRMHYO)`)#syofnz6CW+Fb@@~R?MOmSp9@(pZNIq&xzqQVJj?4xUML_grlt?s zH)$F1VA798F?K6<`qwbpV*ccGg0eVgfW$;th8dt&S6@ug#cuX#kXx_>f--Uir#n$v zh_ib_Ag--e03;)!O`Np6=0}`o_=+`_o*+fKDMCC+7t$$0_+%yk9R!Unwc(JFmANy% zMVTzU^aM)+(LmZq>H6(WDj#f!*ZuL;mx^Sbb?-Ec%Mx)>5>M2`S(;%C%#9}BSwY#%+0~+mmyTCC=CT1mbl+RFG}j}AWJ*gC+yL1 zEHp@!>Sqhq+h1>pk9CafIDKFLKmh;1AgW^lH&&Pw%!}MhM0#K;oYf`PT2*jA0VLE(oUv=v8VEr-O-y}?9t>La%zeyzIktkA-K5MCZWEJW5 zsat~lL<355yB2*Ow!n{K-Uj6*qV;dOoh@e!6FHj1SP^;`wmEpB#T}}uzul4A$>E#^ z%;A9y%n#37IE2vI;;+x_qoywhNPiVhl~{_Uy}2H{-)`!VMS+mOzT$c@{JEh*eG*3i zJ(T2LFcLP&5sr&q`?Mfb`KZL=+cJFe-Mjp`PMTpJGLWZT)B1uR)0X}v^~2H5(hoK& z+w>r3hnQxn0>GQ0)|aaI+R?LfK2PNEt@s%igS&UuEX)B8NYdhPNP1})Y<2A|g}j8s z1h&RMjl8T34+_h?=ui5m40bX?dW}ZFuy51r%+7~Pz+v#Pmv16E zf^|bXgoSh_;w&bKy1vcRTF^U>k4hoFJ>!!th91yCmhq6X=9Ld7T+`Ai4i4RF-5OID zt3z;qcs##%N_@%H!`V=MzmV>BX3=R5ij>zBmH&5!d#TQ`KFLvdg)!_i$*b#)<>m^5 zc@OW)5>b&*FI-Z?L)sd!9Ce53O~w?QX3@}GpqHFDqWZa85_vFEugWv!fYAo$+CN<) zF;P^|;plm)R8%eBEtJnpbfBQ|Nwf)e6+f|4Y{|E!pZA7=$Aa6psZ?sZ!BN-#wlTqN zJNqv7ln~o(Ute{RG&He%k1Z;#h!T_#z&T7z@FkcBkcWG=f~yuj9hV7!|fy_{So zTM5){2nPE$3zDY9cu`8DEw`$2(rSn`ooIjUutAT^w8E3`j&Ov2@GNi$6&(Y*^3IEs znf0gh;m7PpOSwK=$P6E(A0%xB#Mgxuy!1Eo)ox2^6YA%enfSM}aGGMGicUYJGvIo) zZHnpF?`iNfrW-U1E%P#}$mh}e$!D@CC?C9X@xNr~Y#5rm`g-mm1LXsbKiQ}TD>$4U zOQs0RnO1bZR5I%M*{^JaDEO0q4ZnQ5<@`CL+oFGC zc{==1hvM5@eJ+^XlR=|RZ`JT6uAifcI)lO@EXK4&gnCrY`g$)U5vy{X{yg1}=gxQr zcAJ?; zdzHFa@|F?Gl5F*Oqt-Bz$X>Hp6>VgJmf!BJmn3id;l%fzp|CY1S$QT zMT>mafcpGj)2>}K_H%FIICI@( z%VMLrN$c{vYv(r52_5nbh}EGo-Y9`N`EJ}O8<>y;C{g@y8j4E`r#+gqjy&7#raC|s z?iNA2r<9|mR;ax#rCnuQMWD4Gyb@mesW9}+)JZ_ss0O5*N>-y4us-NFD@2b>ii%F} zC`uy^=w$>xr}N`AE#tb{8WC=bQ+d+Z3wU{~7r_LQc-d$8D~^u~j=W^>R{l+i}r4E9M0oIStqj z6|lelmz)~019gScX9h0}(_R>H>`(0TBG;#UO?>fjyK4t@H}SlF*dtU;N^io}P#uWv zSY5xhA+%4{Zll@^+ZOxLk7slyc{EO#Szl)5;1kfY;VP6FJqHf_t|Ba~*%L@*ozK~0pQPYe!Te>Vnx*?P} zy6XSTwCZojyd5$bFx^rncKkCDJVC#W=D=OjVrl`L(B!B{syIl+_xc_vdy^g4Y&nJe z=A`k(&?gk0U~gcn;sNNiuN^g3uA+|U+A;hUxHEe7c^l-!?frQ#U(Q|2jO#jOAJ z@XN-KkwUz-pfoPhjpbs#mT_E^V2|@xN4dnw2a;&$?J4kx{n=&qkFil<>CYpdWZ+v7 zZZ1|U?PGl>1}D|-GbVdId`}Ft7fM6!4xk_T?xG2+n$VJ=H(GQt!fVJWSI~M&qoyT< zUr|CLKS*lyerd$5;^`J^6mb_2rD)=;5Q?y=e9*pz+G;Eu>p&lTJs8m0ia?6wv76Z0 zx!XK?c;CaA!0IS0SMj#@W7bSu0Qy&wIkG+qDoA{fJmCr#D&cSo z1>6b^U!r-oLHXQ<=+*hbjmT(+mjS$JEON?M(uI)CqBnDq3Volpietkr+`ynb8F!5O zp59yXuj5D8WZAx49IrK;hD}(UG`ZesMtqv0{ z%`N@m&L*G4f1FyVg-)8PKAGiZ+pG?jm*(PH@H)d9`7mEyUcdiYOLrK%g*2EZHGwHX zxYcW=eQP-T`LysujF*e$)&1<^nJJs$-)H(ltLzQ6)jvn=d@O%l7Lyz|fz<-B-kn5}rF7Ou*Cy~|_6*?HY*bIwYfDl(S}l>12AV~mYx>a3xM&rjLas}8Jy}4*{F%`+Bf~jK5az`s!3xrSU zUp&d-7;?BO6BZLS6Z~Nef>9AC_=oVA+p`(lOyFov1f<|(h6^gW-fz3jM-!}GVw-16 zk43fgYq7wStLX_j9Pg8E$_ZCp;IH!53-?eh&plZ#YBc0^V&&+pfBt!7Xjq*nU}Bn{_nq&!@Rz?>^iQu^G<|&vm);@Gli-nOC>OZBS265LBu8 zf`UL5oG&lb>DIa1;qk(1N|E5OaOebhFwn8#)pLcPiR2?RJ=o#d`^u5OfWK>3bky=y HOauNOM-OCM literal 0 HcmV?d00001 diff --git a/docs/developer_guide.md b/docs/developer_guide.md new file mode 100644 index 0000000..5166f66 --- /dev/null +++ b/docs/developer_guide.md @@ -0,0 +1,81 @@ +# 警告 + +本文档专为开发人员编写:不适用于最终用户。 + +# 假设 + +- 您正工作在一台正常运转的测试或开发机器上。 + +# 创建开发环境 + +您可以使用下面两种方式创建开发环境。 + +## 在主机上安装iSulad + +推荐的方式是在您的主机上[安装iSulad的依赖组件](http://code.huawei.com/containers/iSulad/blob/cri/documentation/install_guide.md),安装手册将指导您安装所有iSulad必须的组件,包括protobuf、gRPC、lxc、lcr、iSulad等。 + +## 在容器中安装iSulad + +### 依赖 + +在主机上安装docker。 + +下载[crictl-1.0.0-alpha.1](https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.0.0-alpha.1/crictl-1.0.0-alpha.1-linux-amd64.tar.gz)并放置于`/root/golang`路径下。 + +```sh +$ ls /root/golang/ +crictl-1.0.0-alpha.1-linux-amd64.tar.gz +``` + +您可能还需要配置insecure-registry以下载dockerhub的镜像。 + +- 对于centos,在` /etc/sysconfig/docker`文件中添加`OPTIONS='--insecure-registry rnd-dockerhub.huawei.com'` +- 对于ubuntu,在` /etc/docker/daemon.json`中添加`{\"insecure-registries\":[\"rnd-dockerhub.huawei.com\"]}` + +### 安装 + +```sh +$ ./CI/prepare_compile_env.sh +``` + +脚本执行完后,将运行一个名为`isulad-compile-env-$commit`的容器,您可以执行`docker exec -it isulad-compile-env-$commit /bin/bash`命令进入该容器,iSulad的源码在容器内的根目录`/isulad`下。 + +# 编码风格 + +首先您应该安装[Artistic Style](http://astyle.sourceforge.net)。 + +## 检查代码风格 + +修改完代码后,通过以下命令查看是否存在代码风格问题。 + +```sh +$ ./tools/check-syntax +``` + +## 修复代码风格 + +修改完代码后,通过以下命令尝试修复代码风格问题。 + +```sh +$ ./tools/check-syntax -f +``` + +# 修改Proto文件 + +如果涉及gRPC接口变更,需修改`./src/api/services/`下的[Proto](https://developers.google.com/protocol-buffers/docs/proto3)文件。修改完成后,重新[编译iSulad](http://code.huawei.com/containers/iSulad/blob/cri/documentation/install_guide.md#build-lcrd)。 + +# 添加json-schema文件 + +当您需要解析新的json文件时,要在`src/json/schema/schema`路径下添加[json-schema](http://json-schema.org)文件,具体要求参考[iSula_C-json反射脚本使用说明](http://code.huawei.com/iSula/MayTheForceBeWithYou/blob/master/isulad/iSula%20C-Json%E5%8F%8D%E5%B0%84%E8%84%9A%E6%9C%AC%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.docx)。 + +# 如何写commit message + +如果提交了测试脚本的MR,需要在iSulad的commit信息中,添加测试脚本MR的信息。在commit信息的reason后面,添加一行,command: "测试脚本MR的路径和分支"。示例如下: + +```sh +isulad: xxxxx + +DTS/AR: XXXX +reason: xxxxx +command: "git fetch http://code-sh.rnd.huawei.com/xxxxxxxxx/isula_testcases.git console" +``` diff --git a/docs/install_guide.md b/docs/install_guide.md new file mode 100644 index 0000000..f6934bc --- /dev/null +++ b/docs/install_guide.md @@ -0,0 +1,241 @@ +## Dependencies + +This project depends on gRPC (need protobuf at least v3.1.0, gRPC at least v1.1.0) or REST (need libevent 2.1.8, libcurl at least 7.40, http-parser at least 2.6.2, local modified libevhtp), LCR. Other version are not tested, nor supported. + +## Installation steps: + +### Initialization + +```sh +$ # for ubuntu +$ sudo apt-get install unzip libtool automake autoconf g++ cmake curl zlib1g-dev libcap-dev libseccomp-dev libyajl-dev libsqlite3-dev libwebsockets-dev +$ # for centos/RTOS +$ sudo yum install gcc-c++ autoconf libtool unzip automake cmake curl zlib-devel libcap-devel libseccomp-devel yajl-devel sqlite-devel libwebsockets-devel +``` + +### protobuf v3.5.0 + +Compile protobuf from source code: +```sh +$ git clone http://dgggit09-rd.huawei.com/a/euleros/third_party/open_source/userspace/protobuf +$ cd protobuf +$ git checkout -b next origin/next +$ tar -xf protobuf-3.5.0.tar.gz +$ cp googlemock-1.7.0.tar.gz googletest-1.7.0.tar.gz 0001-fix-build-on-s390x.patch protobuf-3.5.0 +$ cd protobuf-3.5.0 +$ tar -xf googlemock-1.7.0.tar.gz +$ tar -xf googletest-1.7.0.tar.gz +$ mv googlemock-release-1.7.0 gmock +$ tar -xf googletest-1.7.0.tar.gz -C gmock +$ mv gmock/googletest-release-1.7.0 gmock/gtest +$ patch -p1 < 0001-fix-build-on-s390x.patch +$ ./autogen.sh # Because of internal network issue, we need to change curl to allow insecure connections (curl -k) +$ ./configure +$ make -j +$ sudo make install +$ sudo ldconfig +``` + +### gRPC v1.17.1 + +Compile the gRPC C Core library +```sh +$ git clone http://dgggit09-rd.huawei.com/a/euleros/third_party/open_source/userspace/grpc +$ cd grpc +$ tar xf grpc-1.17.1.tar.gz +$ cd grpc-1.17.1 +$ git checkout -b next origin/next +$ patch -p1 < ../0001-Do-not-build-the-Ruby-plugin.patch +$ patch -p1 < ../0001-enforce-system-crypto-policies.patch +$ patch -p1 < ../0002-patch-from-15532.patch +$ patch -p1 < ../cxx-Arg-List-Too-Long.patch +$ make -j +$ sudo make install +$ sudo ldconfig +``` + +### clibcni + +Compile clibcni from source code: +```sh +$ git clone http://dgggit09-rd.huawei.com/a/euleros/self_src/userspace/clibcni +$ cd clibcni +$ git checkout -b next_docker origin/next_docker +$ rm -rf build +$ mkdir build && cd build +$ cmake .. +$ make -j +$ sudo make install +$ sudo ldconfig +``` +if enbale testcase +```sh +$ rm -rf build +$ mkdir build && cd build +$ cmake -DENABLE_TESTS=ON .. +$ make -j +$ sudo make install +$ sudo ldconfig +$ cd tests && ./cni_test +$ cd - +``` + +### containernetworking plugins + +Compile containernetworking plugins from source code: +```sh +$ git clone http://code-sh.rnd.huawei.com/containers/plugins/plugins.git +$ cd plugins +$ git checkout critest +$ ./build.sh +$ mkdir -p /opt/cni/bin +$ cp bin/* /opt/cni/bin/ +``` + +### iSulad-kit + +Compile iSulad-kit from source code: +```sh +$ git clone http://dgggit09-rd.huawei.com/a/euleros/self_src/userspace/iSulad-kit +$ git clone http://dgggit09-rd.huawei.com/a/euleros/third_party/open_source/userspace/skopeo +$ cd skopeo +$ git checkout -b next_docker origin/next_docker +$ mkdir ./tmp +$ tar -zxf skopeo-e814f96.tar.gz --strip-components 1 -C ./tmp +$ cp -r ./tmp/vendor ../iSulad-kit/ +$ cd ../iSulad-kit +$ git checkout -b next_docker origin/next_docker +$ patch -p1 -F1 -s < ../skopeo/backport-update-vendor-to-e96a9b0e1b9019f9.patch +# apply the patchs +$ cp ./patch/* ./ +$ cat series-patch.conf | while read line + do + if [[ $line == '' || $line =~ ^\s*# ]]; then + continue + fi + patch -p1 -F1 -s < $line + done +$ make -j +$ make install +``` + +### LXC + +Compile lxc from source code: +```sh +$ git clone http://dgggit09-rd.huawei.com/a/euleros/third_party/open_source/userspace/lxc +$ cd lxc +$ git checkout -b next_docker origin/next_docker +$ tar xf lxc-3.0.3.tar.gz +$ cd lxc-3.0.3 +$ mv ../*.patch . +# official patch +$ for var in $(ls lxc-*.patch | sort -n) + do + if [[ "$var" =~ "CVE-2019-5736" ]]; then + echo "ignoring CVE patch cause valgrind can not work" + continue + fi + patch -p1 < ${var} + done + # self-developing patch +$ for var in $(ls huawei-*.patch | sort -n) + do + patch -p1 < ${var} + done +$ ./autogen.sh +$ ./configure +$ make -j (If the GCC version on the system is greater than 7, please add CFLAGS="-Wno-error" option) +$ sudo make install +$ sudo ldconfig +``` + +### huawei securec library + +Compile huawei securec library from source code: +```sh +$ git clone git@code-sh.huawei.com:containers/securec.git +$ cd securec +$ ./autogen.sh +$ ./configue +$ make -j $(nproc) +$ sudo make install +$ sudo ldconfig +``` +### LCR + +Note: If you encounter an error like "You must install [project] >= [version]" during executing "./configure", +please export the environment variable +```sh +$ export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig +``` + +Compile lcr from source code: +```sh +$ git clone http://dgggit09-rd.huawei.com/a/euleros/self_src/userspace/lcr +$ cd lcr +$ git checkout -b next_docker origin/next_docker +$ mkdir -p build +$ cd build +$ cmake ../ +$ make -j +$ sudo make install +$ sudo ldconfig +``` + +## Build LCRD +Note: If you encounter an error like "not found libcurl" during executing "./configure" on the ubuntu system, +please execute the following command: +```sh +$ sudo apt-get install libcurl4-gnutls-dev +``` + +In most cases, if we do not need to change the interface API(container.proto), just build the server and client like this: +```sh +$ git clone http://dgggit09-rd.huawei.com/a/euleros/self_src/userspace/iSulad +$ cd iSulad +$ git checkout -b next_docker origin/next_docker +$ rm -rf build +$ mkdir build && cd build +# To enable gRPC, configure lcrd by default +$ cmake ../ +$ make -j (If the GCC version on the system is greater than 7, please add CFLAGS="-Wno-error" option) +$ sudo make install +$ sudo ldconfig +``` + +## Run + +### Start daemon +Note: if you encounter an error like "error while loading shared libraries" when start the daemon , +please execute the following command: +```sh +$ sudo echo "/usr/local/lib" >> /etc/ld.so.conf +``` + +You should have built and installed lcrd and lcrc. To run the daemon: +```sh +$ sudo lcrd # run the lcrd server with default socket name and default log level and images manage function +``` + +### Download rootfs + +To create a container, you should have downloaded rootfs to your platform like this: +```sh +$ mkdir $HOME/myrootfs +$ sudo lcr-pull --name ubuntu --rootfs $HOME/myrootfs --dist ubuntu -r xenial -a amd64 +``` +If lcrd started with the images manage function you can download images from registry (e.g., docker.io) + +### Operations on containers: + +```sh +$ sudo lcrc ps -a # list containers +# create a container 'ubuntu1' with the directory +$ sudo lcrc create -n ubuntu1 --external-rootfs $HOME/myrootfs/ none +# or, you can create a container with OverlayFS +$ sudo mkdir $HOME/upperdir/ # create the upperdir for OverlayFS +$ sudo lcrc create -n 'ubuntu1' --external-rootfs overlayfs:$HOME/myrootfs:$HOME/upperdir none +$ sudo lcrc start ubuntu1 # start the container 'ubuntu1' +$ sudo lcrc kill ubuntu1 # kill the container 'ubuntu1' +``` diff --git a/docs/limitations.md b/docs/limitations.md new file mode 100644 index 0000000..e69de29 diff --git a/iSulad.spec b/iSulad.spec new file mode 100644 index 0000000..11b9afc --- /dev/null +++ b/iSulad.spec @@ -0,0 +1,187 @@ +%global _version 1.0.31 +%global _release 20190919.232053.gitf0f8c706 +%global is_systemd 1 +%global debug_package %{nil} + +Name: iSulad +Version: %{_version} +Release: %{_release}%{?dist} +Summary: Lightweight Container Runtime Daemon +License: Mulan PSL v1 +BuildRoot: {_tmppath}/%{name}-%{version} +ExclusiveArch: x86_64 aarch64 +URL: http://code.huawei.com/containers/lcrd +Source: %{name}-1.0.tar.gz + +%ifarch x86_64 aarch64 +Provides: libhttpclient.so()(64bit) +Provides: liblcrc.so()(64bit) +%endif + +%if 0%{?is_systemd} +# Systemd 230 and up no longer have libsystemd-journal +BuildRequires: pkgconfig(systemd) +Requires: systemd-units +%else +Requires(post): chkconfig +Requires(preun): chkconfig +# This is for /sbin/service +Requires(preun): initscripts +%endif + +BuildRequires: cmake gcc-c++ lxc lxc-devel lcr yajl yajl-devel clibcni-devel +BuildRequires: grpc grpc-devel protobuf-devel grpc-plugins +BuildRequires: libsecurec libsecurec-devel libcurl libcurl-devel sqlite-devel +BuildRequires: http-parser-devel libevhtp-devel libevent-devel +BuildRequires: libseccomp-devel libcap-devel libwebsockets libwebsockets-devel +BuildRequires: systemd-devel git + +Requires: iSulad-kit lcr lxc clibcni +Requires: grpc protobuf yajl +Requires: libcurl libsecurec +Requires: sqlite http-parser libseccomp +Requires: libcap libwebsockets +Requires: libevhtp libevent systemd + +%description +This is a umbrella project for gRPC-services based Lightweight Container +Runtime Daemon, written by C. + +%prep +%autosetup -c -n %{name}-%{version} + +%build +mkdir -p build +cd build +%cmake -DDEBUG=OFF -DLIB_INSTALL_DIR=%{_libdir} -DCMAKE_INSTALL_PREFIX=/usr ../ +%make_build + +%install +rm -rf %{buildroot} +cd build +install -d $RPM_BUILD_ROOT/%{_libdir} +install -m 0644 ./src/liblcrc.so %{buildroot}/%{_libdir}/liblcrc.so +install -m 0644 ./src/http/libhttpclient.so %{buildroot}/%{_libdir}/libhttpclient.so + +install -d $RPM_BUILD_ROOT/%{_libdir}/pkgconfig +install -m 0640 ./conf/lcrd.pc %{buildroot}/%{_libdir}/pkgconfig/lcrd.pc + +install -d $RPM_BUILD_ROOT/%{_bindir} +install -m 0755 ./src/lcrc %{buildroot}/%{_bindir}/lcrc +install -m 0755 ./src/lcrd %{buildroot}/%{_bindir}/lcrd + +install -d $RPM_BUILD_ROOT/%{_includedir}/lcrd +install -m 0644 ../src/liblcrc.h %{buildroot}/%{_includedir}/lcrd/liblcrc.h +install -m 0644 ../src/connect/client/lcrc_connect.h %{buildroot}/%{_includedir}/lcrd/lcrc_connect.h +install -m 0644 ../src/container_def.h %{buildroot}/%{_includedir}/lcrd/container_def.h +install -m 0644 ../src/types_def.h %{buildroot}/%{_includedir}/lcrd/types_def.h +install -m 0644 ../src/error.h %{buildroot}/%{_includedir}/lcrd/error.h +install -m 0644 ../src/engines/engine.h %{buildroot}/%{_includedir}/lcrd/engine.h + +install -d $RPM_BUILD_ROOT/%{_sysconfdir}/isulad +install -m 0640 ../src/contrib/config/daemon.json %{buildroot}/%{_sysconfdir}/isulad/daemon.json +install -m 0640 ../src/contrib/config/seccomp_default.json %{buildroot}/%{_sysconfdir}/isulad/seccomp_default.json + +install -d $RPM_BUILD_ROOT/%{_sysconfdir}/default/lcrd +install -m 0640 ../src/contrib/config/config.json %{buildroot}/%{_sysconfdir}/default/lcrd/config.json +install -m 0550 ../src/contrib/sysmonitor/isulad-check.sh %{buildroot}/%{_sysconfdir}/default/lcrd/isulad-check.sh + +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/sysmonitor/process +cp ../src/contrib/sysmonitor/isulad-monit $RPM_BUILD_ROOT/etc/sysmonitor/process + +install -d $RPM_BUILD_ROOT/%{_sysconfdir}/default/lcrd/hooks +install -m 0640 ../src/contrib/config/hooks/default.json %{buildroot}/%{_sysconfdir}/default/lcrd/hooks/default.json + +install -d $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig +install -p -m 0640 ../src/contrib/config/iSulad.sysconfig $RPM_BUILD_ROOT/%{_sysconfdir}/sysconfig/iSulad + +%if 0%{?is_systemd} +install -d $RPM_BUILD_ROOT/%{_unitdir} +install -p -m 0640 ../src/contrib/init/lcrd.service $RPM_BUILD_ROOT/%{_unitdir}/lcrd.service +%else +install -d $RPM_BUILD_ROOT/%{_initddir} +install -p -m 0640 ../src/contrib/init/lcrd.init $RPM_BUILD_ROOT/%{_initddir}/lcrd.init +%endif + +%clean +rm -rf %{buildroot} + +%post +if ! getent group lcrd > /dev/null; then + groupadd --system lcrd +fi + +if [ "$1" = "1" ]; then +%if 0%{?is_systemd} +systemctl enable lcrd +systemctl start lcrd +%else +/sbin/chkconfig --add lcrd +%endif +elif [ "$1" = "2" ]; then +%if 0%{?is_systemd} +systemctl status lcrd | grep 'Active:' | grep 'running' +if [ $? -eq 0 ]; then + systemctl restart lcrd +fi +%else +/sbin/service lcrd status | grep 'Active:' | grep 'running' +if [ $? -eq 0 ]; then + /sbin/service lcrd restart +fi +%endif +fi + +if ! getent group lcrd > /dev/null; then + groupadd --system lcrd +fi + +%preun +%if 0%{?is_systemd} +%systemd_preun lcrd +%else +if [ $1 -eq 0 ] ; then + /sbin/service lcrd stop >/dev/null 2>&1 + /sbin/chkconfig --del lcrd +fi +%endif + +%postun +%if 0%{?is_systemd} +%systemd_postun_with_restart lcrd +%else +if [ "$1" -ge "1" ] ; then + /sbin/service lcrd condrestart >/dev/null 2>&1 || : +fi +%endif + +%files +%attr(0600,root,root) %{_sysconfdir}/sysmonitor/process/isulad-monit +%attr(0550,root,root) %{_sysconfdir}/default/lcrd/isulad-check.sh +%defattr(0640,root,root,0750) +%{_sysconfdir}/isulad/* +%{_sysconfdir}/default/* +%defattr(-,root,root,-) +%if 0%{?is_systemd} +%{_unitdir}/lcrd.service +%attr(0640,root,root) %{_unitdir}/lcrd.service +%else +%{_initddir}/lcrd.init +%attr(0640,root,root) %{_initddir}/lcrd.init +%endif +%{_includedir}/lcrd/* +%attr(0755,root,root) %{_libdir}/pkgconfig +%attr(0640,root,root) %{_libdir}/pkgconfig/lcrd.pc +%defattr(0550,root,root,0750) +%{_bindir}/* +%{_libdir}/* +%attr(0640,root,root) %{_sysconfdir}/sysconfig/iSulad +%attr(0640,root,root) %{_sysconfdir}/isulad/daemon.json + +%config(noreplace,missingok) %{_sysconfdir}/sysconfig/iSulad +%config(noreplace,missingok) %{_sysconfdir}/isulad/daemon.json +%if 0%{?is_systemd} +%config(noreplace,missingok) %{_unitdir}/lcrd.service +%else +%config(noreplace,missingok) %{_initddir}/lcrd.init +%endif diff --git a/lcrd.pc.in b/lcrd.pc.in new file mode 100644 index 0000000..48b72c0 --- /dev/null +++ b/lcrd.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@CMAKE_INSTALL_PREFIX@/lib +localstatedir=@CMAKE_INSTALL_PREFIX@/var +includedir=@CMAKE_INSTALL_PREFIX@/include + +Name: liblcrc +Description: light-weighted container runtime daemon library +Version: @LCRD_VERSION@ +URL: http://code.huawei.com/containers/iSulad +Libs: -L@CMAKE_INSTALL_PREFIX@/lib -llcrc +Cflags: -I@CMAKE_INSTALL_PREFIX@/include + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..2eff80a --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,215 @@ +# generate .c and .h to analyse json file +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/json) +# get json generate source files +aux_source_directory(${CMAKE_BINARY_DIR}/json generatesrcs) +message("-- Get generate srcs: " ${generatesrcs}) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/json/schema/src commonjsonsrcs) +message("-- Get common json srcs: " ${commonjsonsrcs}) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/json topjsonsrcs) +message("-- Get top json srcs: " ${topjsonsrcs}) +list(APPEND JSON_FILES ${generatesrcs} ${commonjsonsrcs} ${topjsonsrcs}) +list(REMOVE_DUPLICATES JSON_FILES) + +set(CHECKED_INCLUDE_DIRS + ${STD_HEADER_CTYPE} + ${STD_HEADER_SYS_PARAM} + ${LIBSECUREC_INCLUDE_DIR} + ${LIBYAJL_INCLUDE_DIR} + ${HTTP_PARSER_INCLUDE_DIR} + ${OPENSSL_INCLUDE_DIR} + ${CURL_INCLUDE_DIR} + ${SYSTEMD_INCLUDE_DIR} + ) +if (GRPC_CONNECTOR) + list(APPEND CHECKED_INCLUDE_DIRS + ${GRPC_INCLUDE_DIR} + ${CLIBCNI_INCLUDE_DIR} + ${WEBSOCKET_INCLUDE_DIR} + ) +else() + list(APPEND CHECKED_INCLUDE_DIRS + ${SQLIT3_INCLUDE_DIR} + ${EVENT_INCLUDE_DIR} + ${EVHTP_INCLUDE_DIR} + ) +endif() +list(REMOVE_DUPLICATES CHECKED_INCLUDE_DIRS) + +set(SHARED_INCS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/cutils + ${CMAKE_CURRENT_SOURCE_DIR}/sha256 + ${CMAKE_CURRENT_SOURCE_DIR}/tar + ${CMAKE_CURRENT_SOURCE_DIR}/console + ${CMAKE_CURRENT_SOURCE_DIR}/json + ${CMAKE_CURRENT_SOURCE_DIR}/json/schema/src + ${CMAKE_BINARY_DIR}/json + ${CMAKE_BINARY_DIR}/conf + ${CHECKED_INCLUDE_DIRS} + ) + +add_subdirectory(tar) +add_subdirectory(sha256) +add_subdirectory(cutils) +add_subdirectory(console) + +set(SHARED_SRCS + ${JSON_FILES} + ${TAR_SRCS} + ${SHA256_SRCS} + ${CUTILS_SRCS} + ${CONSOLE_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/container_def.c + ${CMAKE_CURRENT_SOURCE_DIR}/types_def.c + ${CMAKE_CURRENT_SOURCE_DIR}/error.c + ${CMAKE_CURRENT_SOURCE_DIR}/path.c + ${CMAKE_CURRENT_SOURCE_DIR}/log.c + ${CMAKE_CURRENT_SOURCE_DIR}/mainloop.c + ) + +# get all c and header files +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/connect) + +if (GRPC_CONNECTOR) + # GRPC + aux_source_directory(${CMAKE_BINARY_DIR}/grpc/src/api/types CONNECT_API_TYPES) + aux_source_directory(${CMAKE_BINARY_DIR}/grpc/src/api/services/containers CONNECT_API_CONTAINERS) + aux_source_directory(${CMAKE_BINARY_DIR}/grpc/src/api/services/images CONNECT_API_IMAGES) + aux_source_directory(${CMAKE_BINARY_DIR}/grpc/src/api/services/cri CONNECT_API_CRI) + set(CONNECT_API ${CONNECT_API_TYPES} ${CONNECT_API_CONTAINERS} ${CONNECT_API_IMAGES} ${CONNECT_API_CRI}) + + list(APPEND SHARED_INCS + ${CMAKE_BINARY_DIR}/grpc/src/api/types + ${CMAKE_BINARY_DIR}/grpc/src/api/services/containers + ${CMAKE_BINARY_DIR}/grpc/src/api/services/images + ${CMAKE_BINARY_DIR}/grpc/src/api/services/cri + ${CMAKE_CURRENT_SOURCE_DIR}/cpputils + ) + + add_subdirectory(cpputils) + add_subdirectory(websocket) + + list(APPEND SHARED_SRCS ${CPPUTILS_SRCS}) +else() + list(APPEND SHARED_INCS + ${CMAKE_CURRENT_SOURCE_DIR}/api/services/containers/rest + ${CMAKE_CURRENT_SOURCE_DIR}/api/services/images/rest + ) +endif() + +list(APPEND SHARED_SRCS ${CONNECT_API} ${CPPUTILS_SRCS}) +list(REMOVE_DUPLICATES SHARED_INCS) +list(REMOVE_DUPLICATES SHARED_SRCS) + +add_subdirectory(http) + +# ------ build liblcrc ------ +if (OPENSSL_VERIFY) + list(APPEND CONNECTOR ${CMAKE_CURRENT_SOURCE_DIR}/http/certificate.c) +endif() + +add_library(liblcrc ${LIBTYPE} + ${CMAKE_CURRENT_SOURCE_DIR}/liblcrc.c + ${CMAKE_CURRENT_SOURCE_DIR}/pack_config.c + ${CONNECTOR} + ${SHARED_SRCS} + ) + +target_include_directories(liblcrc PUBLIC + ${SHARED_INCS} + ${CONNECTOR_INCS} + ${CMAKE_CURRENT_SOURCE_DIR}/http + ) + +# set liblcrc FLAGS +set_target_properties(liblcrc PROPERTIES PREFIX "") +target_link_libraries(liblcrc ${LIBYAJL_LIBRARY} ${LIBSECUREC_LIBRARY}) + +if (GRPC_CONNECTOR) + target_link_libraries(liblcrc -Wl,--as-needed -lstdc++ -lcrypto) + target_link_libraries(liblcrc -Wl,--as-needed ${PROTOBUF_LIBRARY}) + target_link_libraries(liblcrc -Wl,--no-as-needed ${GRPC_PP_REFLECTION_LIBRARY} ${GRPC_PP_LIBRARY} ${GRPC_LIBRARY} ${GPR_LIBRARY}) +else() + target_link_libraries(liblcrc ${EVHTP_LIBRARY} ${EVENT_LIBRARY} ${ZLIB_LIBRARY} -ldl libhttpclient) +endif() +# ------ build liblcrc finish ----- + +add_subdirectory(cmd) +# ------ build lcrc ------- +add_executable(lcrc + ${LCRC_SRCS} + ) +target_include_directories(lcrc PUBLIC ${LCRC_INCS} ${SHARED_INCS}) +target_link_libraries(lcrc liblcrc -lpthread) +# ------ build lcrc finish ------- + +# ------ build lcrd ------- +add_subdirectory(services) +add_subdirectory(image) +add_subdirectory(engines) + +add_subdirectory(plugin) +add_subdirectory(map) +add_subdirectory(config) + +add_executable(lcrd + ${CONNECT_SOCKET} ${SHARED_SRCS} + ${LCRD_SRCS} ${SERVICES_SRCS} + ${HTTP_SRCS} + ${ENGINES_SRCS} + ${IMAGE_SRCS} + ${PLUGIN_SRCS} + ${MAP_SRCS} ${CONFIG_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/filters.c + ${CMAKE_CURRENT_SOURCE_DIR}/namespace.c + ${CMAKE_CURRENT_SOURCE_DIR}/liblcrd.c + ${CMAKE_CURRENT_SOURCE_DIR}/sysctl_tools.c + ${WEBSOCKET_SERVICE_SRCS} + ) + +target_include_directories(lcrd PUBLIC + ${SHARED_INCS} + ${CONNECT_SOCKET_INCS} + ${SERVICES_INCS} + ${IMAGE_INCS} + ${ENGINES_INCS} + ${LCRD_INCS} + ${CMAKE_CURRENT_SOURCE_DIR}/plugin + ${CMAKE_CURRENT_SOURCE_DIR}/map + ${CMAKE_CURRENT_SOURCE_DIR}/config + ${CMAKE_CURRENT_SOURCE_DIR}/http + ${WEBSOCKET_SERVICE_INCS} + ) + +target_link_libraries(lcrd ${LIBYAJL_LIBRARY} ${LIBSECUREC_LIBRARY} ${SYSTEMD_LIBRARY}) +target_link_libraries(lcrd -ldl ${ZLIB_LIBRARY} -lpthread libhttpclient) +if (ENABLE_EMBEDDED) + target_link_libraries(lcrd ${SQLITE3_LIBRARY}) +endif() + +if (GRPC_CONNECTOR) + message("GRPC iSulad") + target_link_libraries(lcrd -Wl,--as-needed -lstdc++ -lcrypto) + target_link_libraries(lcrd -Wl,--as-needed ${PROTOBUF_LIBRARY}) + target_link_libraries(lcrd -Wl,--no-as-needed ${GRPC_PP_REFLECTION_LIBRARY} ${GRPC_PP_LIBRARY} ${GRPC_LIBRARY} ${GPR_LIBRARY}) + target_link_libraries(lcrd ${CLIBCNI_LIBRARY} ${WEBSOCKET_LIBRARY}) +else() + message("Restful iSulad") + target_link_libraries(lcrd ${EVHTP_LIBRARY} ${EVENT_LIBRARY}) +endif() + +if (ISULAD_GCOV) + target_link_libraries(lcrc -lgcov) + target_link_libraries(liblcrc -lgcov) + target_link_libraries(lcrd -lgcov) +endif() + +# ------ build lcrd finish ------- + +# ------ install binary -------- +install(TARGETS liblcrc + LIBRARY DESTINATION ${LIB_INSTALL_DIR_DEFAULT} PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE) +install(TARGETS lcrc + RUNTIME DESTINATION bin PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE) +install(TARGETS lcrd + RUNTIME DESTINATION bin PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE) diff --git a/src/api/services/containers/container.proto b/src/api/services/containers/container.proto new file mode 100644 index 0000000..a42dea4 --- /dev/null +++ b/src/api/services/containers/container.proto @@ -0,0 +1,447 @@ +syntax = "proto3"; +option optimize_for = CODE_SIZE; + +import "google/protobuf/timestamp.proto"; + +package containers; + +enum ContainerStatus { + UNKNOWN = 0; + CREATED = 1; + STARTING = 2; + RUNNING = 3; + STOPPED = 4; + PAUSED = 5; + RESTARTING = 6; +} + +enum EventType { + EXIT = 0; + STOPPED1 = 1; + STARTING1 = 2; + RUNNING1 = 3; + STOPPING = 4; + ABORTING = 5; + FREEZING = 6; + FROZEN = 7; + THAWED = 8; + OOM = 9; + CREATE = 10; + START = 11; + EXEC_ADDED = 12; + PAUSED1 = 13; +} + +message Container { + string id = 1; + int32 pid = 2; + ContainerStatus status = 3; + string interface = 4; + string ipv4 = 5; + string ipv6 = 6; + string image = 7; + string command = 8; + double ram = 9; + double swap = 10; + uint32 exit_code = 11; + uint64 restartcount = 12; + string startat = 13; + string finishat = 14; + string runtime = 15; + string name = 16; + string health_state = 17; +} + +message Container_info { + string id = 1; + int32 pid = 2; + ContainerStatus status = 3; + uint64 pids_current = 4; + uint64 cpu_use_nanos = 5; + uint64 cpu_use_user = 6; + uint64 cpu_use_kernel = 7; + uint64 cpu_system_use = 8; + uint32 online_cpus = 9; + uint64 blkio_read = 10; + uint64 blkio_write = 11; + uint64 mem_used = 12; + uint64 mem_limit = 13; + uint64 kmem_used = 14; + uint64 kmem_limit = 15; +} + +message Event { + string id = 1; + EventType type = 2; + int32 pid = 3; + uint32 exit_status = 4; + google.protobuf.Timestamp timestamp = 5; +} + +service ContainerService { + rpc Create(CreateRequest) returns (CreateResponse); + rpc Start(StartRequest) returns (StartResponse); + rpc RemoteStart(stream RemoteStartRequest) returns (stream RemoteStartResponse); + rpc Top(TopRequest) returns (TopResponse); + rpc Stop(StopRequest) returns (StopResponse); + rpc Kill(KillRequest) returns (KillResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + rpc Pause(PauseRequest) returns (PauseResponse); + rpc Resume(ResumeRequest) returns (ResumeResponse); + rpc Inspect(InspectContainerRequest) returns (InspectContainerResponse); + rpc List(ListRequest) returns (ListResponse); + rpc Stats(StatsRequest) returns (StatsResponse); + rpc Wait(WaitRequest) returns (WaitResponse); + rpc Events(EventsRequest) returns (stream Event); + rpc Exec(ExecRequest) returns (ExecResponse); + rpc RemoteExec(stream RemoteExecRequest) returns (stream RemoteExecResponse); + rpc Version(VersionRequest) returns (VersionResponse); + rpc Info(InfoRequest) returns (InfoResponse); + rpc Update(UpdateRequest) returns (UpdateResponse); + rpc Attach(stream AttachRequest) returns (stream AttachResponse); + rpc Container_conf(Container_conf_Request) returns (Container_conf_Response); + rpc Restart(RestartRequest) returns (RestartResponse); + rpc Export(ExportRequest) returns (ExportResponse); + rpc CopyFromContainer(CopyFromContainerRequest) returns (stream CopyFromContainerResponse); + rpc CopyToContainer(stream CopyToContainerRequest) returns (stream CopyToContainerResponse); + rpc Rename(RenameRequest) returns (RenameResponse); + rpc Logs(LogsRequest) returns (stream LogsResponse); +} + +message CreateRequest { + string id = 1; + string rootfs = 2; + // Image contains the reference of the image used to build the + // specification and snapshots for running this container. + // + string image = 3; + string runtime = 4; + string hostconfig = 5; + string customconfig = 6; +} + +message CreateResponse { + string id = 1; + int32 pid = 2; + uint32 cc = 3; + string errmsg = 4; +} + +message StartRequest { + string id = 1; + string stdin = 2; + bool attach_stdin = 3; + string stdout = 4; + bool attach_stdout = 5; + string stderr = 6; + bool attach_stderr = 7; +} + +message StartResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message RemoteStartRequest { + bytes stdin = 1; + bool finish = 2; +} + +message RemoteStartResponse { + bytes stdout = 1; + bytes stderr = 2; + bool finish = 3; +} + +message TopRequest { + string id = 1; + repeated string args = 2; +} + +message TopResponse { + bytes titles = 1; + repeated bytes processes = 2; + uint32 cc = 3; + string errmsg = 4; +} + +message StopRequest { + string id = 1; + bool force = 2; + int32 timeout = 3; +} + +message StopResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message RestartRequest { + string id = 1; + int32 timeout = 2; +} + +message RestartResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message KillRequest { + string id = 1; + uint32 signal = 2; +} + +message KillResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message DeleteRequest { + string id = 1; + bool force = 2; +} + +message DeleteResponse { + string id = 1; + uint32 exit_status = 2; + uint32 cc = 3; + string errmsg = 4; +} + +message PauseRequest { + string id = 1; +} + +message PauseResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message ResumeRequest { + string id = 1; +} + +message ResumeResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message InspectContainerRequest { + string id = 1; + bool bformat = 2; + int32 timeout = 3; +} + +message InspectContainerResponse { + string ContainerJSON = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message ListRequest { + map filters = 1; + bool all = 2; +} + +message ListResponse { + repeated Container containers = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message StatsRequest { + string runtime = 1; + repeated string containers = 2; + bool all = 3; +} + +message StatsResponse { + repeated Container_info containers = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message WaitRequest { + string id = 1; + uint32 condition = 2; +} + +message WaitResponse { + uint32 cc = 1; + uint32 exit_code = 2; + string errmsg = 3; +} + +message EventsRequest { + google.protobuf.Timestamp since = 1; + google.protobuf.Timestamp until = 2; + bool storeOnly = 3; + string id = 4; +} +message ExecRequest { + // ContainerID specifies the container in which to exec the process. + string container_id = 1; + bool tty = 2; + bool open_stdin = 3; + bool attach_stdin = 4; + bool attach_stdout = 5; + bool attach_stderr = 6; + string stdin = 7; + string stdout = 8; + string stderr = 9; + repeated string argv = 10; + repeated string env = 11; +} +message ExecResponse { + int32 pid = 1; + uint32 exit_code = 2; + uint32 cc = 3; + string errmsg = 4; +} + +message RemoteExecRequest { + repeated bytes cmd = 1; + bool finish = 2; +} +message RemoteExecResponse { + bytes stdout = 1; + bool finish = 2; +} + +message AttachRequest { + bytes stdin = 1; + bool finish = 2; +} + +message AttachResponse { + bytes stdout = 1; + bytes stderr = 2; + bool finish = 3; +} + +message VersionRequest { +} + +message VersionResponse { + string version = 1; + string git_commit = 2; + string build_time = 3; + string root_path = 4; + uint32 cc = 5; + string errmsg = 6; +} +message InfoRequest { +} + +message InfoResponse { + uint32 cc = 1; + string errmsg = 2; + string version = 3; + uint32 containers_num = 4; + uint32 c_running = 5; + uint32 c_paused = 6; + uint32 c_stopped = 7; + uint32 images_num = 8; + string kversion = 9; + string os_type = 10; + string architecture = 11; + string nodename = 12; + uint32 cpus = 13; + string operating_system = 14; + string cgroup_driver = 15; + string logging_driver = 16; + string huge_page_size = 17; + string isulad_root_dir = 18; + uint32 total_mem = 19; + string http_proxy = 20; + string https_proxy = 21; + string no_proxy = 22; +} + +message UpdateRequest { + string id = 1; + string hostconfig = 2; +} + +message UpdateResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} +message Container_conf_Request { + // ContainerID specifies the container in which to get config. + string container_id = 1; + string runtime = 2; +} + +message Container_conf_Response { + string container_logpath = 1; + string container_logsize = 2; + uint32 container_logrotate = 3; + uint32 cc = 4; + string errmsg = 5; +} + +message ExportRequest { + string id = 1; + string file = 2; +} + +message ExportResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message CopyFromContainerRequest { + string id = 1; + string runtime = 2; + string srcpath = 3; +} + +message CopyFromContainerResponse { + bytes data = 1; +} + +message CopyToContainerRequest { + bytes data = 1; +} + +message CopyToContainerResponse { + bool finish = 1; +} + +message RenameRequest { + string oldname = 1; + string newname = 2; +} + +message RenameResponse { + string id = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message LogsRequest { + string id = 1; + string runtime = 2; + string since = 3; + string until = 4; + bool timestamps = 5; + bool follow = 6; + int64 tail = 7; + bool details = 8; +} + +message LogsResponse { + bytes data = 1; + string stream = 2; + string time = 3; + bytes attrs = 4; +} diff --git a/src/api/services/containers/rest/container.rest.h b/src/api/services/containers/rest/container.rest.h new file mode 100644 index 0000000..ce50c73 --- /dev/null +++ b/src/api/services/containers/rest/container.rest.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-22 + * Description: provide container rest definition + **********************************************************************************/ +#ifndef CONTAINER_REST_H_ +#define CONTAINER_REST_H_ + +#include "container_create_request.h" +#include "container_create_response.h" +#include "container_start_request.h" +#include "container_start_response.h" +#include "container_stop_request.h" +#include "container_stop_response.h" +#include "container_restart_request.h" +#include "container_restart_response.h" +#include "container_pause_request.h" +#include "container_pause_response.h" +#include "container_kill_request.h" +#include "container_kill_response.h" +#include "container_update_request.h" +#include "container_update_response.h" +#include "container_version_request.h" +#include "container_version_response.h" +#include "container_exec_request.h" +#include "container_exec_response.h" +#include "container_delete_request.h" +#include "container_delete_response.h" +#include "container_inspect_request.h" +#include "container_inspect_response.h" +#include "container_list_request.h" +#include "container_list_response.h" +#include "container_attach_request.h" +#include "container_attach_response.h" +#include "container_resume_request.h" +#include "container_resume_response.h" +#include "container_wait_request.h" +#include "container_wait_response.h" +#include "container_conf_request.h" +#include "container_conf_response.h" + +#ifndef RestHttpHead +#define RestHttpHead "http://localhost" +#endif + +#define ContainerServiceCreate "/ContainerService/Create" +#define ContainerServiceStart "/ContainerService/Start" +#define ContainerServiceRestart "/ContainerService/Restart" +#define ContainerServiceStop "/ContainerService/Stop" +#define ContainerServiceVersion "/ContainerService/Version" +#define ContainerServiceUpdate "/ContainerService/Update" +#define ContainerServicePause "/ContainerService/Pause" +#define ContainerServiceKill "/ContainerService/Kill" +#define ContainerServiceExec "/ContainerService/Exec" +#define ContainerServiceRemove "/ContainerService/Remove" +#define ContainerServiceInspect "/ContainerService/Inspect" +#define ContainerServiceList "/ContainerService/List" +#define ContainerServiceAttach "/ContainerService/Attach" +#define ContainerServiceResume "/ContainerService/Resume" +#define ContainerServiceWait "/ContainerService/Wait" +#define ContainerServiceConf "/ContainerService/Container_conf" + +/* "/ContainerService/Kill", +"/ContainerService/Delete", +"/ContainerService/Pause", +"/ContainerService/Info", +"/ContainerService/Inspect", +"/ContainerService/Stats", +"/ContainerService/Events", +"/ContainerService/Exec", +"/ContainerService/Version", +"/ContainerService/Update", +"/ContainerService/Attach", +*/ +#endif diff --git a/src/api/services/cri/api.proto b/src/api/services/cri/api.proto new file mode 100644 index 0000000..d0fefba --- /dev/null +++ b/src/api/services/cri/api.proto @@ -0,0 +1,1159 @@ +// To regenerate api.pb.go run hack/update-generated-runtime.sh +syntax = 'proto3'; + +package runtime; + +//import "gogo.proto"; + +//option (gogoproto.goproto_stringer_all) = false; +//option (gogoproto.stringer_all) = true; +//option (gogoproto.goproto_getters_all) = true; +//option (gogoproto.marshaler_all) = true; +//option (gogoproto.sizer_all) = true; +//option (gogoproto.unmarshaler_all) = true; +//option (gogoproto.goproto_unrecognized_all) = false; + +// Runtime service defines the public APIs for remote container runtimes +service RuntimeService { + // Version returns the runtime name, runtime version, and runtime API version. + rpc Version(VersionRequest) returns (VersionResponse) {} + + // RunPodSandbox creates and starts a pod-level sandbox. Runtimes must ensure + // the sandbox is in the ready state on success. + rpc RunPodSandbox(RunPodSandboxRequest) returns (RunPodSandboxResponse) {} + // StopPodSandbox stops any running process that is part of the sandbox and + // reclaims network resources (e.g., IP addresses) allocated to the sandbox. + // If there are any running containers in the sandbox, they must be forcibly + // terminated. + // This call is idempotent, and must not return an error if all relevant + // resources have already been reclaimed. kubelet will call StopPodSandbox + // at least once before calling RemovePodSandbox. It will also attempt to + // reclaim resources eagerly, as soon as a sandbox is not needed. Hence, + // multiple StopPodSandbox calls are expected. + rpc StopPodSandbox(StopPodSandboxRequest) returns (StopPodSandboxResponse) {} + // RemovePodSandbox removes the sandbox. If there are any running containers + // in the sandbox, they must be forcibly terminated and removed. + // This call is idempotent, and must not return an error if the sandbox has + // already been removed. + rpc RemovePodSandbox(RemovePodSandboxRequest) returns (RemovePodSandboxResponse) {} + // PodSandboxStatus returns the status of the PodSandbox. If the PodSandbox is not + // present, returns an error. + rpc PodSandboxStatus(PodSandboxStatusRequest) returns (PodSandboxStatusResponse) {} + // ListPodSandbox returns a list of PodSandboxes. + rpc ListPodSandbox(ListPodSandboxRequest) returns (ListPodSandboxResponse) {} + + // CreateContainer creates a new container in specified PodSandbox + rpc CreateContainer(CreateContainerRequest) returns (CreateContainerResponse) {} + // StartContainer starts the container. + rpc StartContainer(StartContainerRequest) returns (StartContainerResponse) {} + // StopContainer stops a running container with a grace period (i.e., timeout). + // This call is idempotent, and must not return an error if the container has + // already been stopped. + // Note: what must the runtime do after the grace period is reached? + rpc StopContainer(StopContainerRequest) returns (StopContainerResponse) {} + // RemoveContainer removes the container. If the container is running, the + // container must be forcibly removed. + // This call is idempotent, and must not return an error if the container has + // already been removed. + rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse) {} + // ListContainers lists all containers by filters. + rpc ListContainers(ListContainersRequest) returns (ListContainersResponse) {} + // ContainerStatus returns status of the container. If the container is not + // present, returns an error. + rpc ContainerStatus(ContainerStatusRequest) returns (ContainerStatusResponse) {} + // UpdateContainerResources updates ContainerConfig of the container. + rpc UpdateContainerResources(UpdateContainerResourcesRequest) returns (UpdateContainerResourcesResponse) {} + + // ExecSync runs a command in a container synchronously. + rpc ExecSync(ExecSyncRequest) returns (ExecSyncResponse) {} + // Exec prepares a streaming endpoint to execute a command in the container. + rpc Exec(ExecRequest) returns (ExecResponse) {} + // Attach prepares a streaming endpoint to attach to a running container. + rpc Attach(AttachRequest) returns (AttachResponse) {} + // PortForward prepares a streaming endpoint to forward ports from a PodSandbox. + rpc PortForward(PortForwardRequest) returns (PortForwardResponse) {} + + // ContainerStats returns stats of the container. If the container does not + // exist, the call returns an error. + rpc ContainerStats(ContainerStatsRequest) returns (ContainerStatsResponse) {} + // ListContainerStats returns stats of all running containers. + rpc ListContainerStats(ListContainerStatsRequest) returns (ListContainerStatsResponse) {} + + // UpdateRuntimeConfig updates the runtime configuration based on the given request. + rpc UpdateRuntimeConfig(UpdateRuntimeConfigRequest) returns (UpdateRuntimeConfigResponse) {} + + // Status returns the status of the runtime. + rpc Status(StatusRequest) returns (StatusResponse) {} +} + +// ImageService defines the public APIs for managing images. +service ImageService { + // ListImages lists existing images. + rpc ListImages(ListImagesRequest) returns (ListImagesResponse) {} + // ImageStatus returns the status of the image. If the image is not + // present, returns a response with ImageStatusResponse.Image set to + // nil. + rpc ImageStatus(ImageStatusRequest) returns (ImageStatusResponse) {} + // PullImage pulls an image with authentication config. + rpc PullImage(PullImageRequest) returns (PullImageResponse) {} + // RemoveImage removes the image. + // This call is idempotent, and must not return an error if the image has + // already been removed. + rpc RemoveImage(RemoveImageRequest) returns (RemoveImageResponse) {} + // ImageFSInfo returns information of the filesystem that is used to store images. + rpc ImageFsInfo(ImageFsInfoRequest) returns (ImageFsInfoResponse) {} +} + +message VersionRequest { + // Version of the kubelet runtime API. + string version = 1; +} + +message VersionResponse { + // Version of the kubelet runtime API. + string version = 1; + // Name of the container runtime. + string runtime_name = 2; + // Version of the container runtime. The string must be + // semver-compatible. + string runtime_version = 3; + // API version of the container runtime. The string must be + // semver-compatible. + string runtime_api_version = 4; +} + +// DNSConfig specifies the DNS servers and search domains of a sandbox. +message DNSConfig { + // List of DNS servers of the cluster. + repeated string servers = 1; + // List of DNS search domains of the cluster. + repeated string searches = 2; + // List of DNS options. See https://linux.die.net/man/5/resolv.conf + // for all available options. + repeated string options = 3; +} + +enum Protocol { + TCP = 0; + UDP = 1; +} + +// PortMapping specifies the port mapping configurations of a sandbox. +message PortMapping { + // Protocol of the port mapping. + Protocol protocol = 1; + // Port number within the container. Default: 0 (not specified). + int32 container_port = 2; + // Port number on the host. Default: 0 (not specified). + int32 host_port = 3; + // Host IP. + string host_ip = 4; +} + +enum MountPropagation { + // No mount propagation ("private" in Linux terminology). + PROPAGATION_PRIVATE = 0; + // Mounts get propagated from the host to the container ("rslave" in Linux). + PROPAGATION_HOST_TO_CONTAINER = 1; + // Mounts get propagated from the host to the container and from the + // container to the host ("rshared" in Linux). + PROPAGATION_BIDIRECTIONAL = 2; +} + +// Mount specifies a host volume to mount into a container. +message Mount { + // Path of the mount within the container. + string container_path = 1; + // Path of the mount on the host. + string host_path = 2; + // If set, the mount is read-only. + bool readonly = 3; + // If set, the mount needs SELinux relabeling. + bool selinux_relabel = 4; + // Requested propagation mode. + MountPropagation propagation = 5; +} + +// NamespaceOption provides options for Linux namespaces. +message NamespaceOption { + // If set, use the host's network namespace. + bool host_network = 1; + // If set, use the host's PID namespace. + bool host_pid = 2; + // If set, use the host's IPC namespace. + bool host_ipc = 3; +} + +// Int64Value is the wrapper of int64. +message Int64Value { + // The value. + int64 value = 1; +} + +// LinuxSandboxSecurityContext holds linux security configuration that will be +// applied to a sandbox. Note that: +// 1) It does not apply to containers in the pods. +// 2) It may not be applicable to a PodSandbox which does not contain any running +// process. +message LinuxSandboxSecurityContext { + // Configurations for the sandbox's namespaces. + // This will be used only if the PodSandbox uses namespace for isolation. + NamespaceOption namespace_options = 1; + // Optional SELinux context to be applied. + SELinuxOption selinux_options = 2; + // UID to run sandbox processes as, when applicable. + Int64Value run_as_user = 3; + // If set, the root filesystem of the sandbox is read-only. + bool readonly_rootfs = 4; + // List of groups applied to the first process run in the sandbox, in + // addition to the sandbox's primary GID. + repeated int64 supplemental_groups = 5; + // Indicates whether the sandbox will be asked to run a privileged + // container. If a privileged container is to be executed within it, this + // MUST be true. + // This allows a sandbox to take additional security precautions if no + // privileged containers are expected to be run. + bool privileged = 6; + // Seccomp profile for the sandbox, candidate values are: + // * docker/default: the default profile for the docker container runtime + // * unconfined: unconfined profile, ie, no seccomp sandboxing + // * localhost/: the profile installed on the node. + // is the full path of the profile. + // Default: "", which is identical with unconfined. + string seccomp_profile_path = 7; +} + +// LinuxPodSandboxConfig holds platform-specific configurations for Linux +// host platforms and Linux-based containers. +message LinuxPodSandboxConfig { + // Parent cgroup of the PodSandbox. + // The cgroupfs style syntax will be used, but the container runtime can + // convert it to systemd semantics if needed. + string cgroup_parent = 1; + // LinuxSandboxSecurityContext holds sandbox security attributes. + LinuxSandboxSecurityContext security_context = 2; + // Sysctls holds linux sysctls config for the sandbox. + map sysctls = 3; +} + +// PodSandboxMetadata holds all necessary information for building the sandbox name. +// The container runtime is encouraged to expose the metadata associated with the +// PodSandbox in its user interface for better user experience. For example, +// the runtime can construct a unique PodSandboxName based on the metadata. +message PodSandboxMetadata { + // Pod name of the sandbox. Same as the pod name in the PodSpec. + string name = 1; + // Pod UID of the sandbox. Same as the pod UID in the PodSpec. + string uid = 2; + // Pod namespace of the sandbox. Same as the pod namespace in the PodSpec. + string namespace = 3; + // Attempt number of creating the sandbox. Default: 0. + uint32 attempt = 4; +} + +// PodSandboxConfig holds all the required and optional fields for creating a +// sandbox. +message PodSandboxConfig { + // Metadata of the sandbox. This information will uniquely identify the + // sandbox, and the runtime should leverage this to ensure correct + // operation. The runtime may also use this information to improve UX, such + // as by constructing a readable name. + PodSandboxMetadata metadata = 1; + // Hostname of the sandbox. + string hostname = 2; + // Path to the directory on the host in which container log files are + // stored. + // By default the log of a container going into the LogDirectory will be + // hooked up to STDOUT and STDERR. However, the LogDirectory may contain + // binary log files with structured logging data from the individual + // containers. For example, the files might be newline separated JSON + // structured logs, systemd-journald journal files, gRPC trace files, etc. + // E.g., + // PodSandboxConfig.LogDirectory = `/var/log/pods//` + // ContainerConfig.LogPath = `containerName_Instance#.log` + // + // WARNING: Log management and how kubelet should interface with the + // container logs are under active discussion in + // https://issues.k8s.io/24677. There *may* be future change of direction + // for logging as the discussion carries on. + string log_directory = 3; + // DNS config for the sandbox. + DNSConfig dns_config = 4; + // Port mappings for the sandbox. + repeated PortMapping port_mappings = 5; + // Key-value pairs that may be used to scope and select individual resources. + map labels = 6; + // Unstructured key-value map that may be set by the kubelet to store and + // retrieve arbitrary metadata. This will include any annotations set on a + // pod through the Kubernetes API. + // + // Annotations MUST NOT be altered by the runtime; the annotations stored + // here MUST be returned in the PodSandboxStatus associated with the pod + // this PodSandboxConfig creates. + // + // In general, in order to preserve a well-defined interface between the + // kubelet and the container runtime, annotations SHOULD NOT influence + // runtime behaviour. + // + // Annotations can also be useful for runtime authors to experiment with + // new features that are opaque to the Kubernetes APIs (both user-facing + // and the CRI). Whenever possible, however, runtime authors SHOULD + // consider proposing new typed fields for any new features instead. + map annotations = 7; + // Optional configurations specific to Linux hosts. + LinuxPodSandboxConfig linux = 8; +} + +message RunPodSandboxRequest { + // Configuration for creating a PodSandbox. + PodSandboxConfig config = 1; +} + +message RunPodSandboxResponse { + // ID of the PodSandbox to run. + string pod_sandbox_id = 1; +} + +message StopPodSandboxRequest { + // ID of the PodSandbox to stop. + string pod_sandbox_id = 1; +} + +message StopPodSandboxResponse {} + +message RemovePodSandboxRequest { + // ID of the PodSandbox to remove. + string pod_sandbox_id = 1; +} + +message RemovePodSandboxResponse {} + +message PodSandboxStatusRequest { + // ID of the PodSandbox for which to retrieve status. + string pod_sandbox_id = 1; + // Verbose indicates whether to return extra information about the pod sandbox. + bool verbose = 2; +} + +// PodSandboxNetworkStatus is the status of the network for a PodSandbox. +message PodSandboxNetworkStatus { + // IP address of the PodSandbox. + string ip = 1; + // Name of the interface inside the pod + string name = 2; + // Name of the attached network + string network = 3; +} + +// Namespace contains paths to the namespaces. +message Namespace { + // Namespace options for Linux namespaces. + NamespaceOption options = 2; +} + +// LinuxSandboxStatus contains status specific to Linux sandboxes. +message LinuxPodSandboxStatus { + // Paths to the sandbox's namespaces. + Namespace namespaces = 1; +} + +enum PodSandboxState { + SANDBOX_READY = 0; + SANDBOX_NOTREADY = 1; +} + +// PodSandboxStatus contains the status of the PodSandbox. +message PodSandboxStatus { + // ID of the sandbox. + string id = 1; + // Metadata of the sandbox. + PodSandboxMetadata metadata = 2; + // State of the sandbox. + PodSandboxState state = 3; + // Creation timestamp of the sandbox in nanoseconds. Must be > 0. + int64 created_at = 4; + // Networks contains all plane network status if network is handled by the runtime. + repeated PodSandboxNetworkStatus networks = 5; + // Linux-specific status to a pod sandbox. + LinuxPodSandboxStatus linux = 6; + // Labels are key-value pairs that may be used to scope and select individual resources. + map labels = 7; + // Unstructured key-value map holding arbitrary metadata. + // Annotations MUST NOT be altered by the runtime; the value of this field + // MUST be identical to that of the corresponding PodSandboxConfig used to + // instantiate the pod sandbox this status represents. + map annotations = 8; +} + +message PodSandboxStatusResponse { + // Status of the PodSandbox. + PodSandboxStatus status = 1; + // Info is extra information of the PodSandbox. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. network namespace for linux container based container runtime. + // It should only be returned non-empty when Verbose is true. + map info = 2; +} + +// PodSandboxStateValue is the wrapper of PodSandboxState. +message PodSandboxStateValue { + // State of the sandbox. + PodSandboxState state = 1; +} + +// PodSandboxFilter is used to filter a list of PodSandboxes. +// All those fields are combined with 'AND' +message PodSandboxFilter { + // ID of the sandbox. + string id = 1; + // State of the sandbox. + PodSandboxStateValue state = 2; + // LabelSelector to select matches. + // Only api.MatchLabels is supported for now and the requirements + // are ANDed. MatchExpressions is not supported yet. + map label_selector = 3; +} + +message ListPodSandboxRequest { + // PodSandboxFilter to filter a list of PodSandboxes. + PodSandboxFilter filter = 1; +} + + +// PodSandbox contains minimal information about a sandbox. +message PodSandbox { + // ID of the PodSandbox. + string id = 1; + // Metadata of the PodSandbox. + PodSandboxMetadata metadata = 2; + // State of the PodSandbox. + PodSandboxState state = 3; + // Creation timestamps of the PodSandbox in nanoseconds. Must be > 0. + int64 created_at = 4; + // Labels of the PodSandbox. + map labels = 5; + // Unstructured key-value map holding arbitrary metadata. + // Annotations MUST NOT be altered by the runtime; the value of this field + // MUST be identical to that of the corresponding PodSandboxConfig used to + // instantiate this PodSandbox. + map annotations = 6; +} + +message ListPodSandboxResponse { + // List of PodSandboxes. + repeated PodSandbox items = 1; +} + +// ImageSpec is an internal representation of an image. Currently, it wraps the +// value of a Container's Image field (e.g. imageID or imageDigest), but in the +// future it will include more detailed information about the different image types. +message ImageSpec { + string image = 1; +} + +message KeyValue { + string key = 1; + string value = 2; +} + +// LinuxContainerResources specifies Linux specific configuration for +// resources. +// Note: Consider using Resources from opencontainers/runtime-spec/specs-go +// directly. +message LinuxContainerResources { + // CPU CFS (Completely Fair Scheduler) period. Default: 0 (not specified). + int64 cpu_period = 1; + // CPU CFS (Completely Fair Scheduler) quota. Default: 0 (not specified). + int64 cpu_quota = 2; + // CPU shares (relative weight vs. other containers). Default: 0 (not specified). + int64 cpu_shares = 3; + // Memory limit in bytes. Default: 0 (not specified). + int64 memory_limit_in_bytes = 4; + // OOMScoreAdj adjusts the oom-killer score. Default: 0 (not specified). + int64 oom_score_adj = 5; + // CpusetCpus constrains the allowed set of logical CPUs. Default: "" (not specified). + string cpuset_cpus = 6; + // CpusetMems constrains the allowed set of memory nodes. Default: "" (not specified). + string cpuset_mems = 7; +} + +// SELinuxOption are the labels to be applied to the container. +message SELinuxOption { + string user = 1; + string role = 2; + string type = 3; + string level = 4; +} + +// Capability contains the container capabilities to add or drop +message Capability { + // List of capabilities to add. + repeated string add_capabilities = 1; + // List of capabilities to drop. + repeated string drop_capabilities = 2; +} + +// LinuxContainerSecurityContext holds linux security configuration that will be applied to a container. +message LinuxContainerSecurityContext { + // Capabilities to add or drop. + Capability capabilities = 1; + // If set, run container in privileged mode. + // Privileged mode is incompatible with the following options. If + // privileged is set, the following features MAY have no effect: + // 1. capabilities + // 2. selinux_options + // 4. seccomp + // 5. apparmor + // + // Privileged mode implies the following specific options are applied: + // 1. All capabilities are added. + // 2. Sensitive paths, such as kernel module paths within sysfs, are not masked. + // 3. Any sysfs and procfs mounts are mounted RW. + // 4. Apparmor confinement is not applied. + // 5. Seccomp restrictions are not applied. + // 6. The device cgroup does not restrict access to any devices. + // 7. All devices from the host's /dev are available within the container. + // 8. SELinux restrictions are not applied (e.g. label=disabled). + bool privileged = 2; + // Configurations for the container's namespaces. + // Only used if the container uses namespace for isolation. + NamespaceOption namespace_options = 3; + // SELinux context to be optionally applied. + SELinuxOption selinux_options = 4; + // UID to run the container process as. Only one of run_as_user and + // run_as_username can be specified at a time. + Int64Value run_as_user = 5; + // User name to run the container process as. If specified, the user MUST + // exist in the container image (i.e. in the /etc/passwd inside the image), + // and be resolved there by the runtime; otherwise, the runtime MUST error. + string run_as_username = 6; + // If set, the root filesystem of the container is read-only. + bool readonly_rootfs = 7; + // List of groups applied to the first process run in the container, in + // addition to the container's primary GID. + repeated int64 supplemental_groups = 8; + // AppArmor profile for the container, candidate values are: + // * runtime/default: equivalent to not specifying a profile. + // * unconfined: no profiles are loaded + // * localhost/: profile loaded on the node + // (localhost) by name. The possible profile names are detailed at + // http://wiki.apparmor.net/index.php/AppArmor_Core_Policy_Reference + string apparmor_profile = 9; + // Seccomp profile for the container, candidate values are: + // * docker/default: the default profile for the docker container runtime + // * unconfined: unconfined profile, ie, no seccomp sandboxing + // * localhost/: the profile installed on the node. + // is the full path of the profile. + // Default: "", which is identical with unconfined. + string seccomp_profile_path = 10; + // no_new_privs defines if the flag for no_new_privs should be set on the + // container. + bool no_new_privs = 11; +} + +// LinuxContainerConfig contains platform-specific configuration for +// Linux-based containers. +message LinuxContainerConfig { + // Resources specification for the container. + LinuxContainerResources resources = 1; + // LinuxContainerSecurityContext configuration for the container. + LinuxContainerSecurityContext security_context = 2; +} + +// ContainerMetadata holds all necessary information for building the container +// name. The container runtime is encouraged to expose the metadata in its user +// interface for better user experience. E.g., runtime can construct a unique +// container name based on the metadata. Note that (name, attempt) is unique +// within a sandbox for the entire lifetime of the sandbox. +message ContainerMetadata { + // Name of the container. Same as the container name in the PodSpec. + string name = 1; + // Attempt number of creating the container. Default: 0. + uint32 attempt = 2; +} + +// Device specifies a host device to mount into a container. +message Device { + // Path of the device within the container. + string container_path = 1; + // Path of the device on the host. + string host_path = 2; + // Cgroups permissions of the device, candidates are one or more of + // * r - allows container to read from the specified device. + // * w - allows container to write to the specified device. + // * m - allows container to create device files that do not yet exist. + string permissions = 3; +} + +// ContainerConfig holds all the required and optional fields for creating a +// container. +message ContainerConfig { + // Metadata of the container. This information will uniquely identify the + // container, and the runtime should leverage this to ensure correct + // operation. The runtime may also use this information to improve UX, such + // as by constructing a readable name. + ContainerMetadata metadata = 1 ; + // Image to use. + ImageSpec image = 2; + // Command to execute (i.e., entrypoint for docker) + repeated string command = 3; + // Args for the Command (i.e., command for docker) + repeated string args = 4; + // Current working directory of the command. + string working_dir = 5; + // List of environment variable to set in the container. + repeated KeyValue envs = 6; + // Mounts for the container. + repeated Mount mounts = 7; + // Devices for the container. + repeated Device devices = 8; + // Key-value pairs that may be used to scope and select individual resources. + // Label keys are of the form: + // label-key ::= prefixed-name | name + // prefixed-name ::= prefix '/' name + // prefix ::= DNS_SUBDOMAIN + // name ::= DNS_LABEL + map labels = 9; + // Unstructured key-value map that may be used by the kubelet to store and + // retrieve arbitrary metadata. + // + // Annotations MUST NOT be altered by the runtime; the annotations stored + // here MUST be returned in the ContainerStatus associated with the container + // this ContainerConfig creates. + // + // In general, in order to preserve a well-defined interface between the + // kubelet and the container runtime, annotations SHOULD NOT influence + // runtime behaviour. + map annotations = 10; + // Path relative to PodSandboxConfig.LogDirectory for container to store + // the log (STDOUT and STDERR) on the host. + // E.g., + // PodSandboxConfig.LogDirectory = `/var/log/pods//` + // ContainerConfig.LogPath = `containerName_Instance#.log` + // + // WARNING: Log management and how kubelet should interface with the + // container logs are under active discussion in + // https://issues.k8s.io/24677. There *may* be future change of direction + // for logging as the discussion carries on. + string log_path = 11; + + // Variables for interactive containers, these have very specialized + // use-cases (e.g. debugging). + // Note: Determine if we need to continue supporting these fields that are + // part of Kubernetes's Container Spec. + bool stdin = 12; + bool stdin_once = 13; + bool tty = 14; + + // Configuration specific to Linux containers. + LinuxContainerConfig linux = 15; +} + +message CreateContainerRequest { + // ID of the PodSandbox in which the container should be created. + string pod_sandbox_id = 1; + // Config of the container. + ContainerConfig config = 2; + // Config of the PodSandbox. This is the same config that was passed + // to RunPodSandboxRequest to create the PodSandbox. It is passed again + // here just for easy reference. The PodSandboxConfig is immutable and + // remains the same throughout the lifetime of the pod. + PodSandboxConfig sandbox_config = 3; +} + +message CreateContainerResponse { + // ID of the created container. + string container_id = 1; +} + +message StartContainerRequest { + // ID of the container to start. + string container_id = 1; +} + +message StartContainerResponse {} + +message StopContainerRequest { + // ID of the container to stop. + string container_id = 1; + // Timeout in seconds to wait for the container to stop before forcibly + // terminating it. Default: 0 (forcibly terminate the container immediately) + int64 timeout = 2; +} + +message StopContainerResponse {} + +message RemoveContainerRequest { + // ID of the container to remove. + string container_id = 1; +} + +message RemoveContainerResponse {} + +enum ContainerState { + CONTAINER_CREATED = 0; + CONTAINER_RUNNING = 1; + CONTAINER_EXITED = 2; + CONTAINER_UNKNOWN = 3; +} + +// ContainerStateValue is the wrapper of ContainerState. +message ContainerStateValue { + // State of the container. + ContainerState state = 1; +} + +// ContainerFilter is used to filter containers. +// All those fields are combined with 'AND' +message ContainerFilter { + // ID of the container. + string id = 1; + // State of the container. + ContainerStateValue state = 2; + // ID of the PodSandbox. + string pod_sandbox_id = 3; + // LabelSelector to select matches. + // Only api.MatchLabels is supported for now and the requirements + // are ANDed. MatchExpressions is not supported yet. + map label_selector = 4; +} + +message ListContainersRequest { + ContainerFilter filter = 1; +} + +// Container provides the runtime information for a container, such as ID, hash, +// state of the container. +message Container { + // ID of the container, used by the container runtime to identify + // a container. + string id = 1; + // ID of the sandbox to which this container belongs. + string pod_sandbox_id = 2; + // Metadata of the container. + ContainerMetadata metadata = 3; + // Spec of the image. + ImageSpec image = 4; + // Reference to the image in use. For most runtimes, this should be an + // image ID. + string image_ref = 5; + // State of the container. + ContainerState state = 6; + // Creation time of the container in nanoseconds. + int64 created_at = 7; + // Key-value pairs that may be used to scope and select individual resources. + map labels = 8; + // Unstructured key-value map holding arbitrary metadata. + // Annotations MUST NOT be altered by the runtime; the value of this field + // MUST be identical to that of the corresponding ContainerConfig used to + // instantiate this Container. + map annotations = 9; +} + +message ListContainersResponse { + // List of containers. + repeated Container containers = 1; +} + +message ContainerStatusRequest { + // ID of the container for which to retrieve status. + string container_id = 1; + // Verbose indicates whether to return extra information about the container. + bool verbose = 2; +} + +// ContainerStatus represents the status of a container. +message ContainerStatus { + // ID of the container. + string id = 1; + // Metadata of the container. + ContainerMetadata metadata = 2; + // Status of the container. + ContainerState state = 3; + // Creation time of the container in nanoseconds. + int64 created_at = 4; + // Start time of the container in nanoseconds. Default: 0 (not specified). + int64 started_at = 5; + // Finish time of the container in nanoseconds. Default: 0 (not specified). + int64 finished_at = 6; + // Exit code of the container. Only required when finished_at != 0. Default: 0. + int32 exit_code = 7; + // Spec of the image. + ImageSpec image = 8; + // Reference to the image in use. For most runtimes, this should be an + // image ID + string image_ref = 9; + // Brief CamelCase string explaining why container is in its current state. + string reason = 10; + // Human-readable message indicating details about why container is in its + // current state. + string message = 11; + // Key-value pairs that may be used to scope and select individual resources. + map labels = 12; + // Unstructured key-value map holding arbitrary metadata. + // Annotations MUST NOT be altered by the runtime; the value of this field + // MUST be identical to that of the corresponding ContainerConfig used to + // instantiate the Container this status represents. + map annotations = 13; + // Mounts for the container. + repeated Mount mounts = 14; + // Log path of container. + string log_path = 15; +} + +message ContainerStatusResponse { + // Status of the container. + ContainerStatus status = 1; + // Info is extra information of the Container. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. pid for linux container based container runtime. + // It should only be returned non-empty when Verbose is true. + map info = 2; +} + +message UpdateContainerResourcesRequest { + // ID of the container to update. + string container_id = 1; + // Resource configuration specific to Linux containers. + LinuxContainerResources linux = 2; +} + +message UpdateContainerResourcesResponse {} + +message ExecSyncRequest { + // ID of the container. + string container_id = 1; + // Command to execute. + repeated string cmd = 2; + // Timeout in seconds to stop the command. Default: 0 (run forever). + int64 timeout = 3; +} + +message ExecSyncResponse { + // Captured command stdout output. + bytes stdout = 1; + // Captured command stderr output. + bytes stderr = 2; + // Exit code the command finished with. Default: 0 (success). + int32 exit_code = 3; +} + +message ExecRequest { + // ID of the container in which to execute the command. + string container_id = 1; + // Command to execute. + repeated string cmd = 2; + // Whether to exec the command in a TTY. + bool tty = 3; + // Whether to stream stdin. + // One of `stdin`, `stdout`, and `stderr` MUST be true. + bool stdin = 4; + // Whether to stream stdout. + // One of `stdin`, `stdout`, and `stderr` MUST be true. + bool stdout = 5; + // Whether to stream stderr. + // One of `stdin`, `stdout`, and `stderr` MUST be true. + // If `tty` is true, `stderr` MUST be false. Multiplexing is not supported + // in this case. The output of stdout and stderr will be combined to a + // single stream. + bool stderr = 6; +} + +message ExecResponse { + // Fully qualified URL of the exec streaming server. + string url = 1; +} + +message AttachRequest { + // ID of the container to which to attach. + string container_id = 1; + // Whether to stream stdin. + // One of `stdin`, `stdout`, and `stderr` MUST be true. + bool stdin = 2; + // Whether the process being attached is running in a TTY. + // This must match the TTY setting in the ContainerConfig. + bool tty = 3; + // Whether to stream stdout. + // One of `stdin`, `stdout`, and `stderr` MUST be true. + bool stdout = 4; + // Whether to stream stderr. + // One of `stdin`, `stdout`, and `stderr` MUST be true. + // If `tty` is true, `stderr` MUST be false. Multiplexing is not supported + // in this case. The output of stdout and stderr will be combined to a + // single stream. + bool stderr = 5; +} + +message AttachResponse { + // Fully qualified URL of the attach streaming server. + string url = 1; +} + +message PortForwardRequest { + // ID of the container to which to forward the port. + string pod_sandbox_id = 1; + // Port to forward. + repeated int32 port = 2; +} + +message PortForwardResponse { + // Fully qualified URL of the port-forward streaming server. + string url = 1; +} + +message ImageFilter { + // Spec of the image. + ImageSpec image = 1; +} + +message ListImagesRequest { + // Filter to list images. + ImageFilter filter = 1; +} + +// Basic information about a container image. +message Image { + // ID of the image. + string id = 1; + // Other names by which this image is known. + repeated string repo_tags = 2; + // Digests by which this image is known. + repeated string repo_digests = 3; + // Size of the image in bytes. Must be > 0. + uint64 size = 4; + // UID that will run the command(s). This is used as a default if no user is + // specified when creating the container. UID and the following user name + // are mutually exclusive. + Int64Value uid = 5; + // User name that will run the command(s). This is used if UID is not set + // and no user is specified when creating container. + string username = 6; +} + +message ListImagesResponse { + // List of images. + repeated Image images = 1; +} + +message ImageStatusRequest { + // Spec of the image. + ImageSpec image = 1; + // Verbose indicates whether to return extra information about the image. + bool verbose = 2; +} + +message ImageStatusResponse { + // Status of the image. + Image image = 1; + // Info is extra information of the Image. The key could be abitrary string, and + // value should be in json format. The information could include anything useful + // for debug, e.g. image config for oci image based container runtime. + // It should only be returned non-empty when Verbose is true. + map info = 2; +} + +// AuthConfig contains authorization information for connecting to a registry. +message AuthConfig { + string username = 1; + string password = 2; + string auth = 3; + string server_address = 4; + // IdentityToken is used to authenticate the user and get + // an access token for the registry. + string identity_token = 5; + // RegistryToken is a bearer token to be sent to a registry + string registry_token = 6; +} + +message PullImageRequest { + // Spec of the image. + ImageSpec image = 1; + // Authentication configuration for pulling the image. + AuthConfig auth = 2; + // Config of the PodSandbox, which is used to pull image in PodSandbox context. + PodSandboxConfig sandbox_config = 3; +} + +message PullImageResponse { + // Reference to the image in use. For most runtimes, this should be an + // image ID or digest. + string image_ref = 1; +} + +message RemoveImageRequest { + // Spec of the image to remove. + ImageSpec image = 1; +} + +message RemoveImageResponse {} + +message NetworkConfig { + // CIDR to use for pod IP addresses. + string pod_cidr = 1; +} + +message RuntimeConfig { + NetworkConfig network_config = 1; +} + +message UpdateRuntimeConfigRequest { + RuntimeConfig runtime_config = 1; +} + +message UpdateRuntimeConfigResponse {} + +// RuntimeCondition contains condition information for the runtime. +// There are 2 kinds of runtime conditions: +// 1. Required conditions: Conditions are required for kubelet to work +// properly. If any required condition is unmet, the node will be not ready. +// The required conditions include: +// * RuntimeReady: RuntimeReady means the runtime is up and ready to accept +// basic containers e.g. container only needs host network. +// * NetworkReady: NetworkReady means the runtime network is up and ready to +// accept containers which require container network. +// 2. Optional conditions: Conditions are informative to the user, but kubelet +// will not rely on. Since condition type is an arbitrary string, all conditions +// not required are optional. These conditions will be exposed to users to help +// them understand the status of the system. +message RuntimeCondition { + // Type of runtime condition. + string type = 1; + // Status of the condition, one of true/false. Default: false. + bool status = 2; + // Brief CamelCase string containing reason for the condition's last transition. + string reason = 3; + // Human-readable message indicating details about last transition. + string message = 4; +} + +// RuntimeStatus is information about the current status of the runtime. +message RuntimeStatus { + // List of current observed runtime conditions. + repeated RuntimeCondition conditions = 1; +} + +message StatusRequest { + // Verbose indicates whether to return extra information about the runtime. + bool verbose = 1; +} + +message StatusResponse { + // Status of the Runtime. + RuntimeStatus status = 1; + // Info is extra information of the Runtime. The key could be abitrary string, and + // value should be in json format. The information could include anything useful for + // debug, e.g. plugins used by the container runtime. + // It should only be returned non-empty when Verbose is true. + map info = 2; +} + +message ImageFsInfoRequest {} + +// UInt64Value is the wrapper of uint64. +message UInt64Value { + // The value. + uint64 value = 1; +} + +// StorageIdentifier uniquely identify the storage.. +message StorageIdentifier{ + // UUID of the device. + string uuid = 1; +} + +// FilesystemUsage provides the filesystem usage information. +message FilesystemUsage { + // Timestamp in nanoseconds at which the information were collected. Must be > 0. + int64 timestamp = 1; + // The underlying storage of the filesystem. + StorageIdentifier storage_id = 2; + // UsedBytes represents the bytes used for images on the filesystem. + // This may differ from the total bytes used on the filesystem and may not + // equal CapacityBytes - AvailableBytes. + UInt64Value used_bytes = 3; + // InodesUsed represents the inodes used by the images. + // This may not equal InodesCapacity - InodesAvailable because the underlying + // filesystem may also be used for purposes other than storing images. + UInt64Value inodes_used = 4; +} + +message ImageFsInfoResponse { + // Information of image filesystem(s). + repeated FilesystemUsage image_filesystems = 1; +} + +message ContainerStatsRequest{ + // ID of the container for which to retrieve stats. + string container_id = 1; +} + +message ContainerStatsResponse { + // Stats of the container. + ContainerStats stats = 1; +} + +message ListContainerStatsRequest{ + // Filter for the list request. + ContainerStatsFilter filter = 1; +} + +// ContainerStatsFilter is used to filter containers. +// All those fields are combined with 'AND' +message ContainerStatsFilter { + // ID of the container. + string id = 1; + // ID of the PodSandbox. + string pod_sandbox_id = 2; + // LabelSelector to select matches. + // Only api.MatchLabels is supported for now and the requirements + // are ANDed. MatchExpressions is not supported yet. + map label_selector = 3; +} + +message ListContainerStatsResponse { + // Stats of the container. + repeated ContainerStats stats = 1; +} + +// ContainerAttributes provides basic information of the container. +message ContainerAttributes { + // ID of the container. + string id = 1; + // Metadata of the container. + ContainerMetadata metadata = 2; + // Key-value pairs that may be used to scope and select individual resources. + map labels = 3; + // Unstructured key-value map holding arbitrary metadata. + // Annotations MUST NOT be altered by the runtime; the value of this field + // MUST be identical to that of the corresponding ContainerConfig used to + // instantiate the Container this status represents. + map annotations = 4; +} + +// ContainerStats provides the resource usage statistics for a container. +message ContainerStats { + // Information of the container. + ContainerAttributes attributes = 1; + // CPU usage gathered from the container. + CpuUsage cpu = 2; + // Memory usage gathered from the container. + MemoryUsage memory = 3; + // Usage of the writeable layer. + FilesystemUsage writable_layer = 4; +} + +// CpuUsage provides the CPU usage information. +message CpuUsage { + // Timestamp in nanoseconds at which the information were collected. Must be > 0. + int64 timestamp = 1; + // Cumulative CPU usage (sum across all cores) since object creation. + UInt64Value usage_core_nano_seconds = 2; +} + +// MemoryUsage provides the memory usage information. +message MemoryUsage { + // Timestamp in nanoseconds at which the information were collected. Must be > 0. + int64 timestamp = 1; + // The amount of working set memory in bytes. + UInt64Value working_set_bytes = 2; +} diff --git a/src/api/services/health/health.proto b/src/api/services/health/health.proto new file mode 100644 index 0000000..7a1de11 --- /dev/null +++ b/src/api/services/health/health.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +option optimize_for = CODE_SIZE; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + } + ServingStatus status = 1; +} + +service HealthService{ + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/src/api/services/images/images.proto b/src/api/services/images/images.proto new file mode 100644 index 0000000..b9db443 --- /dev/null +++ b/src/api/services/images/images.proto @@ -0,0 +1,137 @@ +syntax = "proto3"; +option optimize_for = CODE_SIZE; + +import "google/protobuf/timestamp.proto"; +import "descriptor.proto"; + +package images; + +// Images is a service that allows one to register images with containerd. +// +// In containerd, an image is merely the mapping of a name to a content root, +// described by a descriptor. The behavior and state of image is purely +// dictated by the type of the descriptor. +// +// From the perspective of this service, these references are mostly shallow, +// in that the existence of the required content won't be validated until +// required by consuming services. +// +// As such, this can really be considered a "metadata service". +service ImagesService { + // List returns a list of all images known to containerd. + rpc List(ListImagesRequest) returns (ListImagesResponse); + + // Delete deletes the image by name. + rpc Delete(DeleteImageRequest) returns (DeleteImageResponse); + + // load image from archive. + rpc Load(LoadImageRequest) returns (LoadImageResponse); + + //inspect image + rpc Inspect(InspectImageRequest) returns (InspectImageResponse); + + // Login to a Docker registry + rpc Login(LoginRequest) returns (LoginResponse); + + // Logout from a Docker registry + rpc Logout(LogoutRequest) returns (LogoutResponse); +} + +message Image { + // Name provides a unique name for the image. + // + // Containerd treats this as the primary identifier. + string name = 1; + + // Labels provides free form labels for the image. These are runtime only + // and do not get inherited into the package image in any way. + // + // Labels may be updated using the field mask. + // The combined size of a key/value pair cannot exceed 4096 bytes. + map labels = 2; + + // Target describes the content entry point of the image. + containerd.types.Descriptor target = 3; + + // CreatedAt is the time the image was first created. + google.protobuf.Timestamp created_at = 7; + + // UpdatedAt is the last time the image was mutated. + google.protobuf.Timestamp updated_at = 8; +} + +message ListImagesRequest { + // Filters contains one or more filters using the syntax defined in the + // containerd filter package. + // + // The returned result will be those that match any of the provided + // filters. Expanded, images that match the following will be + // returned: + // + // filters[0] or filters[1] or ... or filters[n-1] or filters[n] + // + // If filters is zero-length or nil, all items will be returned. + repeated string filters = 1; +} + +message ListImagesResponse { + repeated Image images = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message DeleteImageRequest { + string name = 1; + bool force = 2; +} + +message DeleteImageResponse { + string name = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message LoadImageRequest { + string file = 1; + string type = 2; + string tag = 3; +} + +message LoadImageResponse { + uint32 cc = 1; + string errmsg = 2; +} + +message InspectImageRequest { + string id = 1; + bool bformat = 2; + int32 timeout = 3; +} + +message InspectImageResponse { + string ImageJSON = 1; + uint32 cc = 2; + string errmsg = 3; +} + +message LoginRequest { + string username = 1; + string password = 2; + string server = 3; + string type = 4; +} + +message LoginResponse { + uint32 cc = 1; + string errmsg = 2; +} + +message LogoutRequest { + string server = 1; + string type = 2; +} + +message LogoutResponse { + uint32 cc = 1; + string errmsg = 2; +} diff --git a/src/api/services/images/rest/image.rest.h b/src/api/services/images/rest/image.rest.h new file mode 100644 index 0000000..d1598aa --- /dev/null +++ b/src/api/services/images/rest/image.rest.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide container image rest definition + ********************************************************************************/ +#ifndef IMAGE_REST_H_ +#define IMAGE_REST_H_ + +#include "image_load_image_request.h" +#include "image_load_image_response.h" +#include "image_list_images_request.h" +#include "image_list_images_response.h" +#include "image_delete_image_request.h" +#include "image_delete_image_response.h" +#include "image_inspect_request.h" +#include "image_inspect_response.h" + +#ifndef RestHttpHead +#define RestHttpHead "http://localhost" +#endif + +#define ImagesServiceLoad "/ImagesService/Load" +#define ImagesServiceList "/ImagesService/List" +#define ImagesServiceDelete "/ImagesService/Delete" +#define ImagesServiceInspect "/ImagesService/Inspect" + +#endif diff --git a/src/api/types/descriptor.proto b/src/api/types/descriptor.proto new file mode 100644 index 0000000..198473e --- /dev/null +++ b/src/api/types/descriptor.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +option optimize_for = CODE_SIZE; + +package containerd.types; + +// Descriptor describes a blob in a content store. +// +// This descriptor can be used to reference content from an +// oci descriptor found in a manifest. +// See https://godoc.org/github.com/opencontainers/image-spec/specs-go/v1#Descriptor +message Descriptor { + string media_type = 1; + string digest = 2; + int64 size = 3; +} diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt new file mode 100644 index 0000000..1c8438a --- /dev/null +++ b/src/cmd/CMakeLists.txt @@ -0,0 +1,10 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} comm_srcs) + +add_subdirectory(lcrc) +set(LCRC_SRCS ${comm_srcs} ${CMD_LCRC_SRCS} PARENT_SCOPE) +set(LCRC_INCS ${CMAKE_CURRENT_SOURCE_DIR} ${CMD_LCRC_INCS} PARENT_SCOPE) + +add_subdirectory(lcrd) +set(LCRD_SRCS ${comm_srcs} ${CMD_LCRD_SRCS} PARENT_SCOPE) +set(LCRD_INCS ${CMAKE_CURRENT_SOURCE_DIR} ${CMD_LCRD_INCS} PARENT_SCOPE) diff --git a/src/cmd/commander.c b/src/cmd/commander.c new file mode 100644 index 0000000..059ffcb --- /dev/null +++ b/src/cmd/commander.c @@ -0,0 +1,816 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide command functions + ******************************************************************************/ + +#define _GNU_SOURCE +#include "commander.h" + +#include +#include +#include +#include +#include +#include +#include "securec.h" +#include "liblcrd.h" + +#include "utils.h" +#include "log.h" + +void command_help_lcrd_head() +{ + fprintf(stdout, "lcrd\n\nlightweight container runtime daemon\n"); +} + +int compare_options(const void *s1, const void *s2) +{ + return strcmp((*(const command_option_t *)s1).large, (*(const command_option_t *)s2).large); +} +void print_options(int options_len, const command_option_t *options) +{ + int i = 0; + int max_opt_len = 0; + + for (i = 0; i < options_len; i++) { + command_option_t option = options[i]; + // -s + int len = 2; + // -s, --large, 6 is the length of "-s, --". + if (option.large != NULL) { + len = (int)(strlen(option.large) + 6); + } + if (len > max_opt_len) { + max_opt_len = len; + } + } + + // format: " -s, --large description" + // 6 is the total length of black before "-s" and "description" + max_opt_len += 6; + + for (i = 0; i < options_len; i++) { + command_option_t option = options[i]; + int curindex = 0; + int space_left = 0; + + curindex = fprintf(stdout, " "); + if (option.small) { + curindex += fprintf(stdout, "-%c", (char)option.small); + } + + if (option.large != NULL) { + if (option.small) { + curindex += fprintf(stdout, ", --%s", option.large); + } else { + curindex += fprintf(stdout, " --%s", option.large); + } + } + + if (curindex <= max_opt_len) { + space_left = max_opt_len - curindex; + } + + fprintf(stdout, "%*s%s\n", space_left, "", option.description); + } + fputc('\n', stdout); +} + +void command_help(command_t *self) +{ + const char *progname = strrchr(self->name, '/'); + if (progname == NULL) { + progname = self->name; + } else { + progname++; + } + + if (self->type != NULL && strcmp(self->type, "lcrd") == 0) { + command_help_lcrd_head(); + } + fprintf(stdout, "\nUsage: %s %s\n\n", progname, self->usage); + fprintf(stdout, "%s\n\n", self->description); + qsort(self->options, (size_t)self->option_count, sizeof(self->options[0]), compare_options); + print_options(self->option_count, self->options); +} + +int command_valid_socket(command_option_t *option, const char *arg) +{ + if (!util_validate_socket(arg)) { + COMMAND_ERROR("Invalid socket name : %s", arg); + return -1; + } + return 0; +} + +void command_init(command_t *self, command_option_t *opts, int opts_len, int argc, const char **argv, + const char *description, const char *usage) +{ + if (memset_s(self, sizeof(command_t), 0, sizeof(command_t)) != EOK) { + COMMAND_ERROR("Failed to set memory"); + return; + } + self->name = argv[0]; + self->argc = argc - 2; + self->argv = argv + 2; + self->usage = usage; + self->description = description; + self->options = opts; + self->option_count = opts_len; +} + +void command_option(command_t *self, command_option_type_t type, void *data, int small, const char *large, + const char *desc, command_callback_t cb) +{ + if (self->option_count == COMMANDER_MAX_OPTIONS) { + COMMAND_ERROR("Maximum option definitions exceeded"); + exit(EINVALIDARGS); + } + int n = self->option_count++; + command_option_t *opt = &self->options[n]; + opt->type = type; + opt->data = data; + opt->cb = cb; + opt->small = small; + opt->description = desc; + opt->large = large; +} + +static int read_option_arg(command_t *self, command_option_t *opt, const char **opt_arg, const char **readed) +{ + if (self == NULL || opt == NULL || opt_arg == NULL) { + return -1; + } + if (opt->hasdata) { + *readed = *opt_arg; + *opt_arg = NULL; + } + if (!opt->hasdata && self->argc > 1) { + opt->hasdata = true; + *readed = *++self->argv; + self->argc--; + } + if (!opt->hasdata) { + COMMAND_ERROR("Flag needs an argument: --%s", opt->large); + return -1; + } + return 0; +} + +static int command_get_bool_option_data(command_option_t *option, const char **opt_arg) +{ + bool converted_bool = (option->type == CMD_OPT_TYPE_BOOL) ? true : false; + + if (option->hasdata) { + int ret = util_str_to_bool(*opt_arg, &converted_bool); + if (ret != 0) { + COMMAND_ERROR("Invalid boolean value \"%s\" for flag --%s", *opt_arg, option->large); + return -1; + } + *opt_arg = NULL; + } + + *(bool *)option->data = converted_bool; + + return 0; +} + +static int command_get_string_option_data(command_t *self, command_option_t *option, const char **opt_arg) +{ + if (read_option_arg(self, option, opt_arg, (const char **)option->data)) { + return -1; + } + if (option->cb != NULL) { + return option->cb(option, *(char **)option->data); + } + return 0; +} + +static int command_get_string_dup_option_data(command_t *self, command_option_t *option, const char **opt_arg) +{ + const char *readed_item = NULL; + + if (read_option_arg(self, option, opt_arg, &readed_item) != 0) { + return -1; + } + if (*(char **)option->data != NULL) { + free(*(char **)option->data); + } + *(char **)option->data = util_strdup_s(readed_item); + if (option->cb != NULL) { + return option->cb(option, readed_item); + } + return 0; +} + +static int command_get_callback_option_data(command_t *self, command_option_t *option, const char **opt_arg) +{ + const char *readed_item = NULL; + + if (read_option_arg(self, option, opt_arg, &readed_item)) { + return -1; + } + if (option->cb == NULL) { + COMMAND_ERROR("Must specify callback for type array"); + return -1; + } + return option->cb(option, readed_item); +} + +static int command_get_option_data(command_t *self, command_option_t *option, const char **opt_arg) +{ + if (option == NULL) { + return -1; + } + switch (option->type) { + case CMD_OPT_TYPE_BOOL: + case CMD_OPT_TYPE_BOOL_FALSE: + return command_get_bool_option_data(option, opt_arg); + case CMD_OPT_TYPE_STRING: + return command_get_string_option_data(self, option, opt_arg); + case CMD_OPT_TYPE_STRING_DUP: + return command_get_string_dup_option_data(self, option, opt_arg); + case CMD_OPT_TYPE_CALLBACK: + return command_get_callback_option_data(self, option, opt_arg); + default: + COMMAND_ERROR("Unkown command option type:%d", (int)(option->type)); + return -1; + } +} + +int have_short_options(command_t *self, char arg) +{ + int i; + + for (i = 0; i < self->option_count; i++) { + if (self->options[i].small == arg) { + return 0; + } + } + + return -1; +} + +static int command_parse_options(command_t *self, const char **opt_arg, bool *found) +{ + int j = 0; + + for (j = 0; j < self->option_count; ++j) { + command_option_t *opt = &self->options[j]; + opt->hasdata = false; + if (opt->small != (*opt_arg)[0]) { + continue; + } + // match flag + *found = true; + if ((*opt_arg)[1]) { + if ((*opt_arg)[1] == '=') { + *opt_arg = *opt_arg + 2; + opt->hasdata = true; + } else { + *opt_arg = *opt_arg + 1; + } + } else { + *opt_arg = NULL; + } + if (command_get_option_data(self, opt, opt_arg)) { + return -1; + } + break; + } + + return 0; +} + +static int command_parse_short_arg(command_t *self, const char *arg) +{ + bool found = false; + const char *opt_arg = arg; + + do { + found = false; + if (opt_arg[0] == 'h' && have_short_options(self, 'h') < 0) { + command_help(self); + exit(0); + } + if (command_parse_options(self, &opt_arg, &found)) { + return -1; + } + } while (found && opt_arg != NULL); + + if (opt_arg != NULL) { + COMMAND_ERROR("Unkown flag found:'%c'", opt_arg[0]); + exit(EINVALIDARGS); + } + return 0; +} + +static int command_parse_long_arg(command_t *self, const char *arg) +{ + int j = 0; + + if (strcmp(arg, "help") == 0) { + command_help(self); + exit(0); + } + + for (j = 0; j < self->option_count; ++j) { + command_option_t *opt = &self->options[j]; + const char *opt_arg = NULL; + opt->hasdata = false; + + if (opt->large == NULL) { + continue; + } + + opt_arg = str_skip_str(arg, opt->large); + if (opt_arg == NULL) { + continue; + } + + if (opt_arg[0]) { + if (opt_arg[0] != '=') { + continue; + } + opt_arg = opt_arg + 1; + opt->hasdata = true; + } else { + opt_arg = NULL; + } + if (command_get_option_data(self, opt, &opt_arg)) { + return -1; + } + return 0; + } + COMMAND_ERROR("Unkown flag found:'--%s'\n", arg); + exit(EINVALIDARGS); +} + +int command_parse_args(command_t *self, int *argc, char * const **argv) +{ + int ret = 0; + + for (; self->argc; self->argc--, self->argv++) { + const char *arg_opt = self->argv[0]; + if (arg_opt[0] != '-' || !arg_opt[1]) { + break; + } + + // short option + if (arg_opt[1] != '-') { + arg_opt = arg_opt + 1; + ret = command_parse_short_arg(self, arg_opt); + if (!ret) { + continue; + } + break; + } + + // -- + if (!arg_opt[2]) { + self->argc--; + self->argv++; + break; + } + + // long option + arg_opt = arg_opt + 2; + ret = command_parse_long_arg(self, arg_opt); + if (ret == 0) { + continue; + } + break; + } + if (self->argc > 0) { + *argc = self->argc; + *argv = (char * const *)self->argv; + } + return ret; +} + +int command_valid_socket_append_array(command_option_t *option, const char *arg) +{ + int len; + char **pos = NULL; + + if (option == NULL) { + return -1; + } + if (!util_validate_socket(arg)) { + COMMAND_ERROR("Invalid socket name : %s", arg); + return -1; + } + + for (pos = *(char ***)(option->data), len = 0; pos != NULL && *pos != NULL; pos++, len++) { + if (strcmp(*pos, arg) == 0) { + break; + } + } + if (pos != NULL && *pos != NULL) { + return 0; + } + + if (util_array_append(option->data, arg) != 0) { + ERROR("merge hosts config failed"); + return -1; + } + len++; + if (len > MAX_HOSTS) { + COMMAND_ERROR("Too many hosts, the max number is %d", MAX_HOSTS); + return -1; + } + + return 0; +} + +static int check_default_ulimit_input(const char *val) +{ + int ret = 0; + if (val == NULL || strcmp(val, "") == 0) { + COMMAND_ERROR("ulimit argument can't be empty"); + ret = -1; + goto out; + } + + if (val[0] == '=' || val[strlen(val) - 1] == '=') { + COMMAND_ERROR("Invalid ulimit argument: \"%s\", delimiter '=' can't" + " be the first or the last character", val); + ret = -1; + } + +out: + return ret; +} + +static void get_default_ulimit_split_parts(const char *val, char ***parts, size_t *parts_len, char deli) +{ + *parts = util_string_split_multi(val, deli); + if (*parts == NULL) { + ERROR("Out of memory"); + return; + } + *parts_len = util_array_len(*parts); +} + +static int parse_soft_hard_default_ulimit(const char *val, char **limitvals, size_t limitvals_len, int64_t *soft, + int64_t *hard) +{ + int ret = 0; + // parse soft + ret = util_safe_llong(limitvals[0], (long long *)soft); + if (ret < 0) { + COMMAND_ERROR("Invalid ulimit soft value: \"%s\", parse int64 failed: %s", val, strerror(-ret)); + ret = -1; + goto out; + } + + // parse hard if exists + if (limitvals_len > 1) { + ret = util_safe_llong(limitvals[1], (long long *)hard); + if (ret < 0) { + COMMAND_ERROR("Invalid ulimit hard value: \"%s\", parse int64 failed: %s", val, strerror(-ret)); + ret = -1; + goto out; + } + + if (*soft > *hard) { + COMMAND_ERROR("Ulimit soft limit must be less than or equal to hard limit: %lld > %lld", + (long long int)(*soft), (long long int)(*hard)); + ret = -1; + goto out; + } + } else { + *hard = *soft; // default to soft in case no hard was set + } +out: + return ret; +} + +int check_default_ulimit_type(const char *type) +{ + int ret = 0; + char **tmptype = NULL; + char *ulimit_valid_type[] = { + // "as", // Disabled since this doesn't seem usable with the way Docker inits a container. + "core", "cpu", "data", "fsize", "locks", "memlock", "msgqueue", "nice", + "nofile", "nproc", "rss", "rtprio", "rttime", "sigpending", "stack", NULL + }; + + for (tmptype = ulimit_valid_type; *tmptype != NULL; tmptype++) { + if (strcmp(type, *tmptype) == 0) { + break; + } + } + + if (*tmptype == NULL) { + COMMAND_ERROR("Invalid ulimit type: %s", type); + ret = -1; + } + return ret; +} + +static host_config_ulimits_element *parse_default_ulimit(const char *val) +{ + int ret = 0; + int64_t soft = 0; + int64_t hard = 0; + size_t parts_len = 0; + size_t limitvals_len = 0; + char **parts = NULL; + char **limitvals = NULL; + host_config_ulimits_element *ulimit = NULL; + + ret = check_default_ulimit_input(val); + if (ret != 0) { + return NULL; + } + + get_default_ulimit_split_parts(val, &parts, &parts_len, '='); + if (parts == NULL) { + ERROR("Out of memory"); + return NULL; + } else if (parts_len != 2) { + COMMAND_ERROR("Invalid ulimit argument: %s", val); + ret = -1; + goto out; + } + + ret = check_default_ulimit_type(parts[0]); + if (ret != 0) { + ret = -1; + goto out; + } + + if (parts[1][0] == ':' || parts[1][strlen(parts[1]) - 1] == ':') { + COMMAND_ERROR("Invalid ulimit value: \"%s\", delimiter ':' can't be the first" + " or the last character", val); + ret = -1; + goto out; + } + + // parse value + get_default_ulimit_split_parts(parts[1], &limitvals, &limitvals_len, ':'); + if (limitvals == NULL) { + ret = -1; + goto out; + } + + if (limitvals_len > 2) { + COMMAND_ERROR("Too many limit value arguments - %s, can only have up to two, `soft[:hard]`", + parts[1]); + ret = -1; + goto out; + } + + ret = parse_soft_hard_default_ulimit(val, limitvals, limitvals_len, &soft, &hard); + if (ret < 0) { + goto out; + } + + ulimit = util_common_calloc_s(sizeof(host_config_ulimits_element)); + if (ulimit == NULL) { + ret = -1; + goto out; + } + ulimit->name = util_strdup_s(parts[0]); + ulimit->hard = hard; + ulimit->soft = soft; + +out: + util_free_array(parts); + util_free_array(limitvals); + if (ret != 0) { + free_host_config_ulimits_element(ulimit); + ulimit = NULL; + } + + return ulimit; +} + +int command_default_ulimit_append(command_option_t *option, const char *arg) +{ + int ret = 0; + size_t ulimit_len; + host_config_ulimits_element *tmp = NULL; + host_config_ulimits_element **pos = NULL; + + if (option == NULL) { + ret = -1; + goto out; + } + + tmp = parse_default_ulimit(arg); + if (tmp == NULL) { + ERROR("parse default ulimit from arg failed"); + ret = -1; + goto out; + } + + for (pos = *(host_config_ulimits_element ***)(option->data); pos != NULL && *pos != NULL; pos++) { + if (strcmp((*pos)->name, tmp->name) == 0) { + break; + } + } + if (pos != NULL && *pos != NULL) { + (*pos)->hard = tmp->hard; + (*pos)->soft = tmp->soft; + goto out; + } + + ulimit_len = ulimit_array_len(*(host_config_ulimits_element ***)(option->data)); + if (ulimit_array_append(option->data, tmp, ulimit_len) != 0) { + ERROR("default ulimit append failed"); + ret = -1; + } + +out: + free_host_config_ulimits_element(tmp); + return ret; +} + + +int command_append_array(command_option_t *option, const char *arg) +{ + if (option == NULL) { + return -1; + } + char ***array = option->data; + return util_array_append(array, arg); +} + +int command_convert_u16(command_option_t *option, const char *arg) +{ + int ret = 0; + + if (option == NULL) { + return -1; + } + ret = util_safe_u16(arg, option->data); + if (ret != 0) { + COMMAND_ERROR("Invalid value \"%s\" for flag --%s: %s", arg, option->large, strerror(-ret)); + return EINVALIDARGS; + } + return 0; +} + +int command_convert_llong(command_option_t *opt, const char *arg) +{ + int ret; + + if (opt == NULL) { + return -1; + } + ret = util_safe_llong(arg, opt->data); + if (ret != 0) { + COMMAND_ERROR("Invalid value \"%s\" for flag --%s: %s", arg, opt->large, strerror(-ret)); + return EINVALIDARGS; + } + return 0; +} + +int command_convert_uint(command_option_t *opt, const char *arg) +{ + int ret; + if (opt == NULL) { + return -1; + } + ret = util_safe_uint(arg, opt->data); + if (ret != 0) { + COMMAND_ERROR("Invalid value \"%s\" for flag --%s: %s", arg, opt->large, strerror(-ret)); + return EINVALIDARGS; + } + return 0; +} + +int command_convert_int(command_option_t *option, const char *arg) +{ + int ret = 0; + + if (option == NULL) { + return -1; + } + ret = util_safe_int(arg, option->data); + if (ret != 0) { + COMMAND_ERROR("Invalid value \"%s\" for flag --%s: %s", arg, option->large, strerror(-ret)); + return EINVALIDARGS; + } + return 0; +} + +int command_convert_nanoseconds(command_option_t *option, const char *arg) +{ + if (option == NULL) { + return -1; + } + if (util_parse_time_str_to_nanoseconds(arg, option->data)) { + COMMAND_ERROR("Invalid value \"%s\" for flag --%s", arg, option->large); + return EINVALIDARGS; + } + return 0; +} + +int command_convert_membytes(command_option_t *option, const char *arg) +{ + if (option == NULL) { + return -1; + } + if (util_parse_byte_size_string(arg, option->data) || (*(int64_t *)(option->data)) < 0) { + COMMAND_ERROR("Invalid value \"%s\" for flag --%s", arg, option->large); + return EINVALIDARGS; + } + return 0; +} + +int command_convert_memswapbytes(command_option_t *option, const char *arg) +{ + if (option == NULL) { + return -1; + } + if (strcmp(arg, "-1") == 0) { + *(int64_t *)(option->data) = -1; + return 0; + } + if (command_convert_membytes(option, arg)) { + return EINVALIDARGS; + } + return 0; +} + +size_t ulimit_array_len(host_config_ulimits_element **default_ulimit) +{ + size_t len = 0; + host_config_ulimits_element **pos = NULL; + + for (pos = default_ulimit; pos != NULL && *pos != NULL; pos++) { + len++; + } + + return len; +} + +int ulimit_array_append(host_config_ulimits_element ***ulimit_array, const host_config_ulimits_element *element, + const size_t len) +{ + int ret; + size_t old_size, new_size; + host_config_ulimits_element *new_element = NULL; + host_config_ulimits_element **new_ulimit_array = NULL; + + + if (ulimit_array == NULL || element == NULL) { + return -1; + } + + // let new len to len + 2 for element and null + if (len > SIZE_MAX / sizeof(host_config_ulimits_element *) - 2) { + ERROR("Too many ulimit elements!"); + return -1; + } + + new_size = (len + 2) * sizeof(host_config_ulimits_element *); + old_size = len * sizeof(host_config_ulimits_element *); + + ret = mem_realloc((void **)(&new_ulimit_array), new_size, (void *)*ulimit_array, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for append ulimit"); + return -1; + } + *ulimit_array = new_ulimit_array; + + new_element = util_common_calloc_s(sizeof(host_config_ulimits_element)); + if (new_element == NULL) { + ERROR("Out of memory"); + free_default_ulimit(*ulimit_array); + *ulimit_array = NULL; + return -1; + } + + new_element->name = util_strdup_s(element->name); + new_element->hard = element->hard; + new_element->soft = element->soft; + new_ulimit_array[len] = new_element; + + return 0; +} + +void free_default_ulimit(host_config_ulimits_element **default_ulimit) +{ + host_config_ulimits_element **p = NULL; + + for (p = default_ulimit; p != NULL && *p != NULL; p++) { + free_host_config_ulimits_element(*p); + } + free(default_ulimit); +} + + diff --git a/src/cmd/commander.h b/src/cmd/commander.h new file mode 100644 index 0000000..62e96f0 --- /dev/null +++ b/src/cmd/commander.h @@ -0,0 +1,115 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide command definition + ******************************************************************************/ + +#ifndef __COMMANDER_H_ +#define __COMMANDER_H_ +#include +#include +#include + +#include "host_config.h" + +#ifndef COMMANDER_MAX_OPTIONS +#define COMMANDER_MAX_OPTIONS 64 +#endif + +typedef enum { + /* no arguments */ + CMD_OPT_TYPE_BOOL, + CMD_OPT_TYPE_BOOL_FALSE, + /* required arguments */ + CMD_OPT_TYPE_STRING, + CMD_OPT_TYPE_STRING_DUP, + CMD_OPT_TYPE_CALLBACK +} command_option_type_t; + + +struct _command; + +struct command_option; + +typedef int(*command_callback_t)(struct command_option *options, const char *arg); + +typedef struct command_option { + command_option_type_t type; + bool hasdata; + const char *large; + int small; + void *data; + const char *description; + command_callback_t cb; +} command_option_t; + +typedef struct _command { + const char *type; + const char *usage; + const char *description; + const char *name; + const char *version; + int option_count; + command_option_t *options; + int argc; + const char **argv; +} command_t; + +int command_valid_socket(command_option_t *option, const char *arg); + +void command_init(command_t *self, command_option_t *opts, int opts_len, int argc, const char **argv, + const char *description, const char *usage); + +int compare_options(const void *s1, const void *s2); + +void print_options(int options_len, const command_option_t *options); + +void command_help(command_t *self); + +void command_option(command_t *self, command_option_type_t type, void *data, + int small, const char *large, const char *desc, command_callback_t cb); + +int command_parse_args(command_t *self, int *argc, char * const **argv); + +int have_short_options(command_t *self, char arg); + +int command_valid_socket_append_array(command_option_t *option, const char *arg); + +int command_append_array(command_option_t *option, const char *arg); + +int command_convert_u16(command_option_t *option, const char *arg); + +int command_convert_llong(command_option_t *opt, const char *arg); + +int command_convert_uint(command_option_t *opt, const char *arg); + +int command_convert_int(command_option_t *option, const char *arg); + +int command_convert_nanoseconds(command_option_t *option, const char *arg); + +int command_convert_membytes(command_option_t *option, const char *arg); + +int command_convert_memswapbytes(command_option_t *option, const char *arg); + +int command_default_ulimit_append(command_option_t *option, const char *arg); + +size_t ulimit_array_len(host_config_ulimits_element **default_ulimit); + +int ulimit_array_append(host_config_ulimits_element ***default_ulimit, + const host_config_ulimits_element *element, + const size_t len); + +int check_default_ulimit_type(const char *type); + +void free_default_ulimit(host_config_ulimits_element **default_ulimit); + +#endif /* COMMANDER_H */ diff --git a/src/cmd/lcrc/CMakeLists.txt b/src/cmd/lcrc/CMakeLists.txt new file mode 100644 index 0000000..a422d09 --- /dev/null +++ b/src/cmd/lcrc/CMakeLists.txt @@ -0,0 +1,27 @@ +# get current directory sources files +add_subdirectory(base) +add_subdirectory(information) +add_subdirectory(extend) +add_subdirectory(stream) +add_subdirectory(images) +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} lcrc_srcs) + +set(CMD_LCRC_SRCS + ${lcrc_srcs} + ${LCRC_BASE_SRCS} + ${LCRC_EXTEND_SRCS} + ${LCRC_IMAGES_SRCS} + ${LCRC_INFORMATION_SRCS} + ${LCRC_STREAM_SRCS} + PARENT_SCOPE + ) + +set(CMD_LCRC_INCS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/base + ${CMAKE_CURRENT_SOURCE_DIR}/extend + ${CMAKE_CURRENT_SOURCE_DIR}/images + ${CMAKE_CURRENT_SOURCE_DIR}/information + ${CMAKE_CURRENT_SOURCE_DIR}/stream + PARENT_SCOPE + ) diff --git a/src/cmd/lcrc/arguments.c b/src/cmd/lcrc/arguments.c new file mode 100644 index 0000000..98e08bb --- /dev/null +++ b/src/cmd/lcrc/arguments.c @@ -0,0 +1,272 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide container client arguments functions + ******************************************************************************/ +#include "arguments.h" + +#include +#include +#include +#include +#include + +#include "error.h" +#include "commander.h" +#include "log.h" +#include "securec.h" +#include "utils.h" +#include "constants.h" + +client_connect_config_t get_connect_config(const struct client_arguments *args) +{ + client_connect_config_t config = { 0 }; + + config.socket = args->socket; + // unix socket not support tls + if (strncmp(args->socket, "tcp://", strlen("tcp://"))) { + config.tls_verify = false; + config.tls = false; + config.ca_file = NULL; + config.cert_file = NULL; + config.key_file = NULL; + } else { + config.tls = args->tls; + config.tls_verify = args->tls_verify; + + if (args->tls_verify) { + config.tls = true; + } + config.ca_file = args->ca_file; + config.cert_file = args->cert_file; + config.key_file = args->key_file; + } + return config; +} + +static int set_default_tls_options(struct client_arguments *args) +{ + int ret = -1; + char *tls = NULL; + char *tls_verify = NULL; + char *tmp_path = NULL; + char *cert_path = NULL; + char *ca_file = NULL; + char *cert_file = NULL; + char *key_file = NULL; + + tls = getenv("ISULAD_TLS"); + args->tls = (tls != NULL && strlen(tls) != 0 && strcmp(tls, "0") != 0); + tls = NULL; + + tls_verify = getenv("ISULAD_TLS_VERIFY"); + args->tls_verify = (tls_verify != NULL && strlen(tls_verify) != 0 && strcmp(tls_verify, "0") != 0); + tls_verify = NULL; + + tmp_path = getenv("ISULAD_CERT_PATH"); + if (tmp_path != NULL && strlen(tmp_path) != 0) { + cert_path = util_strdup_s(tmp_path); + ca_file = util_path_join(cert_path, DEFAULT_CA_FILE); + if (ca_file == NULL) { + goto out; + } + free(args->ca_file); + + args->ca_file = ca_file; + key_file = util_path_join(cert_path, DEFAULT_KEY_FILE); + if (key_file == NULL) { + goto out; + } + free(args->key_file); + + args->key_file = key_file; + + cert_file = util_path_join(cert_path, DEFAULT_CERT_FILE); + if (cert_file == NULL) { + goto out; + } + free(args->cert_file); + + args->cert_file = cert_file; + } + + ret = 0; + +out: + free(cert_path); + return ret; +} + +/* client arguments init */ +int client_arguments_init(struct client_arguments *args) +{ + char *host = NULL; + + if (args == NULL) { + return -1; + } + args->name = NULL; + args->create_rootfs = NULL; + args->log_file = "none"; + args->argc = 0; + args->argv = NULL; + host = getenv("ISULAD_HOST"); + if (host != NULL && strlen(host) != 0) { + args->socket = util_strdup_s(host); + } else { + args->socket = util_strdup_s(DEFAULT_UNIX_SOCKET); + } + + if (memset_s(&args->custom_conf, sizeof(args->custom_conf), 0x00, sizeof(struct custom_configs)) != EOK) { + COMMAND_ERROR("Failed to set memory"); + return -1; + } + + if (memset_s(&args->cr, sizeof(args->cr), 0x00, sizeof(struct args_cgroup_resources)) != EOK) { + COMMAND_ERROR("Failed to set memory"); + return -1; + } + if (set_default_tls_options(args) != 0) { + return -1; + } + + return 0; +} + +/* client arguments free */ +void client_arguments_free(struct client_arguments *args) +{ + int i; + struct custom_configs *custom_conf = NULL; + + if (args == NULL) { + return; + } + + free(args->name); + args->name = NULL; + + free(args->socket); + args->socket = NULL; + + util_free_array(args->filters); + args->filters = NULL; + + custom_conf = &(args->custom_conf); + if (custom_conf == NULL) { + return; + } + + util_free_array(custom_conf->env); + custom_conf->env = NULL; + + util_free_array(custom_conf->hugepage_limits); + custom_conf->hugepage_limits = NULL; + + free(custom_conf->hook_spec); + custom_conf->hook_spec = NULL; + + free(custom_conf->env_target_file); + custom_conf->env_target_file = NULL; + + free(custom_conf->cgroup_parent); + custom_conf->cgroup_parent = NULL; + + free(custom_conf->user); + custom_conf->user = NULL; + + free(custom_conf->hostname); + custom_conf->hostname = NULL; + + util_free_array(custom_conf->cap_adds); + custom_conf->cap_adds = NULL; + + util_free_array(custom_conf->cap_drops); + custom_conf->cap_drops = NULL; + + util_free_array(custom_conf->storage_opts); + custom_conf->storage_opts = NULL; + + util_free_array(custom_conf->sysctls); + custom_conf->sysctls = NULL; + + util_free_array(custom_conf->volumes); + custom_conf->volumes = NULL; + + free(custom_conf->entrypoint); + custom_conf->entrypoint = NULL; + + util_free_array(custom_conf->ulimits); + custom_conf->ulimits = NULL; + + util_free_array(custom_conf->devices); + custom_conf->devices = NULL; + + util_free_array(custom_conf->weight_devices); + custom_conf->weight_devices = NULL; + + for (i = 0; i < NAMESPACE_MAX; i++) { + free(custom_conf->share_ns[i]); + custom_conf->share_ns[i] = NULL; + } + + free(args->create_rootfs); + args->create_rootfs = NULL; + + free_json_map_string_string(args->annotations); + args->annotations = NULL; + + free(custom_conf->workdir); + custom_conf->workdir = NULL; + + util_free_array(custom_conf->security); + custom_conf->security = NULL; + + free(args->ca_file); + args->ca_file = NULL; + + free(args->cert_file); + args->cert_file = NULL; + + free(args->key_file); + args->key_file = NULL; +} + +/* print common help */ +void print_common_help() +{ + struct client_arguments cmd_common_args = {}; + struct command_option options[] = { COMMON_OPTIONS(cmd_common_args), VERSION_OPTIONS(cmd_common_args) }; + size_t len = sizeof(options) / sizeof(options[0]); + qsort(options, len, sizeof(options[0]), compare_options); + fprintf(stdout, "COMMON OPTIONS :\n"); + print_options((int)len, options); +} + +/* client print error */ +void client_print_error(uint32_t cc, uint32_t server_errono, const char *errmsg) +{ + switch (server_errono) { + case LCRD_SUCCESS: + if (errmsg != NULL) { + COMMAND_ERROR("%s", errmsg); + } + break; + default: + if (errmsg != NULL) { + COMMAND_ERROR("Error response from daemon: %s", errmsg); + } else { + COMMAND_ERROR("%s", errno_to_error_message(server_errono)); + } + break; + } +} diff --git a/src/cmd/lcrc/arguments.h b/src/cmd/lcrc/arguments.h new file mode 100644 index 0000000..764bd54 --- /dev/null +++ b/src/cmd/lcrc/arguments.h @@ -0,0 +1,346 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container client arguments definition + ******************************************************************************/ + +#ifndef __LCRC_ARGUMENTS_H +#define __LCRC_ARGUMENTS_H + +#include +#include +#include +#include + +#include "commander.h" +#include "container_def.h" +#include "json_common.h" +#include "lcrc_connect.h" + +/* max arguments can be specify in client */ +#define MAX_CLIENT_ARGS 1000 + +struct client_arguments; +struct custom_configs; + +struct custom_configs { + /* environment variables */ + char **env; + + /* hugepage limits */ + char **hugepage_limits; + + /* group add*/ + char **group_add; + + /* hook-spec file */ + char *hook_spec; + + /* volumes to mount */ + char **volumes; + + /* mounts to attach a filesystem */ + char **mounts; + + /* pids limit */ + int64_t pids_limit; + + /* files limit */ + int64_t files_limit; + + /* shm size */ + int64_t shm_size; + + /* user and group */ + char *user; + + /* hostname */ + char *hostname; + + /* privileged */ + bool privileged; + + /* auto remove */ + bool auto_remove; + + /* readonly rootfs */ + bool readonly; + + /* alldevices */ + bool all_devices; + + /* system container */ + bool system_container; + char *ns_change_opt; + + /* user remap */ + char *user_remap; + + /* cap add */ + char **cap_adds; + + /* cap drop */ + char **cap_drops; + + /* storage opts */ + char **storage_opts; + + /* sysctls */ + char **sysctls; + + /* extra hosts */ + char **extra_hosts; + + /* dns */ + char **dns; + + /* dns options */ + char **dns_options; + + /* dns search */ + char **dns_search; + + /* tty */ + bool tty; + + /* open stdin of container */ + bool open_stdin; + + /* attach stdin of container */ + bool attach_stdin; + + /* attach stdout of container */ + bool attach_stdout; + + /* attach stderr of container */ + bool attach_stderr; + + /* entrypoint */ + char *entrypoint; + + /* populate devices */ + char **devices; + + /* ulimit options */ + char **ulimits; + + /* blkio weight devices */ + char **weight_devices; + + /* namespace mode */ + char *share_ns[NAMESPACE_MAX]; + + /* work dir */ + char *workdir; + + /* security opt */ + char **security; + + /* health cmd */ + char *health_cmd; + + /* health interval */ + int64_t health_interval; + + /* health retries */ + int health_retries; + + /* health timeout */ + int64_t health_timeout; + + /* health start period */ + int64_t health_start_period; + + /* no healthcheck */ + bool no_healthcheck; + + /* exit on unhealthy */ + bool exit_on_unhealthy; + + /* oom kill disable */ + bool oom_kill_disable; + + /* create/run accel options */ + char **accel; + + /* env target file */ + char *env_target_file; + + /* cgroup parent */ + char *cgroup_parent; + + /* device read bps */ + char **blkio_throttle_read_bps_device; + + /* device write bps */ + char **blkio_throttle_write_bps_device; +}; + +struct args_cgroup_resources { + uint16_t blkio_weight; + int64_t cpu_shares; + int64_t cpu_period; + int64_t cpu_quota; + int64_t cpu_rt_period; + int64_t cpu_rt_runtime; + int64_t oom_score_adj; + char *cpuset_cpus; + char *cpuset_mems; + int64_t memory_limit; + int64_t memory_swap; + int64_t memory_reservation; + int64_t kernel_memory_limit; +}; + +struct client_arguments { + const char *progname; /* main progname name */ + const char *subcommand; /* sub command name */ + const struct option *options; + + // For common options + char *name; /* container name */ + + char *socket; + + char *runtime; + + char *restart; + + char *host_channel; + + // lcr create + char *external_rootfs; + char *create_rootfs; + char *image_name; + char *log_file; + char *log_file_size; + unsigned int log_file_rotate; + + /* notes: we should free the mem in custom_conf by hand*/ + struct custom_configs custom_conf; + + // lcrc run; + bool detach; + + bool interactive; + // stop/kill/delete + bool force; + int time; + + // delete + bool volume; + + // events + char *since; + char *until; + + // health check + char *service; + + // list + bool dispname; + bool list_all; + char **filters; + + // inspect + char *format; + + // stats + bool nostream; + bool showall; + + // update + struct args_cgroup_resources cr; + + // pull/rmi + char *ref; + bool plain_http; + + // logs + bool follow; + /* + * tail < 0: show all logs + * tail = 0: do not show log + * tail > 0: show number of logs set by tail + * */ + long long tail; + + // kill + char *signal; + + // load + char *file; + char *type; + char *tag; + + // login/logout + char *username; + char *password; + char *server; + bool password_stdin; + + /* extra environment variables used in exec */ + char **extra_env; + + // remaining arguments + char * const *argv; + int argc; + + // top + char *ps_args; + + json_map_string_string *annotations; + + // gRPC tls config + bool tls; + bool tls_verify; + char *ca_file; + char *cert_file; + char *key_file; +}; + +#define LOG_OPTIONS(log) \ + { CMD_OPT_TYPE_BOOL_FALSE, false, "debug", 'D', &(log).quiet, "Enable debug mode", NULL } + +#define COMMON_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING_DUP, false, "host", 'H', &(cmdargs).socket, \ + "Daemon socket(s) to connect to", command_valid_socket }, \ + { CMD_OPT_TYPE_BOOL, false, "tls", 0, &(cmdargs).tls, \ + "Use TLS; implied by --tlsverify", NULL}, \ + { CMD_OPT_TYPE_BOOL, false, "tlsverify", 0, &(cmdargs).tls_verify, \ + "Use TLS and verify the remote", NULL}, \ + { CMD_OPT_TYPE_STRING_DUP, false, "tlscacert", 0, &(cmdargs).ca_file, \ + "Trust certs signed only by this CA (default \"/root/.iSulad/ca.pem\")", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "tlscert", 0, &(cmdargs).cert_file, \ + "Path to TLS certificate file (default \"/root/.iSulad/cert.pem\")", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "tlskey", 0, &(cmdargs).key_file, \ + "Path to TLS key file (default \"/root/.iSulad/key.pem\")", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "help", 0, NULL, "Print usage", NULL } + +#define VERSION_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "version", 0, NULL, "Print version information and quit", NULL } + +extern void print_common_help(); + +extern int client_arguments_init(struct client_arguments *args); + +extern void client_arguments_free(struct client_arguments *args); + +extern void lcrd_screen_print(uint32_t cc, uint32_t server_errono, + struct client_arguments *args); + + +extern void client_print_error(uint32_t cc, uint32_t server_errono, const char *errmsg); + +extern client_connect_config_t get_connect_config(const struct client_arguments *args); + +#endif /*__LCRC_ARGUMENTS_H*/ diff --git a/src/cmd/lcrc/base/CMakeLists.txt b/src/cmd/lcrc/base/CMakeLists.txt new file mode 100644 index 0000000..12ea458 --- /dev/null +++ b/src/cmd/lcrc/base/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} lcrc_base_srcs) + +set(LCRC_BASE_SRCS + ${lcrc_base_srcs} + PARENT_SCOPE + ) diff --git a/src/cmd/lcrc/base/create.c b/src/cmd/lcrc/base/create.c new file mode 100644 index 0000000..2e3657c --- /dev/null +++ b/src/cmd/lcrc/base/create.c @@ -0,0 +1,2023 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container create functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#include "namespace.h" +#include "error.h" +#include "securec.h" +#include "arguments.h" +#include "log.h" +#include "utils.h" +#include "console.h" +#include "create.h" +#include "commands.h" +#include "lcrc_connect.h" +#include "path.h" +#include "pull.h" +#include "liblcrd.h" + +const char g_cmd_create_desc[] = "Create a new container"; +const char g_cmd_create_usage[] = "create [OPTIONS] --external-rootfs=PATH|IMAGE [COMMAND] [ARG...]"; + +struct client_arguments g_cmd_create_args = { + .runtime = "lcr", + .restart = "no", + .log_file_size = "1MB", + .log_file_rotate = 7, + .cr.oom_score_adj = 0, + .custom_conf.health_interval = 0, + .custom_conf.health_timeout = 0, + .custom_conf.health_start_period = 0, + .custom_conf.health_retries = 0, +}; + +static void request_pack_host_config_limit(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* pids limit */ + if (args->custom_conf.pids_limit != 0) { + hostconfig->cr->pids_limit = args->custom_conf.pids_limit; + } + /* files limit */ + if (args->custom_conf.files_limit != 0) { + hostconfig->cr->files_limit = args->custom_conf.files_limit; + } +} + +static int request_pack_host_config_storage_opts(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + size_t i = 0; + size_t len = 0; + + if (args->custom_conf.storage_opts == NULL) { + return 0; + } + + hostconfig->storage_opts = util_common_calloc_s(sizeof(json_map_string_string)); + if (hostconfig->storage_opts == NULL) { + COMMAND_ERROR("Out of memory"); + return -1; + } + + len = util_array_len(args->custom_conf.storage_opts); + for (i = 0; i < len; i++) { + char *p = NULL; + p = strchr(args->custom_conf.storage_opts[i], '='); + if (p != NULL) { + *p = '\0'; + if (append_json_map_string_string(hostconfig->storage_opts, args->custom_conf.storage_opts[i], p + 1)) { + COMMAND_ERROR("Failed to append map"); + *p = '='; + return -1; + } + *p = '='; + } else { + COMMAND_ERROR("Invalid storage option '%s'", args->custom_conf.storage_opts[i]); + return -1; + } + } + return 0; +} + +static int request_pack_host_config_sysctls(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + size_t i = 0; + size_t len = 0; + + if (args->custom_conf.sysctls == NULL) { + return 0; + } + + hostconfig->sysctls = util_common_calloc_s(sizeof(json_map_string_string)); + if (hostconfig->sysctls == NULL) { + COMMAND_ERROR("Out of memory"); + return -1; + } + + len = util_array_len(args->custom_conf.sysctls); + for (i = 0; i < len; i++) { + char *p = NULL; + p = strchr(args->custom_conf.sysctls[i], '='); + if (p != NULL) { + *p = '\0'; + if (append_json_map_string_string(hostconfig->sysctls, args->custom_conf.sysctls[i], p + 1)) { + COMMAND_ERROR("Failed to append map"); + *p = '='; + return -1; + } + *p = '='; + } + } + return 0; +} + +static int request_pack_host_config_cgroup(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + if (args == NULL) { + return -1; + } + + /* blkio weight */ + hostconfig->cr->blkio_weight = args->cr.blkio_weight; + + /* cpu shares */ + hostconfig->cr->cpu_shares = args->cr.cpu_shares; + + /* cpu period */ + hostconfig->cr->cpu_period = args->cr.cpu_period; + + /* cpu quota */ + hostconfig->cr->cpu_quota = args->cr.cpu_quota; + + /* cpu realtime period */ + hostconfig->cr->cpu_realtime_period = args->cr.cpu_rt_period; + + /* cpu realtime runtime */ + hostconfig->cr->cpu_realtime_runtime = args->cr.cpu_rt_runtime; + + /* cpuset-cpus */ + hostconfig->cr->cpuset_cpus = args->cr.cpuset_cpus; + + /* cpuset memory */ + hostconfig->cr->cpuset_mems = args->cr.cpuset_mems; + + /* oom_score_adj */ + hostconfig->cr->oom_score_adj = args->cr.oom_score_adj; + + /* Memory limit */ + hostconfig->cr->memory = args->cr.memory_limit; + + /* memory swap */ + hostconfig->cr->memory_swap = args->cr.memory_swap; + + /* memory reservation */ + hostconfig->cr->memory_reservation = args->cr.memory_reservation; + + /* kernel memory limit */ + hostconfig->cr->kernel_memory = args->cr.kernel_memory_limit; + + request_pack_host_config_limit(args, hostconfig); + + return 0; +} + +static int util_env_set_lcrd_enable_plugins(char ***penv, const size_t *penv_len, const char *names) +{ + size_t env_len; + size_t len = 0; + char *val = NULL; + char *kv = NULL; + char **env = NULL; + const char *arr[10] = { NULL }; + + if (penv == NULL || penv_len == NULL || names == NULL) { + return -1; + } + + env = *penv; + env_len = *penv_len; + + arr[0] = LCRD_ENABLE_PLUGINS; + arr[1] = "="; + arr[2] = names; + len = 3; + + val = util_env_get_val(env, env_len, LCRD_ENABLE_PLUGINS, strlen(LCRD_ENABLE_PLUGINS)); + if (val != NULL && strlen(val) != 0) { + arr[3] = LCRD_ENABLE_PLUGINS_SEPERATOR; + arr[4] = val; + len = 5; + } + + kv = util_string_join("", arr, len); + if (kv == NULL) { + goto failed; + } + + if (util_env_set_val(penv, penv_len, LCRD_ENABLE_PLUGINS, strlen(LCRD_ENABLE_PLUGINS), kv)) { + goto failed; + } + + free(val); + free(kv); + return 0; + +failed: + free(val); + free(kv); + return -1; +} + +static int request_pack_custom_env(struct client_arguments *args, lcrc_container_config_t *conf) +{ + int ret = 0; + char *pe = NULL; + + if (args->custom_conf.env != NULL) { + conf->env = args->custom_conf.env; + conf->env_len = util_array_len(conf->env); + } + + if (args->custom_conf.accel != NULL) { + pe = util_env_get_val(conf->env, conf->env_len, LCRD_ENABLE_PLUGINS, strlen(LCRD_ENABLE_PLUGINS)); + if (pe == NULL) { + if (util_array_append(&conf->env, LCRD_ENABLE_PLUGINS "=")) { + COMMAND_ERROR("init env LCRD_ENABLE_PLUGINS failed"); + ret = -1; + goto out; + } + } + + args->custom_conf.env = conf->env; /* make sure args->custom_conf.env point to valid memory. */ + conf->env_len = util_array_len(conf->env); + + conf->accel = args->custom_conf.accel; + conf->accel_len = util_array_len(args->custom_conf.accel); + if (util_env_set_lcrd_enable_plugins(&conf->env, &conf->env_len, LCRD_ISULA_ADAPTER)) { + COMMAND_ERROR("init accel env failed"); + ret = -1; + goto out; + } + } + +out: + free(pe); + return ret; +} + +static void request_pack_custom_user(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args->custom_conf.user != NULL) { + conf->user = args->custom_conf.user; + } + + return; +} + +static void request_pack_custom_hostname(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args->custom_conf.hostname != NULL) { + conf->hostname = args->custom_conf.hostname; + } + + return; +} + +static void request_pack_custom_all_devices(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + /* alldevices */ + if (args->custom_conf.all_devices) { + conf->all_devices = true; + } + return; +} + +static void request_pack_custom_system_container(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args->custom_conf.system_container) { + conf->system_container = true; + } + + /*ns change opt*/ + if (!args->custom_conf.privileged) { + if (args->custom_conf.ns_change_opt != NULL) { + conf->ns_change_opt = args->custom_conf.ns_change_opt; + } + } + + return; +} + +static void request_pack_custom_mounts(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args->custom_conf.mounts != NULL) { + conf->mounts_len = util_array_len(args->custom_conf.mounts); + conf->mounts = args->custom_conf.mounts; + } + return; +} + +static void request_pack_custom_entrypoint(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args->custom_conf.entrypoint != NULL) { + conf->entrypoint = args->custom_conf.entrypoint; + } + + return; +} + +static void request_pack_custom_args(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args->argc != 0 && args->argv != NULL) { + conf->cmd_len = (size_t)(args->argc); + conf->cmd = (char **)args->argv; + } + + return; +} + +static void request_pack_custom_log_options(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args->log_file != NULL) { + conf->log_file = args->log_file; + } + if (args->log_file_size != NULL) { + conf->log_file_size = args->log_file_size; + } + conf->log_file_rotate = args->log_file_rotate; + + return; +} + +static int request_pack_custom_log_accel(struct client_arguments *args, lcrc_container_config_t *conf) +{ + int ret = 0; + char *accargs = NULL; + + if (conf->accel != NULL) { + accargs = util_string_join(LCRD_ISULA_ACCEL_ARGS_SEPERATOR, (const char **)conf->accel, conf->accel_len); + + if (conf->annotations == NULL) { + conf->annotations = util_common_calloc_s(sizeof(json_map_string_string)); + if (conf->annotations == NULL) { + COMMAND_ERROR("alloc annotations failed for accel"); + ret = -1; + goto out; + } + } + + ret = append_json_map_string_string(conf->annotations, LCRD_ISULA_ACCEL_ARGS, accargs); + if (ret != 0) { + COMMAND_ERROR("init accel annotations failed accel=%s", accargs); + ret = -1; + goto out; + } + UTIL_FREE_AND_SET_NULL(accargs); + } + +out: + free(accargs); + return ret; +} + +static void request_pack_custom_work_dir(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + /* work dir in container */ + if (args->custom_conf.workdir != NULL) { + conf->workdir = args->custom_conf.workdir; + } + + return; +} + +static void request_pack_custom_tty(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + conf->tty = args->custom_conf.tty; + conf->open_stdin = args->custom_conf.open_stdin; + conf->attach_stdin = args->custom_conf.attach_stdin; + conf->attach_stdout = args->custom_conf.attach_stdout; + conf->attach_stderr = args->custom_conf.attach_stderr; + + return; +} + +static void request_pack_custom_health_check(const struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args->custom_conf.health_cmd != NULL) { + conf->health_cmd = args->custom_conf.health_cmd; + } + /* health check */ + conf->health_interval = args->custom_conf.health_interval; + conf->health_timeout = args->custom_conf.health_timeout; + conf->health_start_period = args->custom_conf.health_start_period; + conf->health_retries = args->custom_conf.health_retries; + conf->no_healthcheck = args->custom_conf.no_healthcheck; + conf->exit_on_unhealthy = args->custom_conf.exit_on_unhealthy; + + return; +} + +static int request_pack_custom_conf(struct client_arguments *args, lcrc_container_config_t *conf) +{ + if (args == NULL) { + return -1; + } + + /* environment variables */ + if (request_pack_custom_env(args, conf) != 0) { + return -1; + } + + /* user and group */ + request_pack_custom_user(args, conf); + + request_pack_custom_hostname(args, conf); + + /* alldevices */ + request_pack_custom_all_devices(args, conf); + + /* system container */ + request_pack_custom_system_container(args, conf); + + /* mounts to mount filesystem */ + request_pack_custom_mounts(args, conf); + + /* entrypoint */ + request_pack_custom_entrypoint(args, conf); + + /* command args */ + request_pack_custom_args(args, conf); + + /* console log options */ + request_pack_custom_log_options(args, conf); + + conf->annotations = args->annotations; + args->annotations = NULL; + + if (request_pack_custom_log_accel(args, conf) != 0) { + return -1; + } + + /* work dir in container */ + request_pack_custom_work_dir(args, conf); + + request_pack_custom_tty(args, conf); + + request_pack_custom_health_check(args, conf); + + return 0; +} + +static int request_pack_host_ns_change_files(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + int ret = 0; + size_t i = 0; + size_t files_len = 0; + char **files = NULL; + char *net_files[] = { "/proc/sys/net" }; + char *ipc_files[] = { + "/proc/sys/kernel/shmmax", + "/proc/sys/kernel/shmmni", + "/proc/sys/kernel/shmall", + "/proc/sys/kernel/shm_rmid_forced", + "/proc/sys/kernel/msgmax", + "/proc/sys/kernel/msgmni", + "/proc/sys/kernel/msgmnb", + "/proc/sys/kernel/sem", + "/proc/sys/fs/mqueue" + }; + char *net_ipc_files[] = { + "/proc/sys/net", + "/proc/sys/kernel/shmmax", + "/proc/sys/kernel/shmmni", + "/proc/sys/kernel/shmall", + "/proc/sys/kernel/shm_rmid_forced", + "/proc/sys/kernel/msgmax", + "/proc/sys/kernel/msgmni", + "/proc/sys/kernel/msgmnb", + "/proc/sys/kernel/sem", + "/proc/sys/fs/mqueue" + }; + + if (args->custom_conf.ns_change_opt == NULL) { + return 0; + } + + if (args->custom_conf.privileged) { + return 0; + } + + if (strcmp(args->custom_conf.ns_change_opt, "net") == 0) { + files = net_files; + files_len = sizeof(net_files) / sizeof(net_files[0]); + } else if (strcmp(args->custom_conf.ns_change_opt, "ipc") == 0) { + files = ipc_files; + files_len = sizeof(ipc_files) / sizeof(ipc_files[0]); + } else { + files = net_ipc_files; + files_len = sizeof(net_ipc_files) / sizeof(net_ipc_files[0]); + } + if (files_len > (SIZE_MAX / sizeof(char *)) - 1) { + ERROR("Too many files"); + return -1; + } + hostconfig->ns_change_files = util_common_calloc_s((files_len + 1) * sizeof(char *)); + if (hostconfig->ns_change_files == NULL) { + ERROR("Out of memory"); + return -1; + } + + for (i = 0; i < files_len; i++) { + hostconfig->ns_change_files[hostconfig->ns_change_files_len++] = util_strdup_s(files[i]); + } + + return ret; +} + +static void request_pack_host_caps(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* cap add */ + if (args->custom_conf.cap_adds != NULL) { + hostconfig->cap_add_len = util_array_len(args->custom_conf.cap_adds); + hostconfig->cap_add = args->custom_conf.cap_adds; + } + /* cap drop */ + if (args->custom_conf.cap_drops != NULL) { + hostconfig->cap_drop_len = util_array_len(args->custom_conf.cap_drops); + hostconfig->cap_drop = args->custom_conf.cap_drops; + } +} + +static void request_pack_host_group_add(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* group add */ + if (args->custom_conf.group_add != NULL) { + hostconfig->group_add_len = util_array_len(args->custom_conf.group_add); + hostconfig->group_add = args->custom_conf.group_add; + } +} + +static void request_pack_host_extra_hosts(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* extra hosts */ + if (args->custom_conf.extra_hosts != NULL) { + hostconfig->extra_hosts_len = util_array_len(args->custom_conf.extra_hosts); + hostconfig->extra_hosts = args->custom_conf.extra_hosts; + } +} + +static void request_pack_host_dns(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* dns */ + if (args->custom_conf.dns != NULL) { + hostconfig->dns_len = util_array_len(args->custom_conf.dns); + hostconfig->dns = args->custom_conf.dns; + } + + /* dns options */ + if (args->custom_conf.dns_options != NULL) { + hostconfig->dns_options_len = util_array_len(args->custom_conf.dns_options); + hostconfig->dns_options = args->custom_conf.dns_options; + } + + /* dns search */ + if (args->custom_conf.dns_search != NULL) { + hostconfig->dns_search_len = util_array_len(args->custom_conf.dns_search); + hostconfig->dns_search = args->custom_conf.dns_search; + } +} + +static void request_pack_host_ulimit(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* ulimit options */ + if (args->custom_conf.ulimits != NULL) { + hostconfig->ulimits_len = util_array_len(args->custom_conf.ulimits); + hostconfig->ulimits = args->custom_conf.ulimits; + } +} + +static void request_pack_host_weight_devices(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* blkio weight devices */ + if (args->custom_conf.weight_devices != NULL) { + hostconfig->blkio_weight_device_len = util_array_len(args->custom_conf.weight_devices); + hostconfig->blkio_weight_device = args->custom_conf.weight_devices; + } +} + +static void request_pack_host_device_read_bps(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + if (args->custom_conf.blkio_throttle_read_bps_device != NULL) { + hostconfig->blkio_throttle_read_bps_device_len = + util_array_len(args->custom_conf.blkio_throttle_read_bps_device); + hostconfig->blkio_throttle_read_bps_device = args->custom_conf.blkio_throttle_read_bps_device; + } +} + +static void request_pack_host_device_write_bps(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + if (args->custom_conf.blkio_throttle_write_bps_device != NULL) { + hostconfig->blkio_throttle_write_bps_device_len = + util_array_len(args->custom_conf.blkio_throttle_write_bps_device); + hostconfig->blkio_throttle_write_bps_device = args->custom_conf.blkio_throttle_write_bps_device; + } +} + +static void request_pack_host_blockio(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + request_pack_host_weight_devices(args, hostconfig); + request_pack_host_device_read_bps(args, hostconfig); + request_pack_host_device_write_bps(args, hostconfig); +} + +static void request_pack_host_devices(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* devices */ + if (args->custom_conf.devices != NULL) { + hostconfig->devices_len = util_array_len(args->custom_conf.devices); + hostconfig->devices = args->custom_conf.devices; + } +} + +static void request_pack_host_hugepage_limits(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* hugepage limits*/ + if (args->custom_conf.hugepage_limits != NULL) { + hostconfig->hugetlbs_len = util_array_len(args->custom_conf.hugepage_limits); + hostconfig->hugetlbs = args->custom_conf.hugepage_limits; + } +} + +static void request_pack_host_binds(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* volumes to binds */ + if (args->custom_conf.volumes != NULL) { + hostconfig->binds_len = (size_t)util_array_len(args->custom_conf.volumes); + hostconfig->binds = args->custom_conf.volumes; + } +} + +static void request_pack_host_hook_spec(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* hook-spec file */ + if (args->custom_conf.hook_spec != NULL) { + hostconfig->hook_spec = args->custom_conf.hook_spec; + } +} + +static void request_pack_host_restart_policy(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + if (args->restart != NULL) { + hostconfig->restart_policy = args->restart; + } +} + +static void request_pack_host_namespaces(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + if (args->host_channel != NULL) { + hostconfig->host_channel = args->host_channel; + } + + if (args->custom_conf.share_ns[NAMESPACE_NET] != NULL) { + hostconfig->network_mode = args->custom_conf.share_ns[NAMESPACE_NET]; + } + if (args->custom_conf.share_ns[NAMESPACE_IPC] != NULL) { + hostconfig->ipc_mode = args->custom_conf.share_ns[NAMESPACE_IPC]; + } + if (args->custom_conf.share_ns[NAMESPACE_USER] != NULL) { + hostconfig->userns_mode = args->custom_conf.share_ns[NAMESPACE_USER]; + } + if (args->custom_conf.share_ns[NAMESPACE_UTS] != NULL) { + hostconfig->uts_mode = args->custom_conf.share_ns[NAMESPACE_UTS]; + } + if (args->custom_conf.share_ns[NAMESPACE_PID] != NULL) { + hostconfig->pid_mode = args->custom_conf.share_ns[NAMESPACE_PID]; + } +} + +static void request_pack_host_security(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + /* security opt */ + if (args->custom_conf.security != NULL) { + hostconfig->security_len = util_array_len(args->custom_conf.security); + hostconfig->security = args->custom_conf.security; + } +} + +static int request_pack_host_config(const struct client_arguments *args, lcrc_host_config_t *hostconfig) +{ + int ret = 0; + + if (args == NULL) { + return -1; + } + + /* privileged */ + hostconfig->privileged = args->custom_conf.privileged; + + /* system container */ + hostconfig->system_container = args->custom_conf.system_container; + + /* oom kill disable */ + hostconfig->oom_kill_disable = args->custom_conf.oom_kill_disable; + + /* shm size */ + hostconfig->shm_size = args->custom_conf.shm_size; + + /* user remap */ + hostconfig->user_remap = args->custom_conf.user_remap; + + /* auto remove */ + hostconfig->auto_remove = args->custom_conf.auto_remove; + + /* readonly rootfs */ + hostconfig->readonly_rootfs = args->custom_conf.readonly; + + /* env target file */ + hostconfig->env_target_file = args->custom_conf.env_target_file; + + /* cgroup parent */ + hostconfig->cgroup_parent = args->custom_conf.cgroup_parent; + + ret = request_pack_host_config_cgroup(args, hostconfig); + if (ret != 0) { + return ret; + } + + /* storage options */ + ret = request_pack_host_config_storage_opts(args, hostconfig); + if (ret != 0) { + return ret; + } + + /* sysctls */ + ret = request_pack_host_config_sysctls(args, hostconfig); + if (ret != 0) { + return ret; + } + + ret = request_pack_host_ns_change_files(args, hostconfig); + if (ret != 0) { + return ret; + } + + request_pack_host_caps(args, hostconfig); + + request_pack_host_group_add(args, hostconfig); + + request_pack_host_extra_hosts(args, hostconfig); + + request_pack_host_dns(args, hostconfig); + + request_pack_host_ulimit(args, hostconfig); + + request_pack_host_blockio(args, hostconfig); + + request_pack_host_devices(args, hostconfig); + + request_pack_host_hugepage_limits(args, hostconfig); + + request_pack_host_binds(args, hostconfig); + + request_pack_host_hook_spec(args, hostconfig); + + request_pack_host_restart_policy(args, hostconfig); + + request_pack_host_namespaces(args, hostconfig); + + request_pack_host_security(args, hostconfig); + + return ret; +} + +#define IMAGE_NOT_FOUND_ERROR "No such image" + +static int do_client_create(const struct client_arguments *args, const lcrc_connect_ops *ops, + const struct lcrc_create_request *request, + struct lcrc_create_response *response) +{ + int ret = 0; + client_connect_config_t config = get_connect_config(args); + + ret = ops->container.create(request, response, &config); + if (ret != 0) { + if (response->cc == LCRD_ERR_INPUT) { + ret = EINVALIDARGS; + } else if (response->server_errono || + (response->errmsg && !strcmp(response->errmsg, errno_to_error_message(LCRD_ERR_CONNECT)))) { + ret = ESERVERERROR; + } else { + ret = ECOMMON; + } + } + return ret; +} + +static int client_try_to_create(const struct client_arguments *args, const struct lcrc_create_request *request, + struct lcrc_create_response **out_response) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_create_response *response = NULL; + + response = util_common_calloc_s(sizeof(struct lcrc_create_response)); + if (response == NULL) { + ERROR("Out of memory"); + ret = ECOMMON; + goto out; + } + + ops = get_connect_client_ops(); + if (ops == NULL || ops->container.create == NULL) { + ERROR("Unimplemented ops"); + ret = ESERVERERROR; + goto out; + } + + ret = do_client_create(args, ops, request, response); + if (ret != 0) { + if (response->errmsg == NULL || strstr(response->errmsg, IMAGE_NOT_FOUND_ERROR) == NULL) { + client_print_error(response->cc, response->server_errono, response->errmsg); + goto out; + } + COMMAND_ERROR("Unable to find image '%s' locally", request->image); + ret = client_pull(args); + if (ret != 0) { + goto out; + } + + /* retry create */ + lcrc_create_response_free(response); + response = util_common_calloc_s(sizeof(struct lcrc_create_response)); + if (response == NULL) { + ERROR("Out of memory"); + ret = ECOMMON; + goto out; + } + ret = do_client_create(args, ops, request, response); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + goto out; + } + } +out: + *out_response = response; + return ret; +} + +static void free_alloced_memory_in_host_config(lcrc_host_config_t *hostconfig) +{ + lcrc_ns_change_files_free(hostconfig); + lcrc_host_config_storage_opts_free(hostconfig); + lcrc_host_config_sysctl_free(hostconfig); +} + +static void free_alloced_memory_in_config(lcrc_container_config_t *custom_conf) +{ + if (custom_conf == NULL) { + return; + } + + free_json_map_string_string(custom_conf->annotations); + custom_conf->annotations = NULL; +} + +/* + * Create a create request message and call RPC + */ +int client_create(struct client_arguments *args) +{ + int ret = 0; + struct lcrc_create_request request = { 0 }; + struct lcrc_create_response *response = NULL; + lcrc_container_config_t custom_conf = { 0 }; + lcrc_host_config_t host_config = { 0 }; + container_cgroup_resources_t cr = { 0 }; + + request.name = args->name; + request.rootfs = args->create_rootfs; + request.runtime = args->runtime; + request.image = args->image_name; + request.hostconfig = &host_config; + request.config = &custom_conf; + host_config.cr = &cr; + + ret = request_pack_custom_conf(args, request.config); + if (ret != 0) { + goto out; + } + + ret = request_pack_host_config(args, request.hostconfig); + if (ret != 0) { + goto out; + } + + ret = client_try_to_create(args, &request, &response); + if (ret != 0) { + goto out; + } + + if (response->id != NULL) { + free(args->name); + args->name = util_strdup_s(response->id); + } else { + ERROR("Container id create failed."); + ret = ESERVERERROR; + goto out; + } +out: + free_alloced_memory_in_host_config(request.hostconfig); + free_alloced_memory_in_config(request.config); + lcrc_create_response_free(response); + return ret; +} + +static int log_opt_parse_options(struct client_arguments *args, const char *optkey, const char *value) +{ + int ret = -1; + + if (strcmp(optkey, "max-size") == 0) { + args->log_file_size = util_strdup_s(value); + ret = 0; + } else if (strcmp(optkey, "max-file") == 0) { + if (util_safe_uint(value, &args->log_file_rotate)) { + goto out; + } + if (args->log_file_rotate == 0) { + COMMAND_ERROR("Invalid option 'max-file' key:%s, value:%s", optkey, value); + goto out; + } + ret = 0; + } else if (strcmp(optkey, "disable-log") == 0) { + if (strcmp(value, "true") == 0) { + ret = 0; + } else if (strcmp(value, "false") == 0) { + args->log_file = NULL; + ret = 0; + } else { + COMMAND_ERROR("Invalid option 'disable-log' key:%s, value:%s", optkey, value); + } + } +out: + return ret; +} + +int log_opt_parser(struct client_arguments *args, const char *option) +{ + int ret = -1; + char *optkey = NULL; + char *value = NULL; + char *tmp = NULL; + size_t len; + size_t total_len; + + if (option == NULL || args == NULL) { + goto out; + } + + tmp = util_strdup_s(option); + + // log option format: key=value + total_len = strlen(tmp); + if (total_len <= 2) { + goto out; + } + + optkey = tmp; + value = strchr(tmp, '='); + // option do not contain '=' + if (value == NULL) { + goto out; + } + + len = (size_t)(value - optkey); + // if option is 'optkey=' + if (total_len == len + 1) { + goto out; + } + + // if option is '=optkey' + if (len == 0) { + goto out; + } + + tmp[len] = '\0'; + value += 1; + + ret = log_opt_parse_options(args, optkey, value); + +out: + if (ret < 0) { + COMMAND_ERROR("Invalid option: %s", option); + } + free(tmp); + + return ret; +} +int callback_log_opt(command_option_t *option, const char *value) +{ + struct client_arguments *args = (struct client_arguments *)option->data; + return log_opt_parser(args, value); +} + +static int annotation_parser(struct client_arguments *args, const char *option) +{ + int ret = -1; + char *optkey = NULL; + char *value = NULL; + char *tmp = NULL; + + if (args == NULL || option == NULL) { + goto out; + } + + // annotation format: key[=][value] + tmp = util_strdup_s(option); + + optkey = tmp; + value = strchr(tmp, '='); + + if (value != NULL) { + *value = '\0'; + value++; + } else { + value = ""; + } + + if (optkey[0] == '\0') { + goto out; + } + + if (args->annotations == NULL) { + args->annotations = util_common_calloc_s(sizeof(json_map_string_string)); + if (args->annotations == NULL) { + COMMAND_ERROR("Out Memory"); + goto out; + } + } + + if (append_json_map_string_string(args->annotations, optkey, value)) { + COMMAND_ERROR("Failed to append annotation key:%s, value:%s", optkey, value); + goto out; + } + + ret = 0; + +out: + if (ret < 0) { + COMMAND_ERROR("Invalid option: '%s'", option); + } + free(tmp); + + return ret; +} +int callback_annotation(command_option_t *option, const char *value) +{ + struct client_arguments *args = (struct client_arguments *)option->data; + return annotation_parser(args, value); +} + +int cmd_create_main(int argc, const char **argv) +{ + int nret = 0; + int ret = 0; + command_t cmd = { 0 }; + struct log_config lconf = { 0 }; + + if (client_arguments_init(&g_cmd_create_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_create_args.progname = argv[0]; + g_cmd_create_args.subcommand = argv[1]; + set_default_command_log_config(argv[0], &lconf); + struct command_option options[] = { + LOG_OPTIONS(lconf), + CREATE_OPTIONS(g_cmd_create_args), + CREATE_EXTEND_OPTIONS(g_cmd_create_args), + COMMON_OPTIONS(g_cmd_create_args) + }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_create_desc, + g_cmd_create_usage); + if (command_parse_args(&cmd, &g_cmd_create_args.argc, &g_cmd_create_args.argv) || + create_checker(&g_cmd_create_args)) { + nret = EINVALIDARGS; + goto out; + } + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed"); + exit(ECOMMON); + } + + ret = client_create(&g_cmd_create_args); + if (ret != 0) { + ERROR("Container \"%s\" create failed", g_cmd_create_args.name); + nret = ECOMMON; + goto out; + } + printf("%s\n", g_cmd_create_args.name); + nret = EXIT_SUCCESS; +out: + client_arguments_free(&g_cmd_create_args); + exit(nret); +} + +static int check_parsed_devices(const char *devices, const char *cgroup_permissions, const char *path_in_container) +{ + int ret = 0; + int nret = 0; + + /* check valid device mode */ + if (!util_valid_device_mode(cgroup_permissions)) { + COMMAND_ERROR("Invalid value \"%s\" for flag --device: bad mode specified: %s", devices, cgroup_permissions); + ret = -1; + goto out; + } + + /* check valid path in container */ + nret = util_validate_absolute_path(path_in_container); + if (nret != 0) { + COMMAND_ERROR("Invalid value \"%s\" for flag --device: %s is not an absolute path", devices, path_in_container); + ret = -1; + goto out; + } +out: + return ret; +} + +static bool check_devices_conf_valid(const char *devices) +{ + bool ret = true; + size_t tmp_str_len = 0; + char **tmp_str = NULL; + char *cgroup_permissions = NULL; + char *path_in_container = NULL; + + if (devices == NULL || !strcmp(devices, "")) { + COMMAND_ERROR("Invalid value \"%s\" for flag --device", devices ? devices : "null"); + return false; + } + + tmp_str = util_string_split(devices, ':'); + if (tmp_str == NULL) { + ERROR("Out of memory"); + ret = false; + goto out; + } + tmp_str_len = util_array_len(tmp_str); + + // device format: x:x:x or x:x or x + switch (tmp_str_len) { + case 3: + path_in_container = tmp_str[1]; + cgroup_permissions = tmp_str[2]; + break; + case 2: + if (util_valid_device_mode(tmp_str[1])) { + path_in_container = tmp_str[0]; + cgroup_permissions = tmp_str[1]; + } else { + path_in_container = tmp_str[1]; + cgroup_permissions = "rwm"; + } + break; + case 1: + path_in_container = tmp_str[0]; + cgroup_permissions = "rwm"; + break; + default: + COMMAND_ERROR("Invalid value \"%s\" for flag --device\n", devices); + ret = false; + break; + } + if (!ret) { + goto out; + } + + /* check valid device */ + if (check_parsed_devices(devices, cgroup_permissions, path_in_container) != 0) { + ret = false; + goto out; + } + +out: + util_free_array(tmp_str); + return ret; +} + +static bool check_volumes_valid(const char *volume) +{ + bool ret = true; + size_t alen = 0; + char **array = NULL; + char **modes = NULL; + + // split volume to src:dest:mode + array = util_string_split(volume, ':'); + if (array == NULL) { + COMMAND_ERROR("Out of memory"); + ret = false; + goto free_out; + } + alen = util_array_len(array); + + // volume format: src:dst:mode + switch (alen) { + case 1: + COMMAND_ERROR("Not supported volume format '%s'", volume); + ret = false; + goto free_out; + /* fall-through */ + case 2: + if (util_valid_mount_mode(array[1])) { + // Destination + Mode is not a valid volume - volumes + // cannot include a mode. eg /foo:rw + COMMAND_ERROR("Invalid volume specification '%s',Invalid mode:%s", volume, array[1]); + ret = false; + goto free_out; + } + break; + case 3: + if (!util_valid_mount_mode(array[2])) { + COMMAND_ERROR("Invalid volume specification '%s'.Invalid mode:%s", volume, array[2]); + ret = false; + goto free_out; + } + modes = util_string_split(array[2], ','); + if (modes == NULL) { + ERROR("Out of memory"); + ret = false; + goto free_out; + } + break; + default: + COMMAND_ERROR("Invalid volume specification '%s'", volume); + ret = false; + goto free_out; + } + + if (array[0][0] != '/' || array[1][0] != '/' || strcmp(array[1], "/") == 0) { + COMMAND_ERROR("Invalid volume: path must be absolute, and destination can't be '/'"); + ret = false; + goto free_out; + } + +free_out: + util_free_array(array); + util_free_array(modes); + return ret; +} + +static bool check_volumes_conf_valid(const char *volume) +{ + if (volume == NULL || !strcmp(volume, "")) { + COMMAND_ERROR("Volume can't be empty"); + return false; + } + + if (volume[0] == ':' || volume[strlen(volume) - 1] == ':') { + COMMAND_ERROR("Delimiter ':' can't be the first or the last character"); + return false; + } + + return check_volumes_valid(volume); +} + +struct valid_mounts_state { + char *mount; + bool has_type; + bool has_src; + bool has_dst; + bool type_squashfs; + char *source; +}; + +#define MOUNT_STATE_CHECK_SUCCESS 0 +#define MOUNT_STATE_CHECK_IGNORE 1 +#define MOUNT_STATE_CHECK_INVALID_ARG 2 + +static int parse_mount_item_type(const char *value, struct valid_mounts_state *state) +{ + /* If value of type is NULL, ignore it */ + if (value == NULL) { + return MOUNT_STATE_CHECK_IGNORE; + } + + if (state->has_type) { + COMMAND_ERROR("Invalid mount specification '%s'.More than one type found", state->mount); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + + if (strcmp(value, "squashfs") && strcmp(value, "bind")) { + COMMAND_ERROR("Invalid mount specification '%s'.Type must be squashfs or bind", state->mount); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + + if (strcmp(value, "squashfs") == 0) { + state->type_squashfs = true; + } + + state->has_type = true; + return MOUNT_STATE_CHECK_SUCCESS; +} + +static int parse_mount_item_src(const char *value, struct valid_mounts_state *state) +{ + /* If value of source is NULL, ignore it */ + if (value == NULL) { + return MOUNT_STATE_CHECK_IGNORE; + } + + if (state->has_src) { + COMMAND_ERROR("Invalid mount specification '%s'.More than one source found", state->mount); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + + if (value[0] != '/') { + COMMAND_ERROR("Invalid mount specification '%s'.Source must be absolute path", state->mount); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + + free(state->source); + state->source = util_strdup_s(value); + + state->has_src = true; + return MOUNT_STATE_CHECK_SUCCESS; +} + +static int parse_mount_item_dst(const char *value, struct valid_mounts_state *state) +{ + char dstpath[PATH_MAX] = { 0 }; + + /* If value of destination is NULL, ignore it */ + if (value == NULL) { + return MOUNT_STATE_CHECK_IGNORE; + } + + if (state->has_dst) { + COMMAND_ERROR("Invalid mount specification '%s'.More than one destination found", state->mount); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + + if (value[0] != '/') { + COMMAND_ERROR("Invalid mount specification '%s'.Destination must be absolute path", state->mount); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + + if (strcmp(value, "/") == 0) { + COMMAND_ERROR("Invalid mount specification '%s'.Destination can't be '/'", state->mount); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + + if (!cleanpath(value, dstpath, sizeof(dstpath))) { + COMMAND_ERROR("Invalid mount specification '%s'.Can't translate destination path to clean path", state->mount); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + + state->has_dst = true; + return MOUNT_STATE_CHECK_SUCCESS; +} + +static int parse_mount_item_ro(const char *value, const struct valid_mounts_state *state) +{ + if (value != NULL) { + if (strcmp(value, "1") && strcmp(value, "true") && strcmp(value, "0") && strcmp(value, "false")) { + COMMAND_ERROR("Invalid mount specification '%s'.Invalid readonly mode:%s", state->mount, value); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + } + return MOUNT_STATE_CHECK_SUCCESS; +} + +static int parse_mount_item_propagation(const char *value, const struct valid_mounts_state *state) +{ + if (value == NULL) { + return MOUNT_STATE_CHECK_IGNORE; + } + + if (!util_valid_propagation_mode(value)) { + COMMAND_ERROR("Invalid mount specification '%s'.Invalid propagation mode:%s", state->mount, value); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + return MOUNT_STATE_CHECK_SUCCESS; +} + +static int parse_mount_item_selinux(const char *value, const struct valid_mounts_state *state) +{ + if (value == NULL) { + return MOUNT_STATE_CHECK_IGNORE; + } + + if (!util_valid_label_mode(value)) { + COMMAND_ERROR("Invalid mount specification '%s'.Invalid bind selinux opts:%s", state->mount, value); + return MOUNT_STATE_CHECK_INVALID_ARG; + } + return MOUNT_STATE_CHECK_SUCCESS; +} + +/* + * 0: success + * 1: ignore this item, continue + * 2: failed + */ +static int valid_mounts_item(const char *mntkey, const char *value, struct valid_mounts_state *state) +{ + if (strcmp(mntkey, "type") == 0) { + return parse_mount_item_type(value, state); + } else if (strcmp(mntkey, "src") == 0 || strcmp(mntkey, "source") == 0) { + return parse_mount_item_src(value, state); + } else if (strcmp(mntkey, "dst") == 0 || strcmp(mntkey, "destination") == 0) { + return parse_mount_item_dst(value, state); + } else if (strcmp(mntkey, "ro") == 0 || strcmp(mntkey, "readonly") == 0) { + return parse_mount_item_ro(value, state); + } else if (strcmp(mntkey, "bind-propagation") == 0) { + return parse_mount_item_propagation(value, state); + } else if (strcmp(mntkey, "bind-selinux-opts") == 0) { + return parse_mount_item_selinux(value, state); + } else { + COMMAND_ERROR("Invalid mount specification '%s'.Unsupported item:%s", state->mount, mntkey); + return MOUNT_STATE_CHECK_INVALID_ARG; + } +} + +static int parse_mounts_conf(const char *mount, struct valid_mounts_state *state) +{ + int ret = 0; + size_t i = 0; + size_t items_len = 0; + char **items = NULL; + char **key_val = NULL; + + items = util_string_split(mount, ','); + if (items == NULL) { + ret = EINVALIDARGS; + COMMAND_ERROR("Invalid mount specification '%s'. unsupported format", mount); + goto out; + } + + items_len = util_array_len(items); + + for (i = 0; i < items_len; i++) { + key_val = util_string_split(items[i], '='); + if (key_val == NULL) { + continue; + } + + ret = valid_mounts_item(key_val[0], key_val[1], state); + if (ret == MOUNT_STATE_CHECK_IGNORE) { /* ignore this item */ + ret = 0; + util_free_array(key_val); + key_val = NULL; + continue; + } else if (ret == MOUNT_STATE_CHECK_INVALID_ARG) { /* invalid args */ + ret = EINVALIDARGS; + goto out; + } + util_free_array(key_val); + key_val = NULL; + } + +out: + util_free_array(key_val); + util_free_array(items); + return ret; +} + +static int check_parsed_mounts_conf(const char *mount, const struct valid_mounts_state *state) +{ + int ret = 0; + char real_path[PATH_MAX] = { 0 }; /* Init to zero every time loop enter here. */ + + if (!state->has_type) { + ret = EINVALIDARGS; + COMMAND_ERROR("Invalid mount specification '%s'.Missing type", mount); + goto out; + } + + if (!state->has_src) { + ret = EINVALIDARGS; + COMMAND_ERROR("Invalid mount specification '%s'.Missing source", mount); + goto out; + } + + if (!state->has_dst) { + ret = EINVALIDARGS; + COMMAND_ERROR("Invalid mount specification '%s'.Missing destination", mount); + goto out; + } + + if (state->type_squashfs) { + if (strlen(state->source) > PATH_MAX || realpath(state->source, real_path) == NULL) { + ret = EINVALIDARGS; + COMMAND_ERROR("Invalid mount specification '%s'.Source %s not exist", mount, state->source); + goto out; + } + + /* Make sure it's a regular file */ + if (!util_valid_file(real_path, S_IFREG)) { + ret = EINVALIDARGS; + COMMAND_ERROR("Invalid mount specification '%s'.Source %s is not a squashfs file", mount, state->source); + goto out; + } + } +out: + return ret; +} + +static bool check_mounts_conf_valid(const char *mount) +{ + int ret = 0; + struct valid_mounts_state state = { (char *)mount, false, false, false, NULL }; + + if (mount == NULL) { + COMMAND_ERROR("Invalid mount specification: can't be empty"); + return false; + } + if (!mount[0]) { + COMMAND_ERROR("Invalid mount specification: can't be empty"); + return false; + } + + ret = parse_mounts_conf(mount, &state); + if (ret != 0) { + goto out; + } + + ret = check_parsed_mounts_conf(mount, &state); + if (ret != 0) { + goto out; + } + +out: + free(state.source); + return ret ? false : true; +} + +static int check_hook_spec_file(const char *hook_spec) +{ + struct stat hookstat = { 0 }; + + if (hook_spec == NULL) { + return 0; + } + if (util_validate_absolute_path(hook_spec)) { + COMMAND_ERROR("Hook path \"%s\" must be an absolute path", hook_spec); + return -1; + } + if (stat(hook_spec, &hookstat)) { + COMMAND_ERROR("Stat hook spec file failed: %s", strerror(errno)); + return -1; + } + if ((hookstat.st_mode & S_IFMT) != S_IFREG) { + COMMAND_ERROR("Hook spec file must be a regular text file"); + return -1; + } + + if (hookstat.st_size > REGULAR_FILE_SIZE) { + COMMAND_ERROR("Hook spec file size %llu exceed limit: %dM", (unsigned long long)hookstat.st_size, + (int)(REGULAR_FILE_SIZE / SIZE_MB)); + return -1; + } + + return 0; +} + +static int create_check_rootfs(struct client_arguments *args) +{ + int ret = 0; + + if (args->external_rootfs != NULL) { + args->create_rootfs = util_strdup_s(args->external_rootfs); + } else { + if (strcmp(args->argv[0], "none:latest") == 0 || strcmp(args->argv[0], "none") == 0) { + char *rootfs = getenv("IMAGE_NONE_PATH"); + if (rootfs != NULL) { + args->create_rootfs = util_strdup_s(rootfs); + } else { + args->create_rootfs = util_strdup_s(DEFAULT_ROOTFS_PATH); + } + } + } + + args->image_name = args->argv[0]; + + args->argc--; + args->argv++; + + if (args->create_rootfs != NULL) { + char real_path[PATH_MAX] = { 0 }; + if (realpath(args->create_rootfs, real_path) == NULL) { + COMMAND_ERROR("Failed to get rootfs '%s': %s", args->create_rootfs, strerror(errno)); + ret = -1; + goto out; + } + free(args->create_rootfs); + args->create_rootfs = util_strdup_s(real_path); + } +out: + return ret; +} + +static int create_check_hugetlbs(const struct client_arguments *args) +{ + int ret = 0; + size_t len, i; + + len = util_array_len(args->custom_conf.hugepage_limits); + for (i = 0; i < len; i++) { + char *limit = NULL; + int64_t limitvalue; + char *dup = NULL; + char *p = NULL; + char *pdot2 = NULL; + + dup = util_strdup_s(args->custom_conf.hugepage_limits[i]); + + p = dup; + p = strchr(p, ':'); + if (p == NULL) { + limit = dup; + } else { + *p = '\0'; + p++; + pdot2 = strchr(p, ':'); + if (pdot2 != NULL) { + COMMAND_ERROR("Invalid arguments \"%s\" for flag --hugetlb-limit: too many colons", + args->custom_conf.hugepage_limits[i]); + free(dup); + ret = -1; + goto out; + } + limit = p; + } + ret = util_parse_byte_size_string(limit, &limitvalue); + if (ret != 0) { + COMMAND_ERROR("Invalid hugetlb limit:%s:%s", limit, strerror(-ret)); + free(dup); + ret = -1; + goto out; + } + free(dup); + } +out: + return ret; +} + +static int create_check_network(const struct client_arguments *args) +{ + size_t len, i; + struct sockaddr_in sa; + + len = util_array_len(args->custom_conf.extra_hosts); + for (i = 0; i < len; i++) { + char **items = NULL; + items = util_string_split(args->custom_conf.extra_hosts[i], ':'); + if (items == NULL) { + COMMAND_ERROR("split extra hosts '%s' failed.", args->custom_conf.extra_hosts[i]); + return -1; + } + if (util_array_len(items) != 2) { + util_free_array(items); + COMMAND_ERROR("Invalid extra hosts specification '%s'. unsupported format", + args->custom_conf.extra_hosts[i]); + return EINVALIDARGS; + } + if (!inet_pton(AF_INET, items[1], &sa.sin_addr)) { + COMMAND_ERROR("Invalid host ip address '%s'.", items[1]); + util_free_array(items); + return EINVALIDARGS; + } + util_free_array(items); + } + len = util_array_len(args->custom_conf.dns); + for (i = 0; i < len; i++) { + if (!inet_pton(AF_INET, args->custom_conf.dns[i], &sa.sin_addr)) { + COMMAND_ERROR("Invalid dns ip address '%s'.", args->custom_conf.dns[i]); + return EINVALIDARGS; + } + } + return 0; +} + +static int create_hostname_checker(const struct client_arguments *args) +{ + int ret = 0; + + if (args->custom_conf.hostname != NULL) { + if (!util_valid_host_name(args->custom_conf.hostname)) { + COMMAND_ERROR("Invalid container hostname (%s), only %s and less than 64 bytes are allowed.", + args->custom_conf.hostname, HOST_NAME_REGEXP); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int create_name_checker(const struct client_arguments *args) +{ + int ret = 0; + + if (args->name != NULL && !util_valid_container_name(args->name)) { + COMMAND_ERROR("Invalid container name (%s), only [a-zA-Z0-9][a-zA-Z0-9_.-] are allowed.", args->name); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int create_devices_volumes_checker(const struct client_arguments *args) +{ + int ret = 0; + size_t i; + size_t len = 0; + + len = util_array_len(args->custom_conf.devices); + for (i = 0; i < len; i++) { + if (!check_devices_conf_valid(args->custom_conf.devices[i])) { + ret = -1; + goto out; + } + } + len = util_array_len(args->custom_conf.volumes); + for (i = 0; i < len; i++) { + if (!check_volumes_conf_valid(args->custom_conf.volumes[i])) { + ret = -1; + goto out; + } + } + len = util_array_len(args->custom_conf.mounts); + for (i = 0; i < len; i++) { + if (!check_mounts_conf_valid(args->custom_conf.mounts[i])) { + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int create_namespaces_checker(const struct client_arguments *args) +{ + int ret = 0; + const char *net_mode = args->custom_conf.share_ns[NAMESPACE_NET]; + + if (args->custom_conf.share_ns[NAMESPACE_NET]) { + if (!is_host(net_mode) && !is_container(net_mode) && !is_none(net_mode)) { + COMMAND_ERROR("Unsupported network mode %s", net_mode); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int create_check_user_remap(const struct client_arguments *args) +{ + char *user_remap = args->custom_conf.user_remap; + unsigned int host_uid = 0; + unsigned int host_gid = 0; + unsigned int size = 0; + + if (user_remap == NULL) { + return 0; + } + if (args->custom_conf.privileged || !args->custom_conf.system_container || args->external_rootfs == NULL) { + COMMAND_ERROR("--user-remap only available for system container"); + return -1; + } + return util_parse_user_remap(user_remap, &host_uid, &host_gid, &size); +} + +static int create_check_nschangeopt(const struct client_arguments *args) +{ + size_t array_str_len; + size_t i; + char **array_str = NULL; + + if (args->custom_conf.ns_change_opt == NULL) { + return 0; + } + + if (!args->custom_conf.system_container) { + COMMAND_ERROR("Unsupported ns-change-opt param in normal container"); + return EINVALIDARGS; + } + + array_str = util_string_split(args->custom_conf.ns_change_opt, ','); + if (array_str == NULL) { + ERROR("Out of memory"); + return EINVALIDARGS; + } + array_str_len = util_array_len(array_str); + if (array_str_len != 1 && array_str_len != 2) { + ERROR("invalid ns-change-opt pararm:%s\n", args->custom_conf.ns_change_opt); + util_free_array(array_str); + return EINVALIDARGS; + } + + for (i = 0; i < array_str_len; i++) { + if ((strcmp(array_str[i], "net") != 0) && (strcmp(array_str[i], "ipc") != 0)) { + ERROR("invalid ns-change-opt pararm:%s\n", args->custom_conf.ns_change_opt); + util_free_array(array_str); + return EINVALIDARGS; + } + } + + util_free_array(array_str); + return 0; +} + +static int create_check_oomkilldisable(const struct client_arguments *args) +{ + if (args->custom_conf.oom_kill_disable && args->cr.memory_limit == 0) { + COMMAND_ERROR("WARNING: Disabling the OOM killer on containers without " \ + "setting a '-m/--memory' limit may be dangerous."); + } + + return 0; +} + +static void restore_to_equate(char *p) +{ + *p = '='; +} + +static bool do_create_check_sysctl(const char *sysctl) +{ + char *p = NULL; + + p = strchr(sysctl, '='); + if (p != NULL) { + *p = '\0'; + if (strcmp("kernel.pid_max", sysctl) == 0) { + if (!pid_max_kernel_namespaced()) { + COMMAND_ERROR("Sysctl '%s' is not kernel namespaced, it cannot be changed", + sysctl); + restore_to_equate(p); + return false; + } else { + restore_to_equate(p); + return true; + } + } + if (!check_sysctl_valid(sysctl)) { + restore_to_equate(p); + COMMAND_ERROR("Sysctl '%s' is not whitelist", sysctl); + return false; + } + restore_to_equate(p); + } else { + COMMAND_ERROR("Invalid sysctl option '%s'", sysctl); + return false; + } + return true; +} + +static int create_check_sysctl(const struct client_arguments *args) +{ + size_t i = 0; + size_t len = 0; + + if (args->custom_conf.sysctls == NULL) { + return 0; + } + + len = util_array_len(args->custom_conf.sysctls); + for (i = 0; i < len; i++) { + if (!do_create_check_sysctl((const char *)args->custom_conf.sysctls[i])) { + return -1; + } + } + return 0; +} + +static int create_check_env_target_file(const struct client_arguments *args) +{ + int ret = 0; + int64_t file_size = 0; + char *env_path = NULL; + char *env_target_file = args->custom_conf.env_target_file; + + if (env_target_file == NULL) { + return 0; + } + if (env_target_file[0] != '/') { + COMMAND_ERROR("env target file path must be absolute path"); + return -1; + } + if (args->external_rootfs == NULL) { + COMMAND_ERROR("external rootfs not specified"); + return 0; + } + env_path = util_path_join(args->external_rootfs, env_target_file); + if (env_path == NULL) { + COMMAND_ERROR("join env target file path error"); + return -1; + } + if (strncmp(env_path, args->external_rootfs, strlen(args->external_rootfs)) != 0) { + COMMAND_ERROR("env target file path must be under rootfs '%s'", args->external_rootfs); + ret = -1; + goto out; + } + if (!util_file_exists(env_path)) { + goto out; + } + file_size = util_file_size(env_path); + if (file_size > REGULAR_FILE_SIZE) { + COMMAND_ERROR("env target file '%s', size exceed limit: %lld", env_path, REGULAR_FILE_SIZE); + ret = -1; + goto out; + } +out: + free(env_path); + return ret; +} + +int create_checker(struct client_arguments *args) +{ + int ret = 0; + + if (args == NULL) { + return -1; + } + + args->custom_conf.attach_stdin = args->custom_conf.open_stdin; + + if (create_hostname_checker(args) != 0) { + ret = -1; + goto out; + } + + if (create_name_checker(args) != 0) { + ret = -1; + goto out; + } + + if (create_devices_volumes_checker(args) != 0) { + ret = -1; + goto out; + } + + if (args->argc < 1) { + COMMAND_ERROR("\"%s\" requires a minimum of 1 argument.", args->subcommand); + ret = -1; + goto out; + } + + if (create_check_rootfs(args)) { + ret = -1; + goto out; + } + + if (create_check_network(args)) { + ret = -1; + goto out; + } + + if (create_check_user_remap(args)) { + ret = -1; + goto out; + } + + if (check_hook_spec_file(args->custom_conf.hook_spec)) { + ret = -1; + goto out; + } + + if (create_check_hugetlbs(args)) { + ret = -1; + goto out; + } + + if (create_namespaces_checker(args) != 0) { + ret = -1; + goto out; + } + + if (create_check_nschangeopt(args)) { + ret = -1; + goto out; + } + + if (create_check_oomkilldisable(args)) { + ret = -1; + goto out; + } + + if (create_check_sysctl(args)) { + ret = -1; + goto out; + } + + if (create_check_env_target_file(args)) { + ret = -1; + goto out; + } + +out: + return ret; +} diff --git a/src/cmd/lcrc/base/create.h b/src/cmd/lcrc/base/create.h new file mode 100644 index 0000000..667b2c1 --- /dev/null +++ b/src/cmd/lcrc/base/create.h @@ -0,0 +1,170 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container create definition + ******************************************************************************/ +#ifndef __CMD_CREATE_H +#define __CMD_CREATE_H + +#include "arguments.h" + +#define CREATE_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_CALLBACK, false, "accel", 0, &(cmdargs).custom_conf.accel, \ + "Accelerator bindings (format: [=][@[,]])", \ + command_append_array }, \ + { CMD_OPT_TYPE_BOOL, false, "read-only", 0, &(cmdargs).custom_conf.readonly, \ + "Make container rootfs readonly", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "cap-add", 0, &(cmdargs).custom_conf.cap_adds, \ + "Add Linux capabilities ('ALL' to add all capabilities)", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "cap-drop", 0, &(cmdargs).custom_conf.cap_drops, \ + "Drop Linux capabilities ('ALL' to drop all capabilities)", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "cpu-shares", 0, &(cmdargs).cr.cpu_shares, \ + "CPU shares (relative weight)", command_convert_llong }, \ + { CMD_OPT_TYPE_CALLBACK, false, "cpu-period", 0, &(cmdargs).cr.cpu_period, \ + "Limit CPU CFS (Completely Fair Scheduler) period", command_convert_llong }, \ + { CMD_OPT_TYPE_CALLBACK, false, "cpu-quota", 0, &(cmdargs).cr.cpu_quota, \ + "Limit CPU CFS (Completely Fair Scheduler) quota", command_convert_llong }, \ + { CMD_OPT_TYPE_STRING, false, "cpuset-cpus", 0, &(cmdargs).cr.cpuset_cpus, \ + "CPUs in which to allow execution (e.g. 0-3, 0,1)", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "cpuset-mems", 0, &(cmdargs).cr.cpuset_mems, \ + "MEMs in which to allow execution (0-3, 0,1)", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "device-read-bps", 0, &(cmdargs).custom_conf.blkio_throttle_read_bps_device, \ + "Limit read rate (bytes per second) from a device (default [])", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "device-write-bps", 0, &(cmdargs).custom_conf.blkio_throttle_write_bps_device, \ + "Limit write rate (bytes per second) to a device (default [])", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "oom-score-adj", 0, &(cmdargs).cr.oom_score_adj, \ + "Tune host's OOM preferences (-1000 to 1000)", command_convert_llong }, \ + { CMD_OPT_TYPE_CALLBACK, false, "device", 0, &(cmdargs).custom_conf.devices, \ + "Add a host device to the container", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "env", 'e', &(cmdargs).custom_conf.env, \ + "Set environment variables", command_append_array }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "entrypoint", 0, &(cmdargs).custom_conf.entrypoint, \ + "Entrypoint to run when starting the container", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "external-rootfs", 0, &(cmdargs).external_rootfs, \ + "Specify the custom rootfs that is not managed by LCRD for the container, directory or block device", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "files-limit", 0, &(cmdargs).custom_conf.files_limit, \ + "Tune container files limit (set -1 for unlimited)", command_convert_llong }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "hook-spec", 0, &(cmdargs).custom_conf.hook_spec, \ + "File containing hook definition(prestart, poststart, poststop)", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "hostname", 'h', &(cmdargs).custom_conf.hostname, \ + "Container host name", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "add-host", 0, &(cmdargs).custom_conf.extra_hosts, \ + "Add a custom host-to-IP mapping (host:ip)", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "dns", 0, &(cmdargs).custom_conf.dns, \ + "Set custom DNS servers", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "dns-opt", 0, &(cmdargs).custom_conf.dns_options, \ + "Set DNS options", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "dns-search", 0, &(cmdargs).custom_conf.dns_search, \ + "Set custom DNS search domains", command_append_array }, \ + { CMD_OPT_TYPE_STRING, false, "user-remap", 0, &(cmdargs).custom_conf.user_remap, \ + "Set user remap for container", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "ipc", 0, &(cmdargs).custom_conf.share_ns[NAMESPACE_IPC], \ + "IPC namespace to use", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "shm-size", 0, &(cmdargs).custom_conf.shm_size, \ + "Size of /dev/shm, default value is 64MB", command_convert_membytes }, \ + { CMD_OPT_TYPE_CALLBACK, false, "kernel-memory", 0, &(cmdargs).cr.kernel_memory_limit, \ + "Kernel memory limit", command_convert_membytes }, \ + { CMD_OPT_TYPE_CALLBACK, false, "hugetlb-limit", 0, &(cmdargs).custom_conf.hugepage_limits, \ + "Huge page limit (format: [size:], e.g. --hugetlb-limit 2MB:32MB)", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "log-opt", 0, &(cmdargs), \ + "Container log options, value formate: key=value", callback_log_opt }, \ + { CMD_OPT_TYPE_CALLBACK, false, "memory", 'm', &(cmdargs).cr.memory_limit, \ + "Memory limit", command_convert_membytes }, \ + { CMD_OPT_TYPE_CALLBACK, false, "memory-reservation", 0, &(cmdargs).cr.memory_reservation, \ + "Memory soft limit", command_convert_membytes }, \ + { CMD_OPT_TYPE_CALLBACK, false, "memory-swap", 0, &(cmdargs).cr.memory_swap, \ + "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", command_convert_memswapbytes }, \ + { CMD_OPT_TYPE_CALLBACK, false, "mount", 0, &(cmdargs).custom_conf.mounts, \ + "Attach a filesystem mount to the service", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "group-add", 0, &(cmdargs).custom_conf.group_add, \ + "Add additional groups to join", command_append_array }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "name", 'n', &(cmdargs).name, "Name of the container", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "net", 0, &(cmdargs).custom_conf.share_ns[NAMESPACE_NET], \ + "Connect a container to a network", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "pid", 0, &(cmdargs).custom_conf.share_ns[NAMESPACE_PID], \ + "PID namespace to use", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "pids-limit", 0, &(cmdargs).custom_conf.pids_limit, \ + "Tune container pids limit (set -1 for unlimited)", command_convert_llong }, \ + { CMD_OPT_TYPE_BOOL, false, "privileged", 0, &(cmdargs).custom_conf.privileged, \ + "Give extended privileges to this container", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "tty", 't', &(cmdargs).custom_conf.tty, "Allocate a pseudo-TTY", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "restart", 0, &(cmdargs).restart, \ + "Restart policy to apply when a container exits(no, always, on-reboot, on-failure[:max-retries])", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "host-channel", 0, &(cmdargs).host_channel, \ + "Create share memory between host and container", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "runtime", 'R', &(cmdargs).runtime, \ + "Runtime to use for containers(default: lcr)", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "user", 'u', &(cmdargs).custom_conf.user, \ + "Username or UID (format: [:])", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "uts", 0, &(cmdargs).custom_conf.share_ns[NAMESPACE_UTS], \ + "UTS namespace to use", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "volume", 'v', &(cmdargs).custom_conf.volumes, \ + "Bind mount a volume", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "annotation", 0, &(cmdargs), \ + "Set annotations on a container", callback_annotation }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "workdir", 0, &(cmdargs).custom_conf.workdir, \ + "Working directory inside the container", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "system-container", 0, &(cmdargs).custom_conf.system_container, \ + "Extend some features only needed by running system container", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "oom-kill-disable", 0, &(cmdargs).custom_conf.oom_kill_disable, \ + "Disable OOM Killer", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "security-opt", 0, &(cmdargs).custom_conf.security, \ + "Security Options (default [])", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "storage-opt", 0, &(cmdargs).custom_conf.storage_opts, \ + "Storage driver options for the container", command_append_array }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "health-cmd", 0, &(cmdargs).custom_conf.health_cmd, \ + "Command to run to check health", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "sysctl", 0, &(cmdargs).custom_conf.sysctls, \ + "Sysctl options", command_append_array }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "env-target-file", 0, &(cmdargs).custom_conf.env_target_file, \ + "Export env to target file path in rootfs", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "cgroup-parent", 0, &(cmdargs).custom_conf.cgroup_parent, \ + "Optional parent cgroup for the container", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "health-interval", 0, &(cmdargs).custom_conf.health_interval, \ + "Time between running the check (ms|s|m|h) (default 30s)", command_convert_nanoseconds }, \ + { CMD_OPT_TYPE_CALLBACK, false, "health-retries", 0, &(cmdargs).custom_conf.health_retries, \ + "Consecutive failures needed to report unhealthy (default 3)", command_convert_int }, \ + { CMD_OPT_TYPE_CALLBACK, false, "health-timeout", 0, &(cmdargs).custom_conf.health_timeout, \ + "Maximum time to allow one check to run (ms|s|m|h) (default 30s)", command_convert_nanoseconds }, \ + { CMD_OPT_TYPE_CALLBACK, false, "health-start-period", 0, &(cmdargs).custom_conf.health_start_period, \ + "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) " \ + "(default 0s)", command_convert_nanoseconds }, \ + { CMD_OPT_TYPE_BOOL, false, "no-healthcheck", 0, &(cmdargs).custom_conf.no_healthcheck, \ + "Disable any container-specified HEALTHCHECK", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "health-exit-on-unhealthy", 0, &(cmdargs).custom_conf.exit_on_unhealthy, \ + "Kill the container when it is detected to be unhealthy", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "ns-change-opt", 0, &(cmdargs).custom_conf.ns_change_opt, \ + "Namespaced kernel param options for system container (default [])", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "ulimit", 0, &(cmdargs).custom_conf.ulimits, \ + "Ulimit options (default [])", command_append_array } + +#define CREATE_EXTEND_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "interactive", 'i', &(cmdargs).custom_conf.open_stdin, \ + "Keep STDIN open even if not attached", NULL } + +extern const char g_cmd_create_desc[]; +extern const char g_cmd_create_usage[]; +extern struct client_arguments g_cmd_create_args; + +int create_parser(struct client_arguments *args, int c, char *arg); + +int create_checker(struct client_arguments *args); + +int client_create(struct client_arguments *args); + +int callback_log_opt(command_option_t *option, const char *value); + +int callback_annotation(command_option_t *option, const char *value); + +int cmd_create_main(int argc, const char **argv); + +#endif /* __CMD_CREATE_H */ diff --git a/src/cmd/lcrc/base/kill.c b/src/cmd/lcrc/base/kill.c new file mode 100644 index 0000000..de34ed0 --- /dev/null +++ b/src/cmd/lcrc/base/kill.c @@ -0,0 +1,139 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container kill functions + ******************************************************************************/ +#include "error.h" +#include "securec.h" +#include "arguments.h" +#include "kill.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_kill_desc[] = "Kill one or more running containers"; +const char g_cmd_kill_usage[] = "kill [OPTIONS] CONTAINER [CONTAINER...]"; + +struct client_arguments g_cmd_kill_args = { + .signal = "SIGKILL", +}; + +static int client_kill(const struct client_arguments *args) +{ + int ret = 0; + int signal = -1; + lcrc_connect_ops *ops = NULL; + struct lcrc_kill_request request = { 0 }; + struct lcrc_kill_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(struct lcrc_kill_response)); + if (response == NULL) { + ERROR("Kill: Out of memory"); + return -1; + } + + request.name = args->name; + + signal = util_sig_parse(args->signal); + if (signal < 0) { + ERROR("Invalid signal number"); + ret = -1; + goto out; + } + request.signal = (uint32_t)signal; + + ops = get_connect_client_ops(); + if (ops == NULL || ops->container.kill == NULL) { + ERROR("Unimplemented kill op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.kill(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + goto out; + } + +out: + lcrc_kill_response_free(response); + return ret; +} + +int cmd_kill_main(int argc, const char **argv) +{ + int signo; + int i = 0; + int status = 0; + command_t cmd; + struct log_config lconf = { 0 }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_kill_args)) { + COMMAND_ERROR("client arguments init failed\n"); + exit(ECOMMON); + } + g_cmd_kill_args.progname = argv[0]; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_kill_args), + KILL_OPTIONS(g_cmd_kill_args) + }; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_kill_desc, + g_cmd_kill_usage); + if (command_parse_args(&cmd, &g_cmd_kill_args.argc, &g_cmd_kill_args.argv)) { + exit(ECOMMON); + } + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed\n"); + exit(ECOMMON); + } + + signo = util_sig_parse(g_cmd_kill_args.signal); + if (signo == -1) { + COMMAND_ERROR("Invalid signal: %s", g_cmd_kill_args.signal); + exit(ECOMMON); + } + + if (!util_valid_signal(signo)) { + COMMAND_ERROR("The Linux daemon does not support signal %d", signo); + exit(ECOMMON); + } + + if (g_cmd_kill_args.argc == 0) { + COMMAND_ERROR("Kill requires at least 1 container names"); + exit(EINVALIDARGS); + } + + if (g_cmd_kill_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to kill."); + exit(EINVALIDARGS); + } + + for (i = 0; i < g_cmd_kill_args.argc; i++) { + g_cmd_kill_args.name = g_cmd_kill_args.argv[i]; + if (client_kill(&g_cmd_kill_args)) { + ERROR("Container \"%s\" kill failed", g_cmd_kill_args.name); + status = -1; + continue; + } + + printf("%s\n", g_cmd_kill_args.name); + } + + if (status) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/base/kill.h b/src/cmd/lcrc/base/kill.h new file mode 100644 index 0000000..fbb1edd --- /dev/null +++ b/src/cmd/lcrc/base/kill.h @@ -0,0 +1,29 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container kill definition + ******************************************************************************/ +#ifndef __CMD_KILL_H +#define __CMD_KILL_H + +#include "arguments.h" +#include "wait.h" + +#define KILL_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "signal", 's', &(cmdargs).signal, \ + "Signal to send to the container (default \"SIGKILL\")", NULL } + +extern const char g_cmd_kill_desc[]; +extern const char g_cmd_kill_usage[]; +extern struct client_arguments g_cmd_kill_args; +int cmd_kill_main(int argc, const char **argv); +#endif diff --git a/src/cmd/lcrc/base/rename.c b/src/cmd/lcrc/base/rename.c new file mode 100644 index 0000000..a2857fc --- /dev/null +++ b/src/cmd/lcrc/base/rename.c @@ -0,0 +1,98 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container stop functions + ******************************************************************************/ +#include "rename.h" +#include "securec.h" +#include "arguments.h" +#include "log.h" +#include "utils.h" +#include "lcrc_connect.h" + +const char g_cmd_rename_desc[] = "Rename a container"; +const char g_cmd_rename_usage[] = + "rename [OPTIONS] OLD_NAME NEW_NAME"; + +struct client_arguments g_cmd_rename_args = { 0 }; + +static int client_rename(const struct client_arguments *args) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_rename_request request = { 0 }; + struct lcrc_rename_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(*response)); + if (response == NULL) { + ERROR("Stop: Out of memory"); + return -1; + } + + request.old_name = args->argv[0]; + request.new_name = args->argv[1]; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.rename) { + ERROR("Unimplemented stop op"); + ret = -1; + goto out; + } + config = get_connect_config(args); + ret = ops->container.rename(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } +out: + lcrc_rename_response_free(response); + return ret; +} + +int cmd_rename_main(int argc, const char **argv) +{ + struct log_config lconf = {0}; + command_t cmd; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_rename_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_rename_args)) { + COMMAND_ERROR("client arguments init failed\n"); + exit(ECOMMON); + } + g_cmd_rename_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), + argc, + (const char **)argv, g_cmd_rename_desc, g_cmd_rename_usage); + if (command_parse_args(&cmd, &g_cmd_rename_args.argc, &g_cmd_rename_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed\n"); + exit(ECOMMON); + } + + if (g_cmd_rename_args.argc != 2) { + COMMAND_ERROR("\"rename\" requires 2 arguments."); + exit(ECOMMON); + } + + if (client_rename(&g_cmd_rename_args)) { + ERROR("Container \"%s\" rename failed", g_cmd_rename_args.argv[0]); + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/base/rename.h b/src/cmd/lcrc/base/rename.h new file mode 100644 index 0000000..f09f271 --- /dev/null +++ b/src/cmd/lcrc/base/rename.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container rename definition + ******************************************************************************/ +#ifndef __CMD_RENAME_H +#define __CMD_RENAME_H + +#include "arguments.h" + +extern const char g_cmd_rename_desc[]; +extern const char g_cmd_rename_usage[]; +extern struct client_arguments g_cmd_rename_args; +int cmd_rename_main(int argc, const char **argv); + +#endif diff --git a/src/cmd/lcrc/base/restart.c b/src/cmd/lcrc/base/restart.c new file mode 100644 index 0000000..0ec9018 --- /dev/null +++ b/src/cmd/lcrc/base/restart.c @@ -0,0 +1,119 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restart functions + ******************************************************************************/ +#include +#include + +#include "restart.h" +#include "arguments.h" +#include "log.h" +#include "utils.h" +#include "lcrc_connect.h" + +const char g_cmd_restart_desc[] = "Restart one or more containers"; +const char g_cmd_restart_usage[] = "restart [OPTIONS] CONTAINER [CONTAINER...]"; + +struct client_arguments g_cmd_restart_args = { + .force = false, + .time = 10, +}; + +static int client_restart(const struct client_arguments *args) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_restart_request request = { 0 }; + struct lcrc_restart_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(struct lcrc_restart_response)); + if (response == NULL) { + ERROR("Out of memory"); + return -1; + } + request.name = args->name; + request.timeout = (unsigned int)args->time; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.restart) { + ERROR("Unimplemented ops"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.restart(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } +out: + lcrc_restart_response_free(response); + return ret; +} + +int cmd_restart_main(int argc, const char **argv) +{ + int i = 0; + int status = 0; + command_t cmd; + struct log_config lconf = { 0 }; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_restart_args), + RESTART_OPTIONS(g_cmd_restart_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_restart_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_restart_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_restart_desc, + g_cmd_restart_usage); + + if (command_parse_args(&cmd, &g_cmd_restart_args.argc, &g_cmd_restart_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Restart: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_restart_args.argc == 0) { + COMMAND_ERROR("Restart requires at least 1 container names"); + exit(EINVALIDARGS); + } + + if (g_cmd_restart_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to restart."); + exit(EINVALIDARGS); + } + + for (i = 0; i < g_cmd_restart_args.argc; i++) { + g_cmd_restart_args.name = g_cmd_restart_args.argv[i]; + if (client_restart(&g_cmd_restart_args)) { + status = -1; + continue; + } + + printf("%s\n", g_cmd_restart_args.name); + } + + if (status != 0) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/base/restart.h b/src/cmd/lcrc/base/restart.h new file mode 100644 index 0000000..d226847 --- /dev/null +++ b/src/cmd/lcrc/base/restart.h @@ -0,0 +1,27 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restart definition + ******************************************************************************/ +#ifndef __CMD_RESTART_H +#define __CMD_RESTART_H + +#define RESTART_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_CALLBACK, false, "time", 't', &(cmdargs).time, \ + "Seconds to wait for stop before killing it (default 10)", command_convert_int } + +extern const char g_cmd_restart_desc[]; +extern const char g_cmd_restart_usage[]; +extern struct client_arguments g_cmd_restart_args; +int cmd_restart_main(int argc, const char **argv); + +#endif /* __CMD_RESTART_H */ diff --git a/src/cmd/lcrc/base/rm.c b/src/cmd/lcrc/base/rm.c new file mode 100644 index 0000000..c64464d --- /dev/null +++ b/src/cmd/lcrc/base/rm.c @@ -0,0 +1,158 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container remove functions + ******************************************************************************/ +#include +#include "rm.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" +#include "commands.h" +#include "console.h" +#include "utils.h" +#include "securec.h" + +const char g_cmd_delete_desc[] = "Remove one or more containers"; +const char g_cmd_delete_usage[] = "rm [OPTIONS] CONTAINER [CONTAINER...]"; + +struct client_arguments g_cmd_delete_args = { + .force = false, + .volume = false, +}; +/* +* Create a rm request message and call RPC +*/ +static int client_delete(const struct client_arguments *args) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_delete_request request = { 0 }; + struct lcrc_delete_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(struct lcrc_delete_response)); + if (response == NULL) { + ERROR("RM: Out of memory"); + return -1; + } + + request.name = args->name; + request.force = args->force; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.remove) { + ERROR("Unimplemented rm op"); + ret = -1; + goto out; + } + config = get_connect_config(args); + ret = ops->container.remove(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } + + if (response->name != NULL) { + g_cmd_delete_args.name = util_strdup_s(response->name); + } +out: + lcrc_delete_response_free(response); + return ret; +} + +static void do_delete_console_fifo(const char *name, const char *stdflag) +{ + int ret = 0; + char fifo_dir[PATH_MAX] = { 0 }; + char fifo_name[PATH_MAX] = { 0 }; + + ret = console_fifo_name(CLIENT_RUNDIR, name, stdflag, fifo_name, sizeof(fifo_name), fifo_dir, + sizeof(fifo_dir), false); + if (ret != 0) { + ERROR("Failed to get console fifo name."); + goto out; + } + + console_fifo_delete(fifo_name); + + if (util_recursive_rmdir(fifo_dir, 0)) { + ERROR("Failed to delete FIFO path:%s", fifo_dir); + } + +out: + return; +} + +static void delete_console_fifo(const char *name) +{ + do_delete_console_fifo(name, "in"); + do_delete_console_fifo(name, "out"); + do_delete_console_fifo(name, "err"); + + return; +} + +int cmd_delete_main(int argc, const char **argv) +{ + int i = 0; + bool status = false; + struct log_config lconf = { 0 }; + command_t cmd; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_delete_args), + DELETE_OPTIONS(g_cmd_delete_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_delete_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_delete_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_delete_desc, + g_cmd_delete_usage); + if (command_parse_args(&cmd, &g_cmd_delete_args.argc, &g_cmd_delete_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Rm: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_delete_args.argc == 0) { + COMMAND_ERROR("\"%s rm\" requires at least 1 argument(s).", g_cmd_delete_args.progname); + COMMAND_ERROR("See '%s rm --help'.", g_cmd_delete_args.progname); + exit(ECOMMON); + } + + if (g_cmd_delete_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to remove."); + exit(ECOMMON); + } + + for (i = 0; i < g_cmd_delete_args.argc; i++) { + g_cmd_delete_args.name = g_cmd_delete_args.argv[i]; + if (client_delete(&g_cmd_delete_args)) { + ERROR("Container \"%s\" rm failed", g_cmd_delete_args.name); + status = true; + continue; + } + delete_console_fifo(g_cmd_delete_args.name); + printf("%s\n", g_cmd_delete_args.name); + } + + if (status) { + exit(ECOMMON); + } + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/base/rm.h b/src/cmd/lcrc/base/rm.h new file mode 100644 index 0000000..8e95cfa --- /dev/null +++ b/src/cmd/lcrc/base/rm.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container remove definition + ******************************************************************************/ +#ifndef __CMD_DELETE_H +#define __CMD_DELETE_H + +#include "arguments.h" + +#define DELETE_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "force", 'f', &(cmdargs).force, \ + "Force the removal of a running container (uses SIGKILL)", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "volumes", 'v', &(cmdargs).volume, \ + "Remove the volumes associated with the container", NULL } + +extern const char g_cmd_delete_desc[]; +extern const char g_cmd_delete_usage[]; +extern struct client_arguments g_cmd_delete_args; +int cmd_delete_main(int argc, const char **argv); + +#endif /* __CMD_DELETE_H */ diff --git a/src/cmd/lcrc/base/run.c b/src/cmd/lcrc/base/run.c new file mode 100644 index 0000000..bf5eafd --- /dev/null +++ b/src/cmd/lcrc/base/run.c @@ -0,0 +1,219 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container run functions + ******************************************************************************/ +#include +#include "run.h" +#include "arguments.h" +#include "log.h" +#include "utils.h" +#include "lcrc_connect.h" +#include "console.h" +#include "error.h" + +const char g_cmd_run_desc[] = "Run a command in a new container"; +const char g_cmd_run_usage[] = "run [OPTIONS] ROOTFS|IMAGE [COMMAND] [ARG...]"; +static int run_checker(struct client_arguments *args); +struct client_arguments g_cmd_run_args = { + .runtime = "lcr", + .restart = "no", + .log_file = NULL, + .log_file_size = "1MB", + .log_file_rotate = 7, +}; + +static int local_cmd_start(struct client_arguments *args, uint32_t *exit_code) +{ + int ret = 0; + bool reset_tty = false; + struct termios oldtios; + struct command_fifo_config *console_fifos = NULL; + + ret = client_start(&g_cmd_run_args, &reset_tty, &oldtios, &console_fifos); + if (ret != 0) { + goto free_out; + } + + if (!g_cmd_run_args.detach) { + ret = client_wait(&g_cmd_run_args, exit_code); + if (ret != 0) { + goto free_out; + } + ret = (int)(*exit_code); + } + + client_wait_fifo_exit(&g_cmd_run_args); +free_out: + client_restore_console(reset_tty, &oldtios, console_fifos); + return ret; +} + +static int remote_cmd_start_set_tty(const struct client_arguments *args, bool *reset_tty, struct termios *oldtios) +{ + int istty = 0; + + istty = isatty(0); + if (istty && args->custom_conf.tty && args->custom_conf.attach_stdin) { + if (setup_tios(0, oldtios)) { + ERROR("Failed to setup terminal properties"); + return -1; + } + *reset_tty = true; + } + return 0; +} + +static int remote_cmd_start(const struct client_arguments *args, uint32_t *exit_code) +{ + int ret = 0; + bool reset_tty = false; + lcrc_connect_ops *ops = NULL; + struct lcrc_start_request request = { 0 }; + struct lcrc_start_response *response = NULL; + client_connect_config_t config = { 0 }; + struct termios oldtios; + + ops = get_connect_client_ops(); + if (ops == NULL || ops->container.remote_start == NULL) { + ERROR("Unimplemented ops"); + ret = ECOMMON; + goto out; + } + + if (remote_cmd_start_set_tty(args, &reset_tty, &oldtios) != 0) { + ret = ECOMMON; + goto out; + } + + request.name = args->name; + request.attach_stdin = args->custom_conf.attach_stdin; + request.attach_stdout = args->custom_conf.attach_stdout; + request.attach_stderr = args->custom_conf.attach_stderr; + response = util_common_calloc_s(sizeof(struct lcrc_start_response)); + if (response == NULL) { + ERROR("Out of memory"); + ret = ECOMMON; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.remote_start(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = ECOMMON; + if (response->server_errono || + (response->errmsg && !strcmp(response->errmsg, errno_to_error_message(LCRD_ERR_CONNECT)))) { + ret = ESERVERERROR; + } + goto out; + } + +out: + lcrc_start_response_free(response); + if (reset_tty && tcsetattr(0, TCSAFLUSH, &oldtios) < 0) { + ERROR("Failed to reset terminal properties"); + return -1; + } + return ret; +} + +int cmd_run_main(int argc, const char **argv) +{ + int ret = 0; + unsigned int exit_code = 0; + command_t cmd = { 0 }; + struct log_config lconf = { 0 }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_run_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_run_args.custom_conf.attach_stdout = true; + g_cmd_run_args.custom_conf.attach_stderr = true; + + g_cmd_run_args.progname = argv[0]; + g_cmd_run_args.subcommand = argv[1]; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_run_args), + CREATE_OPTIONS(g_cmd_run_args), + CREATE_EXTEND_OPTIONS(g_cmd_run_args), + RUN_OPTIONS(g_cmd_run_args) + }; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_run_desc, + g_cmd_run_usage); + if (command_parse_args(&cmd, &g_cmd_run_args.argc, &g_cmd_run_args.argv) || run_checker(&g_cmd_run_args)) { + exit(EINVALIDARGS); + } + + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed"); + exit(ECOMMON); + } + + ret = client_create(&g_cmd_run_args); + if (ret) { + ERROR("Container \"%s\" create failed", g_cmd_run_args.name); + exit(ret); + } + + if (g_cmd_run_args.detach) { + printf("%s\n", g_cmd_run_args.name); + } + + if (strncmp(g_cmd_run_args.socket, "tcp://", strlen("tcp://")) == 0) { + ret = remote_cmd_start(&g_cmd_run_args, &exit_code); + if (ret != 0) { + ERROR("Failed to execute command with remote run"); + goto free_out; + } + } else { + ret = local_cmd_start(&g_cmd_run_args, &exit_code); + if (ret != 0) { + ERROR("Failed to execute command with local run"); + goto free_out; + } + } + +free_out: + client_arguments_free(&g_cmd_run_args); + exit(ret); +} + +static int run_checker(struct client_arguments *args) +{ + int ret = 0; + + ret = create_checker(args); + if (ret) { + goto out; + } + + /* Make detach option a high priority than terminal*/ + if (args->detach) { + args->custom_conf.attach_stdin = false; + args->custom_conf.attach_stdout = false; + args->custom_conf.attach_stderr = false; + } + + if (args->custom_conf.auto_remove && ((args->restart != NULL) && (strcmp("no", args->restart) != 0))) { + COMMAND_ERROR("Conflicting options: --restart and --rm"); + ret = -1; + goto out; + } + +out: + return ret; +} + diff --git a/src/cmd/lcrc/base/run.h b/src/cmd/lcrc/base/run.h new file mode 100644 index 0000000..2b4edbc --- /dev/null +++ b/src/cmd/lcrc/base/run.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container run definition + ******************************************************************************/ +#ifndef __CMD_RUN_H +#define __CMD_RUN_H + +#include "create.h" +#include "start.h" +#include "wait.h" + +#define RUN_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "detach", 'd', &(cmdargs).detach, \ + "Run container in background and print container ID", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "rm", 0, &(cmdargs).custom_conf.auto_remove, \ + "Automatically remove the container when it exits", NULL } + +extern const char g_cmd_run_desc[]; +extern const char g_cmd_run_usage[]; +extern struct client_arguments g_cmd_run_args; +int cmd_run_main(int argc, const char **argv); + +#endif /* __CMD_RUN_H */ diff --git a/src/cmd/lcrc/base/start.c b/src/cmd/lcrc/base/start.c new file mode 100644 index 0000000..8a293e7 --- /dev/null +++ b/src/cmd/lcrc/base/start.c @@ -0,0 +1,246 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide container start functions + ******************************************************************************/ +#include +#include +#include +#include +#include + +#include "error.h" +#include "securec.h" +#include "arguments.h" +#include "commander.h" +#include "start.h" +#include "log.h" +#include "lcrc_connect.h" +#include "console.h" +#include "utils.h" +#include "commands.h" + +const char g_cmd_start_desc[] = "Start one or more stopped containers"; +const char g_cmd_start_usage[] = "start [OPTIONS] CONTAINER [CONTAINER...]"; + +sem_t g_console_waitopen_sem; +sem_t g_console_waitexit_sem; + +struct client_arguments g_cmd_start_args = {}; + +static int start_cmd_init(const struct client_arguments *args) +{ + if (sem_init(&g_console_waitopen_sem, 0, 0)) { + ERROR("Container %s Semaphore initialization failed", args->name); + return ECOMMON; + } + + if (sem_init(&g_console_waitexit_sem, 0, 0)) { + ERROR("Container %s Semaphore initialization failed", args->name); + sem_destroy(&g_console_waitopen_sem); + return ECOMMON; + } + + return 0; +} + +static int start_prepare_console(const struct client_arguments *args, struct termios *oldtios, bool *reset_tty, + struct command_fifo_config **console_fifos) +{ + int ret = 0; + int istty = 0; + + istty = isatty(0); + if (istty && args->custom_conf.tty && args->custom_conf.attach_stdin) { + if (setup_tios(0, oldtios)) { + ERROR("Failed to setup terminal properties"); + ret = ECOMMON; + goto out; + } + *reset_tty = true; + } + if (!istty) { + INFO("The input device is not a TTY"); + } + + if (args->custom_conf.attach_stdin || args->custom_conf.attach_stdout || args->custom_conf.attach_stderr) { + if (create_console_fifos(args->custom_conf.attach_stdin, args->custom_conf.attach_stdout, + args->custom_conf.attach_stderr, args->name, "start", console_fifos)) { + ERROR("Container \"%s\" create console FIFO failed", args->name); + ret = ECOMMON; + goto out; + } + + (*console_fifos)->wait_open = &g_console_waitopen_sem; + (*console_fifos)->wait_exit = &g_console_waitexit_sem; + if (start_client_console_thread(*console_fifos, args->custom_conf.tty)) { + ERROR("Container \"%s\" start console thread failed", args->name); + ret = ECOMMON; + goto out; + } + } + +out: + return ret; +} + +static int do_client_start(const struct client_arguments *args, struct command_fifo_config **console_fifos) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_start_request request = { 0 }; + struct lcrc_start_response *response = NULL; + client_connect_config_t config = { 0 }; + + ops = get_connect_client_ops(); + if (ops == NULL || ops->container.start == NULL) { + ERROR("Unimplemented ops"); + ret = ECOMMON; + goto out; + } + + request.name = args->name; + if (console_fifos != NULL && *console_fifos != NULL) { + request.stdin = (*console_fifos)->stdin_name; + request.stdout = (*console_fifos)->stdout_name; + request.stderr = (*console_fifos)->stderr_name; + } + request.attach_stdin = args->custom_conf.attach_stdin; + request.attach_stdout = args->custom_conf.attach_stdout; + request.attach_stderr = args->custom_conf.attach_stderr; + + response = util_common_calloc_s(sizeof(struct lcrc_start_response)); + if (response == NULL) { + ERROR("Out of memory"); + ret = ECOMMON; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.start(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + if (response->server_errono || + (response->errmsg && !strcmp(response->errmsg, errno_to_error_message(LCRD_ERR_CONNECT)))) { + ret = ESERVERERROR; + util_contain_errmsg(response->errmsg, &ret); + } else { + ret = ECOMMON; + } + goto out; + } + +out: + lcrc_start_response_free(response); + response = NULL; + return ret; +} + +/* +* Create a create request message and call RPC +*/ +int client_start(const struct client_arguments *args, bool *reset_tty, struct termios *oldtios, + struct command_fifo_config **console_fifos) +{ + int ret = 0; + + ret = start_cmd_init(args); + if (ret != 0) { + return ret; + } + + if (oldtios != NULL && console_fifos != NULL && reset_tty != NULL) { + ret = start_prepare_console(args, oldtios, reset_tty, console_fifos); + if (ret != 0) { + goto out; + } + } + + ret = do_client_start(args, console_fifos); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +void client_wait_fifo_exit(const struct client_arguments *args) +{ + if (args->custom_conf.attach_stdin || args->custom_conf.attach_stdout || args->custom_conf.attach_stderr) { + sem_wait(&g_console_waitexit_sem); + } +} + +void client_restore_console(bool reset_tty, const struct termios *oldtios, struct command_fifo_config *console_fifos) +{ + if (reset_tty && tcsetattr(0, TCSAFLUSH, oldtios) < 0) { + WARN("Failed to reset terminal properties: %s.", strerror(errno)); + } + free_command_fifo_config(console_fifos); + sem_destroy(&g_console_waitopen_sem); + sem_destroy(&g_console_waitexit_sem); +} + +int cmd_start_main(int argc, const char **argv) +{ + int ret = 0; + int i = 0; + struct log_config lconf = { 0 }; + command_t cmd; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_start_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_start_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_start_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_start_desc, + g_cmd_start_usage); + if (command_parse_args(&cmd, &g_cmd_start_args.argc, &g_cmd_start_args.argv)) { + exit(EINVALIDARGS); + } + + if (log_init(&lconf)) { + COMMAND_ERROR("Start: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_start_args.argc == 0) { + COMMAND_ERROR("\"start\" requires at least 1 argument"); + exit(EINVALIDARGS); + } + + if (g_cmd_start_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to start."); + exit(EINVALIDARGS); + } + + for (i = 0; i < g_cmd_start_args.argc; i++) { + g_cmd_start_args.name = g_cmd_start_args.argv[i]; + if (client_start(&g_cmd_start_args, NULL, NULL, NULL)) { + ERROR("Container \"%s\" start failed", g_cmd_start_args.name); + ret = ECOMMON; + continue; + } + if (g_cmd_start_args.detach) { + printf("Container \"%s\" started\n", g_cmd_start_args.name); + } + } + + return ret; +} + diff --git a/src/cmd/lcrc/base/start.h b/src/cmd/lcrc/base/start.h new file mode 100644 index 0000000..e6c6575 --- /dev/null +++ b/src/cmd/lcrc/base/start.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide container start definition + ******************************************************************************/ +#ifndef __CMD_START_H +#define __CMD_START_H + +#include "arguments.h" +#include "commands.h" +#include + +extern const char g_cmd_start_desc[]; +extern struct client_arguments g_cmd_start_args; + +void client_wait_fifo_exit(const struct client_arguments *args); +void client_restore_console(bool reset_tty, const struct termios *oldtios, struct command_fifo_config *console_fifos); + +int client_start(const struct client_arguments *args, bool *reset_tty, struct termios *oldtios, + struct command_fifo_config **console_fifos); +int cmd_start_main(int argc, const char **argv); +#endif /* __CMD_START_H */ diff --git a/src/cmd/lcrc/base/stop.c b/src/cmd/lcrc/base/stop.c new file mode 100644 index 0000000..241b516 --- /dev/null +++ b/src/cmd/lcrc/base/stop.c @@ -0,0 +1,123 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container stop functions + ******************************************************************************/ +#include "stop.h" +#include "securec.h" +#include "arguments.h" +#include "log.h" +#include "utils.h" +#include "lcrc_connect.h" + +const char g_cmd_stop_desc[] = "Stop one or more containers"; +const char g_cmd_stop_usage[] = "stop [OPTIONS] CONTAINER [CONTAINER...]"; + +struct client_arguments g_cmd_stop_args = { + .force = false, + .time = 10, +}; + +/* + * Create a stop request message and call RPC + */ +static int client_stop(const struct client_arguments *args) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_stop_request request = { 0 }; + struct lcrc_stop_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(struct lcrc_stop_response)); + if (response == NULL) { + ERROR("Stop: Out of memory"); + return -1; + } + + request.name = args->name; + request.force = args->force; + request.timeout = args->time; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.stop) { + ERROR("Unimplemented stop op"); + ret = -1; + goto out; + } + config = get_connect_config(args); + ret = ops->container.stop(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } +out: + lcrc_stop_response_free(response); + return ret; +} + +int cmd_stop_main(int argc, const char **argv) +{ + int i = 0; + int status = 0; + struct log_config lconf = { 0 }; + command_t cmd; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_stop_args), + STOP_OPTIONS(g_cmd_stop_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_stop_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_stop_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_stop_desc, + g_cmd_stop_usage); + if (command_parse_args(&cmd, &g_cmd_stop_args.argc, &g_cmd_stop_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed"); + exit(ECOMMON); + } + + if (g_cmd_stop_args.force) { + g_cmd_stop_args.time = 0; + } + + if (g_cmd_stop_args.argc == 0) { + COMMAND_ERROR("Stop requires minimum of 1 container name"); + exit(EINVALIDARGS); + } + + if (g_cmd_stop_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to stop."); + exit(EINVALIDARGS); + } + + for (i = 0; i < g_cmd_stop_args.argc; i++) { + g_cmd_stop_args.name = g_cmd_stop_args.argv[i]; + if (client_stop(&g_cmd_stop_args)) { + ERROR("Container \"%s\" stop failed", g_cmd_stop_args.name); + status = -1; + continue; + } + printf("%s\n", g_cmd_stop_args.name); + } + if (status) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/base/stop.h b/src/cmd/lcrc/base/stop.h new file mode 100644 index 0000000..c389a1b --- /dev/null +++ b/src/cmd/lcrc/base/stop.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container stop definition + ******************************************************************************/ +#ifndef __CMD_STOP_H +#define __CMD_STOP_H + +#include "arguments.h" + +#define STOP_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "force", 'f', &(cmdargs).force, "Stop by force killing", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "time", 't', &(cmdargs).time, \ + "Seconds to wait for stop before killing it (default 10)", command_convert_int } + +extern const char g_cmd_stop_desc[]; +extern const char g_cmd_stop_usage[]; +extern struct client_arguments g_cmd_stop_args; + +int cmd_stop_main(int argc, const char **argv); + +#endif /* __CMD_STOP_H */ diff --git a/src/cmd/lcrc/commands.c b/src/cmd/lcrc/commands.c new file mode 100644 index 0000000..e81730b --- /dev/null +++ b/src/cmd/lcrc/commands.c @@ -0,0 +1,432 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container command functions + ******************************************************************************/ +#include "commands.h" + +#include +#include +#include +#include +#include +#include "securec.h" + +#include "arguments.h" +#include "config.h" +#include "log.h" +#include "utils.h" +#include "console.h" +#include "constants.h" + +static void send_msg_to_syslog(int argc, const char **argv) +{ + int nret = 0; + int fd = -1; + int i = 0; + bool found = false; + ssize_t len = 0; + pid_t ppid = -1; + char cmdline_path[PATH_MAX] = { 0 }; + char cmdline[MAX_BUFFER_SIZE + 1] = { 0 }; + char *msg = NULL; + const char *target_command[] = { "kill", "restart", "rm", "stop", NULL }; + + if (argc < 2) { + COMMAND_ERROR("Invalid arguments to send syslog"); + return; + } + for (; target_command[i] != NULL; i++) { + if (strcmp(argv[1], target_command[i]) == 0) { + found = true; + } + } + if (!found) { + return; + } + ppid = getppid(); + // get parent cmdline, "/proc/ppid/cmdline" + nret = sprintf_s(cmdline_path, PATH_MAX, "/proc/%d/cmdline", ppid); + if (nret < 0) { + COMMAND_ERROR("Get parent '%d' cmdline path failed", ppid); + return; + } + fd = util_open(cmdline_path, O_RDONLY, DEFAULT_SECURE_FILE_MODE); + if (fd < 0) { + COMMAND_ERROR("Open parent '%d' cmdline path failed", ppid); + return; + } + + len = util_read_nointr(fd, cmdline, MAX_BUFFER_SIZE); + if (len < 0) { + COMMAND_ERROR("Read cmdline failed"); + goto free_out; + } + msg = util_string_join(" ", argv, (size_t)argc); + if (msg == NULL) { + msg = util_strdup_s(argv[1]); + } + + openlog("isulad-client", LOG_PID, LOG_USER); + syslog(LOG_DEBUG, "received command [%s] from parent [%d] cmdline [%s]", msg, ppid, cmdline); + closelog(); +free_out: + close(fd); + free(msg); +} + +static void print_version() +{ + printf("Version %s, commit %s\n", VERSION, LCRD_GIT_COMMIT); +} + +/* compare commands */ +int compare_commands(const void *s1, const void *s2) +{ + return strcmp((*(const struct command *)s1).name, (*(const struct command *)s2).name); +} + +const struct command *command_by_name(const struct command *cmds, const char * const name) +{ + size_t i = 0; + + if (cmds == NULL) { + return NULL; + } + + while (1) { + if (cmds[i].name == NULL) { + return NULL; + } + + if (strcmp(cmds[i].name, name) == 0) { + return cmds + i; + } + + ++i; + } +} + +// Default help command if implementation doesn't provide one +int command_default_help(const char * const program_name, struct command *commands, int argc, const char **argv) +{ + const struct command *command = NULL; + + if (commands == NULL) { + return 1; + } + + if (argc == 0) { + size_t i = 0; + size_t max_size = 0; + printf("USAGE:\n"); + printf("\t%s [args...]\n", program_name); + printf("\n"); + printf("COMMANDS:\n"); + for (i = 0; commands[i].name != NULL; i++) { + size_t cmd_size = strlen(commands[i].name); + if (cmd_size > max_size) { + max_size = cmd_size; + } + } + qsort(commands, i, sizeof(commands[0]), compare_commands); + for (i = 0; commands[i].name != NULL; i++) { + printf("\t%*s\t%s\n", -(int)max_size, commands[i].name, commands[i].description); + } + + printf("\n"); + print_common_help(); + return 0; + } else if (argc > 1) { + printf("%s: unrecognized argument: \"%s\"\n", program_name, argv[1]); + return 1; + } + + command = command_by_name(commands, argv[0]); + + if (command == NULL) { + printf("%s: sub-command \"%s\" not found\n", program_name, argv[0]); + printf("run `lcrc --help` for a list of sub-commands\n"); + return 1; + } + + if (command->longdesc != NULL) { + printf("%s\n", command->longdesc); + } + return 0; +} + +/* run command */ +int run_command(struct command *commands, int argc, const char **argv) +{ + const struct command *command = NULL; + + if (argc == 1) { + return command_default_help(argv[0], commands, argc - 1, (const char **)(argv + 1)); + } + + if (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { + // lcrc help command format: lcrc [-h|help|--help] args + return command_default_help(argv[0], commands, argc - 2, (const char **)(argv + 2)); + } + + if (strcmp(argv[1], "--version") == 0) { + print_version(); + return 0; + } + + command = command_by_name(commands, argv[1]); + if (command != NULL) { + send_msg_to_syslog(argc, argv); + return command->executor(argc, (const char **)argv); + } + + printf("%s: command \"%s\" not found\n", argv[0], argv[1]); + printf("run `%s --help` or `run -h` for a list of sub-commands\n", argv[0]); + return 1; +} + +/* free command fifo names */ +void free_command_fifo_config(struct command_fifo_config *fifos) +{ + if (fifos != NULL) { + if (fifos->stdin_path != NULL) { + free(fifos->stdin_path); + fifos->stdin_path = NULL; + } + if (fifos->stdout_path != NULL) { + free(fifos->stdout_path); + fifos->stdout_path = NULL; + } + if (fifos->stderr_path != NULL) { + free(fifos->stderr_path); + fifos->stderr_path = NULL; + } + if (fifos->stdin_name != NULL) { + free(fifos->stdin_name); + fifos->stdin_name = NULL; + } + if (fifos->stdout_name != NULL) { + free(fifos->stdout_name); + fifos->stdout_name = NULL; + } + if (fifos->stderr_name != NULL) { + free(fifos->stderr_name); + fifos->stderr_name = NULL; + } + free(fifos); + } +} + +/* delete command fifo */ +void delete_command_fifo(struct command_fifo_config *fifos) +{ + int ret; + + if (fifos == NULL) { + return; + } + + ret = console_fifo_delete(fifos->stdin_name); + if (ret) { + WARN("Delete fifo failed: %s", fifos->stdin_name); + } + ret = console_fifo_delete(fifos->stdout_name); + if (ret) { + WARN("Delete fifo failed: %s", fifos->stdout_name); + } + ret = console_fifo_delete(fifos->stderr_name); + if (ret) { + WARN("Delete fifo failed: %s", fifos->stderr_name); + } + ret = util_recursive_rmdir(fifos->stdin_path, 0); + if (ret) { + WARN("Remove directory failed: %s", fifos->stdin_path); + } + ret = util_recursive_rmdir(fifos->stdout_path, 0); + if (ret) { + WARN("Remove directory failed: %s", fifos->stdout_path); + } + ret = util_recursive_rmdir(fifos->stderr_path, 0); + if (ret) { + WARN("Remove directory failed: %s", fifos->stderr_path); + } + + free_command_fifo_config(fifos); +} + +static int do_create_console_fifo(const char *subpath, const char *stdflag, char **out_fifo_dir, char **out_fifo_name) +{ + int ret = 0; + char fifo_dir[PATH_MAX] = { 0 }; + char fifo_name[PATH_MAX] = { 0 }; + + ret = console_fifo_name(CLIENT_RUNDIR, subpath, stdflag, fifo_name, sizeof(fifo_name), fifo_dir, + sizeof(fifo_dir), true); + if (ret != 0) { + ERROR("Failed to get console fifo name."); + ret = -1; + goto out; + } + + if (console_fifo_create(fifo_name)) { + ERROR("Failed to create console fifo."); + ret = -1; + goto out; + } + + *out_fifo_dir = util_strdup_s(fifo_dir); + *out_fifo_name = util_strdup_s(fifo_name); + +out: + return ret; +} + +int create_console_fifos(bool attach_stdin, bool attach_stdout, bool attach_stderr, const char *name, const char *type, + struct command_fifo_config **pconsole_fifos) +{ + int ret = 0; + char subpath[PATH_MAX] = { 0 }; + struct command_fifo_config *fifos = NULL; + + fifos = util_common_calloc_s(sizeof(struct command_fifo_config)); + if (fifos == NULL) { + ERROR("Failed to malloc memory for FIFO names."); + return -1; + } + + ret = sprintf_s(subpath, sizeof(subpath), "%s/%s-%u-%u", name, type, (unsigned int)getpid(), + (unsigned int)pthread_self()); + if (ret < 0) { + ERROR("Path is too long"); + goto cleanup; + } + + if (attach_stdin) { + ret = do_create_console_fifo(subpath, "in", &fifos->stdin_path, &fifos->stdin_name); + if (ret != 0) { + goto cleanup; + } + INFO("FIFO:%s create for start success.", fifos->stdin_name); + } + + if (attach_stdout) { + ret = do_create_console_fifo(subpath, "out", &fifos->stdout_path, &fifos->stdout_name); + if (ret != 0) { + goto cleanup; + } + INFO("FIFO:%s create for start success.", fifos->stdout_name); + } + + if (attach_stderr) { + ret = do_create_console_fifo(subpath, "err", &fifos->stderr_path, &fifos->stderr_name); + if (ret != 0) { + goto cleanup; + } + INFO("FIFO:%s create for start success.", fifos->stderr_name); + } + + *pconsole_fifos = fifos; + return 0; + +cleanup: + console_fifo_delete(fifos->stdin_name); + console_fifo_delete(fifos->stdout_name); + console_fifo_delete(fifos->stderr_name); + free_command_fifo_config(fifos); + return -1; +} + +struct console_loop_thread_args { + struct command_fifo_config *fifo_config; + bool tty; +}; + +static void *client_console_loop_thread(void *arg) +{ + int ret = 0; + int fifoinfd = -1; + int fifooutfd = -1; + int fifoerrfd = -1; + const struct console_loop_thread_args *args = arg; + bool tty = args->tty; + struct command_fifo_config *fifo_config = args->fifo_config; + sem_t *wait_open = fifo_config->wait_open; + sem_t *wait_exit = fifo_config->wait_exit; + + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Start: set thread detach fail"); + goto err1; + } + + if (fifo_config->stdin_name) { + if (console_fifo_open_withlock(fifo_config->stdin_name, &fifoinfd, O_RDWR | O_NONBLOCK)) { + ERROR("Start: failed to open console fifo."); + goto err2; + } + INFO("FIFO:%s open success for start.", fifo_config->stdin_name); + } + + if (fifo_config->stdout_name) { + if (console_fifo_open(fifo_config->stdout_name, &fifooutfd, O_RDONLY | O_NONBLOCK)) { + ERROR("Failed to open console fifo."); + goto err2; + } + INFO("FIFO:%s open success for start.", fifo_config->stdout_name); + } + + if (fifo_config->stderr_name) { + if (console_fifo_open(fifo_config->stderr_name, &fifoerrfd, O_RDONLY | O_NONBLOCK)) { + ERROR("Start: failed to open console fifo."); + goto err2; + } + INFO("FIFO:%s open success for start.", fifo_config->stderr_name); + } + + sem_post(wait_open); + client_console_loop(0, 1, 2, fifoinfd, fifooutfd, fifoerrfd, 1, tty); + +err2: + if (fifoinfd >= 0) { + console_fifo_close(fifoinfd); + } + if (fifooutfd >= 0) { + console_fifo_close(fifooutfd); + } + if (fifoerrfd >= 0) { + console_fifo_close(fifoerrfd); + } +err1: + sem_post(wait_open); + sem_post(wait_exit); + return NULL; +} + +int start_client_console_thread(struct command_fifo_config *console_fifos, bool tty) +{ + int res = 0; + pthread_t a_thread; + struct console_loop_thread_args args; + + args.fifo_config = console_fifos; + args.tty = tty; + res = pthread_create(&a_thread, NULL, client_console_loop_thread, (void *)(&args)); + if (res != 0) { + CRIT("Thread creation failed"); + return -1; + } + + sem_wait(console_fifos->wait_open); + + return 0; +} diff --git a/src/cmd/lcrc/commands.h b/src/cmd/lcrc/commands.h new file mode 100644 index 0000000..b6dc103 --- /dev/null +++ b/src/cmd/lcrc/commands.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container commands definition + ******************************************************************************/ +#ifndef __COMMAND_H +#define __COMMAND_H + +#include "arguments.h" +#include + +#define CLIENT_RUNDIR "/var/run/lcrc" + +// A command is described by: +// @name: The name which should be passed as a second parameter +// @executor: The function that will be executed if the command +// matches. Receives the argc of the program minus two, and +// the rest os argv +// @description: Brief description, will show in help messages +// @longdesc: Long descripton to show when you run `help ` +struct command { + const char * const name; + int(*executor)(int, const char **); + const char * const description; + const char * const longdesc; + struct client_arguments *args; +}; + +struct command_fifo_config { + char *stdin_path; + char *stdout_path; + char *stderr_path; + char *stdin_name; + char *stdout_name; + char *stderr_name; + sem_t *wait_open; + sem_t *wait_exit; +}; + +int create_console_fifos(bool attach_stdin, bool attach_stdout, bool attach_stderr, const char *name, + const char *type, struct command_fifo_config **pconsole_fifos); + +int start_client_console_thread(struct command_fifo_config *console_fifos, bool tty); + +void free_command_fifo_config(struct command_fifo_config *fifos); + +void delete_command_fifo(struct command_fifo_config *fifos); + +// Gets a pointer to a command, to allow implementing custom behavior +// returns null if not found. +// +// NOTE: Command arrays must end in a command with all member is NULL +const struct command *command_by_name(const struct command *cmds, + const char * const name); + +// Default help command if implementation doesn't prvide one +int commmand_default_help(const char * const program_name, + struct command *commands, + int argc, + const char **argv); + +int run_command(struct command *commands, int argc, const char **argv); +#endif /* __COMMAND_H */ diff --git a/src/cmd/lcrc/extend/CMakeLists.txt b/src/cmd/lcrc/extend/CMakeLists.txt new file mode 100644 index 0000000..d416df6 --- /dev/null +++ b/src/cmd/lcrc/extend/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} lcrc_extend_srcs) + +set(LCRC_EXTEND_SRCS + ${lcrc_extend_srcs} + PARENT_SCOPE + ) diff --git a/src/cmd/lcrc/extend/events.c b/src/cmd/lcrc/extend/events.c new file mode 100644 index 0000000..974a58b --- /dev/null +++ b/src/cmd/lcrc/extend/events.c @@ -0,0 +1,172 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container events functions + ******************************************************************************/ +#include "error.h" +#include "events.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" +#include "securec.h" + +const char g_cmd_events_desc[] = "Get real time events from the server"; +const char g_cmd_events_usage[] = "events [command options]"; + +struct client_arguments g_cmd_events_args = { + .since = NULL, + .until = NULL, +}; + +static const char * const g_strtype[] = { + "EXIT", "STOPPED", "STARTING", "RUNNING", "STOPPING", "ABORTING", "FREEZING", + "FROZEN", "THAWED", "OOM", "CREATE", "START", "EXEC_ADDED", "PAUSED1", +}; + +static const char *lcrsta2str(container_events_type_t sta) +{ + if (sta > EVENTS_TYPE_PAUSED1) { + return NULL; + } + return g_strtype[sta]; +} + +static void print_events_callback(const container_events_format_t *event) +{ + char timebuffer[512] = { 0 }; + + if (event == NULL) { + return; + } + + printf("--------------------------------------------------\n"); + printf("%-15s %s\n", "Name:", event->id); + + if (get_time_buffer(&(event->timestamp), timebuffer, sizeof(timebuffer))) { + printf("%-15s %s\n", "Time:", timebuffer); + } else { + printf("%-15s %s\n", "Time:", "-"); + } + + if (event->has_type) { + printf("%-15s %s\n", "EventType:", lcrsta2str(event->type)); + } else { + printf("%-15s %s\n", "EventType:", "-"); + } + + if (event->has_pid) { + printf("%-15s %u\n", "Pid:", event->pid); + } else { + printf("%-15s %s\n", "Pid:", "-"); + } + + if (event->has_exit_status) { + printf("%-15s %u\n", "Exit_Status:", event->exit_status); + } else { + printf("%-15s %s\n", "Exit_Status:", "-"); + } +} + +/* +* Create a delete request message and call RPC +*/ +static int client_event(struct client_arguments *args) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_events_request request = { 0 }; + struct lcrc_events_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(struct lcrc_events_response)); + if (response == NULL) { + ERROR("Event: Out of memory"); + return -1; + } + + request.cb = print_events_callback; + request.id = args->name; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.events) { + ERROR("Unimplemented event op"); + ret = -1; + goto out; + } + + if (args->since && !get_timestamp(args->since, &request.since)) { + COMMAND_ERROR("Failed to get since timestamp"); + ret = -1; + goto out; + } + + if (args->until && !get_timestamp(args->until, &request.until)) { + COMMAND_ERROR("Failed to get until timestamp"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.events(&request, response, &config); + if (ret) { + COMMAND_ERROR("Failed to get container events, %s", + response->errmsg ? response->errmsg : errno_to_error_message(response->cc)); + } + +out: + lcrc_events_response_free(response); + return ret; +} + +int cmd_events_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + command_t cmd; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_events_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_events_args.progname = argv[0]; + struct command_option options[] = { + LOG_OPTIONS(lconf), + EVENTS_OPTIONS(g_cmd_events_args), + COMMON_OPTIONS(g_cmd_events_args) + }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_events_desc, + g_cmd_events_usage); + if (command_parse_args(&cmd, &g_cmd_events_args.argc, &g_cmd_events_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Events: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_events_args.socket == NULL) { + COMMAND_ERROR("Missing --host,-H option"); + exit(EINVALIDARGS); + } + + if (client_event(&g_cmd_events_args)) { + if (g_cmd_events_args.name != NULL) { + ERROR("Container \"%s\" event failed", g_cmd_events_args.name); + } else { + ERROR("Container events failed"); + } + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/extend/events.h b/src/cmd/lcrc/extend/events.h new file mode 100644 index 0000000..775e370 --- /dev/null +++ b/src/cmd/lcrc/extend/events.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container events definition + ******************************************************************************/ +#ifndef __CMD_EVENT_H +#define __CMD_EVENT_H + +#include "arguments.h" + +#define EVENTS_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "name", 'n', &(cmdargs).name, \ + "Name of the container", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "since", 'S', &(cmdargs).since, \ + "Show all events created since this timestamp", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "until", 'U', &(cmdargs).until, \ + "Show all events created until this timestamp", NULL } + +extern const char g_cmd_events_desc[]; +extern const char g_cmd_events_usage[]; +extern struct client_arguments g_cmd_events_args; +int cmd_events_main(int argc, const char **argv); + +#endif /* __CMD_EVENT_H */ diff --git a/src/cmd/lcrc/extend/export.c b/src/cmd/lcrc/extend/export.c new file mode 100644 index 0000000..17ffa09 --- /dev/null +++ b/src/cmd/lcrc/extend/export.c @@ -0,0 +1,133 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-04-04 + * Description: provide container export functions + ******************************************************************************/ +#include "export.h" +#include +#include "securec.h" +#include "utils.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_export_desc[] = "export container"; +const char g_cmd_export_usage[] = "export [command options] [ID|NAME]"; + +struct client_arguments g_cmd_export_args = {}; + +/* + * Create a export request message and call RPC + */ +static int client_export(const struct client_arguments *args) +{ + int ret = 0; + errno_t mret; + lcrc_connect_ops *ops = NULL; + struct lcrc_export_request request; + struct lcrc_export_response *response = NULL; + client_connect_config_t config = { 0 }; + + mret = memset_s(&request, sizeof(request), 0x00, sizeof(request)); + if (mret != EOK) { + ERROR("Failed to memset export request"); + return -1; + } + response = util_common_calloc_s(sizeof(struct lcrc_export_response)); + if (response == NULL) { + ERROR("Resume: Out of memory"); + return -1; + } + + request.name = args->name; + request.file = args->file; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.export_rootfs) { + ERROR("Unimplemented export op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.export_rootfs(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } +out: + lcrc_export_response_free(response); + return ret; +} + +int cmd_export_main(int argc, const char **argv) +{ + int i = 0; + char file[PATH_MAX] = { 0 }; + struct log_config lconf = { 0 }; + + lconf.name = argv[0]; + lconf.quiet = true; + lconf.driver = "stdout"; + lconf.file = NULL; + lconf.priority = "ERROR"; + if (log_init(&lconf)) { + COMMAND_ERROR("Export: log init failed"); + exit(ECOMMON); + } + + command_t cmd; + if (client_arguments_init(&g_cmd_export_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_export_args.progname = argv[0]; + struct command_option options[] = { COMMON_OPTIONS(g_cmd_export_args), EXPORT_OPTIONS(g_cmd_export_args) }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_export_desc, + g_cmd_export_usage); + if (command_parse_args(&cmd, &g_cmd_export_args.argc, &g_cmd_export_args.argv)) { + exit(EINVALIDARGS); + } + + if (g_cmd_export_args.argc != 1) { + COMMAND_ERROR("Export requires exactly 1 container name"); + exit(EINVALIDARGS); + } + + if (g_cmd_export_args.file == NULL) { + COMMAND_ERROR("Missing output file, use -o,--output option"); + exit(EINVALIDARGS); + } + + /* If it's not a absolute path, add cwd to be absolute path */ + if (g_cmd_export_args.file[0] != '/') { + char cwd[PATH_MAX] = { 0 }; + if (!getcwd(cwd, sizeof(cwd))) { + COMMAND_ERROR("get cwd failed:%s", strerror(errno)); + exit(ECOMMON); + } + + if (sprintf_s(file, sizeof(file), "%s/%s", cwd, g_cmd_export_args.file) < 0) { + COMMAND_ERROR("filename too long"); + exit(EINVALIDARGS); + } + g_cmd_export_args.file = file; + } + + g_cmd_export_args.name = g_cmd_export_args.argv[i]; + if (client_export(&g_cmd_export_args)) { + ERROR("Container \"%s\" export failed", g_cmd_export_args.name); + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/extend/export.h b/src/cmd/lcrc/extend/export.h new file mode 100644 index 0000000..2b2a4d6 --- /dev/null +++ b/src/cmd/lcrc/extend/export.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-04-04 + * Description: provide container resume definition + ******************************************************************************/ +#ifndef __CMD_EXPORT_H +#define __CMD_EXPORT_H + +#include "arguments.h" + +#define EXPORT_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "output", 'o', &(cmdargs).file, "Write to a file", NULL } + +extern const char g_cmd_export_desc[]; +extern const char g_cmd_export_usage[]; +extern struct client_arguments g_cmd_export_args; +int cmd_export_main(int argc, const char **argv); + +#endif diff --git a/src/cmd/lcrc/extend/pause.c b/src/cmd/lcrc/extend/pause.c new file mode 100644 index 0000000..47bba33 --- /dev/null +++ b/src/cmd/lcrc/extend/pause.c @@ -0,0 +1,118 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container pause functions + ******************************************************************************/ +#include "pause.h" +#include "securec.h" +#include "utils.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_pause_desc[] = "Pause all processes within one or more containers"; +const char g_cmd_pause_usage[] = "pause [OPTIONS] CONTAINER [CONTAINER...]"; + +struct client_arguments g_cmd_pause_args = {}; + +/* + * Create a pause request message and call RPC + */ +static int client_pause(const struct client_arguments *args) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_pause_request request = { 0 }; + struct lcrc_pause_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(struct lcrc_pause_response)); + if (response == NULL) { + ERROR("Pause: Out of memory"); + return -1; + } + + request.name = args->name; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.pause) { + ERROR("Unimplemented pause op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.pause(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } +out: + lcrc_pause_response_free(response); + return ret; +} + +int cmd_pause_main(int argc, const char **argv) +{ + int i = 0; + int status = 0; + struct log_config lconf = { 0 }; + + lconf.name = argv[0]; + lconf.quiet = true; + lconf.file = NULL; + lconf.priority = "ERROR"; + lconf.driver = "stdout"; + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed"); + exit(ECOMMON); + } + command_t cmd; + if (client_arguments_init(&g_cmd_pause_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_pause_args.progname = argv[0]; + struct command_option options[] = { COMMON_OPTIONS(g_cmd_pause_args) }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_pause_desc, + g_cmd_pause_usage); + if (command_parse_args(&cmd, &g_cmd_pause_args.argc, &g_cmd_pause_args.argv)) { + exit(EINVALIDARGS); + } + + if (g_cmd_pause_args.argc == 0) { + COMMAND_ERROR("Pause requires at least 1 container names"); + exit(EINVALIDARGS); + } + + if (g_cmd_pause_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to pause."); + exit(EINVALIDARGS); + } + + for (i = 0; i < g_cmd_pause_args.argc; i++) { + g_cmd_pause_args.name = g_cmd_pause_args.argv[i]; + if (client_pause(&g_cmd_pause_args)) { + ERROR("Container \"%s\" pause failed", g_cmd_pause_args.name); + status = -1; + continue; + } + + printf("%s\n", g_cmd_pause_args.name); + } + + if (status) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/extend/pause.h b/src/cmd/lcrc/extend/pause.h new file mode 100644 index 0000000..7cd43f1 --- /dev/null +++ b/src/cmd/lcrc/extend/pause.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container pause definition + ******************************************************************************/ +#ifndef __CMD_PAUSE_H +#define __CMD_PAUSE_H + +#include "arguments.h" + +extern const char g_cmd_pause_desc[]; +extern const char g_cmd_pause_usage[]; +extern struct client_arguments g_cmd_pause_args; +int cmd_pause_main(int argc, const char **argv); + +#endif diff --git a/src/cmd/lcrc/extend/resume.c b/src/cmd/lcrc/extend/resume.c new file mode 100644 index 0000000..5478693 --- /dev/null +++ b/src/cmd/lcrc/extend/resume.c @@ -0,0 +1,118 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container resume functions + ******************************************************************************/ +#include "resume.h" +#include "securec.h" +#include "utils.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_resume_desc[] = "resume container"; +const char g_cmd_resume_usage[] = "resume [command options] --name=NAME"; + +struct client_arguments g_cmd_resume_args = {}; + +/* + * Create a resume request message and call RPC + */ +static int client_resume(const struct client_arguments *args) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_resume_request request = { 0 }; + struct lcrc_resume_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(struct lcrc_resume_response)); + if (response == NULL) { + ERROR("Resume: Out of memory"); + return -1; + } + + request.name = args->name; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.resume) { + ERROR("Unimplemented resume op"); + ret = -1; + goto out; + } + config = get_connect_config(args); + ret = ops->container.resume(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } +out: + lcrc_resume_response_free(response); + return ret; +} + +int cmd_resume_main(int argc, const char **argv) +{ + int i = 0; + int status = 0; + struct log_config lconf = { 0 }; + + lconf.name = argv[0]; + lconf.quiet = true; + lconf.driver = "stdout"; + lconf.file = NULL; + lconf.priority = "ERROR"; + if (log_init(&lconf)) { + COMMAND_ERROR("Resume: log init failed"); + exit(ECOMMON); + } + + command_t cmd; + if (client_arguments_init(&g_cmd_resume_args)) { + COMMAND_ERROR("client arguments init failed\n"); + exit(ECOMMON); + } + g_cmd_resume_args.progname = argv[0]; + struct command_option options[] = { COMMON_OPTIONS(g_cmd_resume_args) }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_resume_desc, + g_cmd_resume_usage); + if (command_parse_args(&cmd, &g_cmd_resume_args.argc, &g_cmd_resume_args.argv)) { + exit(EINVALIDARGS); + } + + if (g_cmd_resume_args.argc == 0) { + COMMAND_ERROR("Pause requires at least 1 container names"); + exit(EINVALIDARGS); + } + + if (g_cmd_resume_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to resume."); + exit(EINVALIDARGS); + } + + for (i = 0; i < g_cmd_resume_args.argc; i++) { + g_cmd_resume_args.name = g_cmd_resume_args.argv[i]; + if (client_resume(&g_cmd_resume_args)) { + ERROR("Container \"%s\" resume failed", g_cmd_resume_args.name); + status = -1; + continue; + } + + printf("%s\n", g_cmd_resume_args.name); + } + + if (status) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/extend/resume.h b/src/cmd/lcrc/extend/resume.h new file mode 100644 index 0000000..5847650 --- /dev/null +++ b/src/cmd/lcrc/extend/resume.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container resume definition + ******************************************************************************/ +#ifndef __CMD_RESUME_H +#define __CMD_RESUME_H + +#include "arguments.h" + +extern const char g_cmd_resume_desc[]; +extern const char g_cmd_resume_usage[]; +extern struct client_arguments g_cmd_resume_args; +int cmd_resume_main(int argc, const char **argv); + +#endif diff --git a/src/cmd/lcrc/extend/stats.c b/src/cmd/lcrc/extend/stats.c new file mode 100644 index 0000000..cab626d --- /dev/null +++ b/src/cmd/lcrc/extend/stats.c @@ -0,0 +1,258 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container stats functions + ******************************************************************************/ +#define __STDC_FORMAT_MACROS /* Required for PRIu64 to work. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "securec.h" +#include "arguments.h" +#include "stats.h" +#include "utils.h" +#include "log.h" +#include "lcrc_connect.h" + +#define ESC "\033" +#define TERMCLEAR ESC "[H" ESC "[J" +#define TERMNORM ESC "[0m" +#define TERMBOLD ESC "[1m" +#define TERMRVRS ESC "[7m" + +const char g_cmd_stats_desc[] = "Display a live stream of container(s) resource usage statistics"; +const char g_cmd_stats_usage[] = "stats [OPTIONS] [CONTAINER...]"; + +struct client_arguments g_cmd_stats_args = { + .showall = false, + .nostream = false, + .runtime = "lcr", +}; + +static struct lcrc_stats_response *g_oldstats = NULL; + +static void lcrc_size_humanize(unsigned long long val, char *buf, size_t bufsz) +{ + int ret = 0; + if (val > 1024 * 1024 * 1024) { + ret = sprintf_s(buf, bufsz, "%u.%.2u GiB", (unsigned int)(val >> 30), + (unsigned int)(val & ((1 << 30) - 1)) / 10737419); + } else if (val > 1024 * 1024) { + unsigned long long x = val + 5243; /* for rounding */ + ret = sprintf_s(buf, bufsz, "%u.%.2u MiB", (unsigned int)(x >> 20), + (unsigned int)(((x & ((1 << 20) - 1)) * 100) >> 20)); + } else if (val > 1024) { + unsigned long long x = val + 5; /* for rounding */ + ret = sprintf_s(buf, bufsz, "%u.%.2u KiB", (unsigned int)(x >> 10), + (unsigned int)(((x & ((1 << 10) - 1)) * 100) >> 10)); + } else { + ret = sprintf_s(buf, bufsz, "%u.00 B", (unsigned int)val); + } + if (ret < 0) { + ERROR("Humanize sprintf failed!"); + } +} + +static void stats_print_header(void) +{ + printf(TERMRVRS TERMBOLD); + printf("%-16s %-10s %-26s %-10s %-26s %-10s", "CONTAINER", "CPU %", "MEM USAGE / LIMIT", "MEM %", + "BLOCK I / O", "PIDS"); + printf("\n"); + printf(TERMNORM); +} + +static void stats_print(const struct lcrc_container_info *stats) +{ +#define SHORTIDLEN 12 +#define PERCENT 100 + char iosb_str[63]; + char iosb_read_str[20]; + char iosb_write_str[20]; + char mem_str[63]; + char mem_used_str[20]; + char mem_limit_str[20]; + int len; + double cpu_percent = 0.0; + char *short_id = NULL; + + lcrc_size_humanize(stats->blkio_read, iosb_read_str, sizeof(iosb_read_str)); + lcrc_size_humanize(stats->blkio_write, iosb_write_str, sizeof(iosb_write_str)); + lcrc_size_humanize(stats->mem_used, mem_used_str, sizeof(mem_used_str)); + lcrc_size_humanize(stats->mem_limit, mem_limit_str, sizeof(mem_limit_str)); + + len = sprintf_s(iosb_str, sizeof(iosb_str), "%s / %s", iosb_read_str, iosb_write_str); + if (len < 0) { + ERROR("Sprintf iosb_str failed"); + return; + } + len = sprintf_s(mem_str, sizeof(mem_str), "%s / %s", mem_used_str, mem_limit_str); + if (len < 0) { + ERROR("Sprintf mem_str failed"); + return; + } + + if (g_oldstats != NULL) { + size_t i; + uint64_t d_sys_use = 0; + uint64_t d_cpu_use = 0; + for (i = 0; i < g_oldstats->container_num; i++) { + if (strcmp(stats->id, g_oldstats->container_stats[i].id) != 0) { + continue; + } + if (stats->cpu_system_use > g_oldstats->container_stats[i].cpu_system_use) { + d_sys_use = stats->cpu_system_use - g_oldstats->container_stats[i].cpu_system_use; + } + if (stats->cpu_use_nanos > g_oldstats->container_stats[i].cpu_use_nanos) { + d_cpu_use = stats->cpu_use_nanos - g_oldstats->container_stats[i].cpu_use_nanos; + } + if (d_sys_use > 0 && stats->online_cpus > 0) { + cpu_percent = ((double)d_cpu_use / d_sys_use) * stats->online_cpus * PERCENT; + } + } + } + + short_id = util_strdup_s(stats->id); + if (strlen(short_id) > SHORTIDLEN) { + short_id[SHORTIDLEN] = '\0'; + } + printf("%-16s %-10.2f %-26s %-10.2f %-26s %-10llu", short_id, cpu_percent, mem_str, + stats->mem_limit ? ((double)stats->mem_used / stats->mem_limit) * PERCENT : 0.00, iosb_str, + (unsigned long long)stats->pids_current); + free(short_id); +} + +static void stats_output(const struct client_arguments *args, struct lcrc_stats_response **response) +{ + size_t i; + + printf(TERMCLEAR); + stats_print_header(); + for (i = 0; i < (*response)->container_num; i++) { + stats_print(&((*response)->container_stats[i])); + printf("\n"); + } + fflush(stdout); + + lcrc_stats_response_free(g_oldstats); + g_oldstats = *response; + *response = NULL; +} + +static int client_stats_mainloop(const struct client_arguments *args, const struct lcrc_stats_request *request) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + client_connect_config_t config; + + if (args == NULL) { + return -1; + } + ops = get_connect_client_ops(); + if (ops == NULL || ops->container.stats == NULL) { + ERROR("Unimplemented ops"); + return -1; + } + config = get_connect_config(args); + + while (1) { + struct lcrc_stats_response *response = NULL; + response = util_common_calloc_s(sizeof(struct lcrc_stats_response)); + if (response == NULL) { + ERROR("Out of memory"); + ret = -1; + goto err_out; + } + + ret = ops->container.stats(request, response, &config); + if (ret) { + ERROR("Failed to stats containers info"); + client_print_error(response->cc, response->server_errono, response->errmsg); + lcrc_stats_response_free(response); + ret = -1; + goto err_out; + } + + stats_output(args, &response); + lcrc_stats_response_free(response); + if (args->nostream) { + goto err_out; + } + + sleep(1); + } + +err_out: + lcrc_stats_response_free(g_oldstats); + g_oldstats = NULL; + return ret; +} + +/* +* Create a stats request message and call RPC +*/ +static int client_stats(const struct client_arguments *args) +{ + struct lcrc_stats_request request = { 0 }; + + request.runtime = args->runtime; + request.all = args->showall; + request.containers = (char **)(args->argv); + request.containers_len = (size_t)(args->argc); + + return client_stats_mainloop(args, &request); +} + +int cmd_stats_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + command_t cmd; + struct command_option options[] = { + LOG_OPTIONS(lconf), + STATUS_OPTIONS(g_cmd_stats_args), + COMMON_OPTIONS(g_cmd_stats_args) + }; + + if (client_arguments_init(&g_cmd_stats_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_stats_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_stats_desc, + g_cmd_stats_usage); + set_default_command_log_config(argv[0], &lconf); + if (command_parse_args(&cmd, &g_cmd_stats_args.argc, &g_cmd_stats_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Stats: log init failed"); + exit(ECOMMON); + } + + if (client_stats(&g_cmd_stats_args)) { + ERROR("Can not stats containers"); + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} + diff --git a/src/cmd/lcrc/extend/stats.h b/src/cmd/lcrc/extend/stats.h new file mode 100644 index 0000000..40b4165 --- /dev/null +++ b/src/cmd/lcrc/extend/stats.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container stats definition + ******************************************************************************/ +#ifndef __CMD_STATS_H +#define __CMD_STATS_H + +#include "arguments.h" + +#define STATUS_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "runtime", 'R', &(cmdargs).runtime, \ + "Runtime to use for containers(default: lcr)", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "all", 'a', &(cmdargs).showall, \ + "Show all containers (default shows just running)", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "no-stream", 0, &(cmdargs).nostream, \ + "Disable streaming stats and only pull the first result", NULL } + +extern const char g_cmd_stats_desc[]; +extern const char g_cmd_stats_usage[]; +extern struct client_arguments g_cmd_stats_args; +int cmd_stats_main(int argc, const char **argv); + +#endif /* __CMD_STATS_H */ diff --git a/src/cmd/lcrc/extend/update.c b/src/cmd/lcrc/extend/update.c new file mode 100644 index 0000000..e533e96 --- /dev/null +++ b/src/cmd/lcrc/extend/update.c @@ -0,0 +1,166 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container update functions + ******************************************************************************/ +#include +#include +#include "securec.h" +#include "arguments.h" +#include "update.h" +#include "utils.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_update_desc[] = "Update configuration of one or more containers"; +const char g_cmd_update_usage[] = "update [OPTIONS] CONTAINER [CONTAINER...]"; + +struct client_arguments g_cmd_update_args = { + .restart = NULL, +}; + +static int pack_update_request(const struct client_arguments *args, struct lcrc_update_request *request) +{ + int ret = 0; + + request->updateconfig->restart_policy = args->restart; + + request->updateconfig->cr->blkio_weight = args->cr.blkio_weight; + + request->updateconfig->cr->cpu_shares = args->cr.cpu_shares; + + request->updateconfig->cr->cpu_period = args->cr.cpu_period; + + request->updateconfig->cr->cpu_quota = args->cr.cpu_quota; + + request->updateconfig->cr->cpuset_cpus = args->cr.cpuset_cpus; + + request->updateconfig->cr->cpuset_mems = args->cr.cpuset_mems; + + request->updateconfig->cr->memory = args->cr.memory_limit; + + request->updateconfig->cr->memory_swap = args->cr.memory_swap; + + request->updateconfig->cr->memory_reservation = args->cr.memory_reservation; + + request->updateconfig->cr->kernel_memory = args->cr.kernel_memory_limit; + + return ret; +} + +static int client_update(const struct client_arguments *args) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + container_cgroup_resources_t cr = { 0 }; + lcrc_update_config_t updateconfig = { 0 }; + struct lcrc_update_request request = { 0 }; + struct lcrc_update_response *response = NULL; + client_connect_config_t config = { 0 }; + + response = util_common_calloc_s(sizeof(struct lcrc_update_response)); + if (response == NULL) { + ERROR("Out of memory"); + return -1; + } + + updateconfig.cr = &cr; + request.updateconfig = &updateconfig; + request.name = args->name; + + ret = pack_update_request(args, &request); + if (ret) { + ret = -1; + goto out; + } + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.update) { + ERROR("Unimplemented ops"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.update(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + goto out; + } + +out: + lcrc_update_response_free(response); + return ret; +} + +int cmd_update_main(int argc, const char **argv) +{ + int ret = 0; + int i = 0; + struct log_config lconf = { 0 }; + command_t cmd; + struct command_option options[] = { + LOG_OPTIONS(lconf), + UPDATE_OPTIONS(g_cmd_update_args), + COMMON_OPTIONS(g_cmd_update_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_update_args)) { + COMMAND_ERROR("client arguments init failed\n"); + exit(ECOMMON); + } + g_cmd_update_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_update_desc, + g_cmd_update_usage); + if (command_parse_args(&cmd, &g_cmd_update_args.argc, &g_cmd_update_args.argv) || + update_checker(&g_cmd_update_args)) { + exit(EINVALIDARGS); + } + if (argc <= 3) { + COMMAND_ERROR("You must provide one or more udpate flags when using this command\n"); + exit(ECOMMON); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Update: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_update_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to update."); + exit(ECOMMON); + } + + for (i = 0; i < g_cmd_update_args.argc; i++) { + g_cmd_update_args.name = g_cmd_update_args.argv[i]; + if (client_update(&g_cmd_update_args)) { + ERROR("Update container \"%s\" failed\n", g_cmd_update_args.name); + ret = ECOMMON; + continue; + } + printf("%s\n", g_cmd_update_args.name); + } + + return ret; +} + +int update_checker(const struct client_arguments *args) +{ + int ret = 0; + + if (args->argc == 0) { + COMMAND_ERROR("Update requires at least 1 container names"); + return EINVALIDARGS; + } + + return ret; +} diff --git a/src/cmd/lcrc/extend/update.h b/src/cmd/lcrc/extend/update.h new file mode 100644 index 0000000..7b57747 --- /dev/null +++ b/src/cmd/lcrc/extend/update.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container update definition + ******************************************************************************/ +#ifndef __CMD_UPDATE_H +#define __CMD_UPDATE_H + +#include "arguments.h" + +#define UPDATE_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_CALLBACK, false, "cpu-shares", 0, &(cmdargs).cr.cpu_shares, \ + "CPU shares (relative weight)", command_convert_llong }, \ + { CMD_OPT_TYPE_CALLBACK, false, "cpu-period", 0, &(cmdargs).cr.cpu_period, \ + "Limit CPU CFS (Completely Fair Scheduler) period", command_convert_llong }, \ + { CMD_OPT_TYPE_CALLBACK, false, "cpu-quota", 0, &(cmdargs).cr.cpu_quota, \ + "Limit CPU CFS (Completely Fair Scheduler) quota", command_convert_llong }, \ + { CMD_OPT_TYPE_STRING, false, "cpuset-cpus", 0, &(cmdargs).cr.cpuset_cpus, \ + "CPUs in which to allow execution (0-3, 0,1)", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "cpuset-mems", 0, &(cmdargs).cr.cpuset_mems, \ + "MEMs in which to allow execution (0-3, 0,1)", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "kernel-memory", 0, &(cmdargs).cr.kernel_memory_limit, \ + "Kernel memory limit", command_convert_membytes }, \ + { CMD_OPT_TYPE_CALLBACK, false, "memory", 'm', &(cmdargs).cr.memory_limit, \ + "Memory limit", command_convert_membytes }, \ + { CMD_OPT_TYPE_CALLBACK, false, "memory-reservation", 0, &(cmdargs).cr.memory_reservation, \ + "Memory soft limit", command_convert_membytes }, \ + { CMD_OPT_TYPE_CALLBACK, false, "memory-swap", 0, &(cmdargs).cr.memory_swap, \ + "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", command_convert_memswapbytes }, \ + { CMD_OPT_TYPE_STRING, false, "restart", 0, &(cmdargs).restart, \ + "Restart policy to apply when a container exits", NULL } + +extern const char g_cmd_update_desc[]; +extern const char g_cmd_update_usage[]; +extern struct client_arguments g_cmd_update_args; +int cmd_update_main(int argc, const char **argv); +int update_checker(const struct client_arguments *args); + +#endif /* __CMD_UPDATE_H */ diff --git a/src/cmd/lcrc/images/CMakeLists.txt b/src/cmd/lcrc/images/CMakeLists.txt new file mode 100644 index 0000000..6bdb7cb --- /dev/null +++ b/src/cmd/lcrc/images/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} lcrc_images_srcs) + +set(LCRC_IMAGES_SRCS + ${lcrc_images_srcs} + PARENT_SCOPE + ) diff --git a/src/cmd/lcrc/images/images.c b/src/cmd/lcrc/images/images.c new file mode 100644 index 0000000..6b81832 --- /dev/null +++ b/src/cmd/lcrc/images/images.c @@ -0,0 +1,280 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container image functions + ******************************************************************************/ +#include "images.h" + +#include +#include +#include +#include +#include +#include "securec.h" + +#include "utils.h" +#include "arguments.h" +#include "lcrc_connect.h" +#include "log.h" + +#define IMAGES_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "quiet", 'q', &((cmdargs).dispname), "Only display image names", NULL } + +#define CREATED_DISPLAY_FORMAT "YYYY-MM-DD HH:MM:SS" +#define SHORT_DIGEST_LEN 12 + +const char g_cmd_images_desc[] = "List images"; +const char g_cmd_images_usage[] = "images"; + +struct client_arguments g_cmd_images_args = {}; +/* keep track of field widths for printing. */ +struct lengths { + unsigned int ref_length; + unsigned int digest_length; + unsigned int created_length; + unsigned int size_length; +}; + +/* trans time */ +static char *trans_time(int64_t created) +{ + struct tm t; + int nret = 0; + char formated_time[sizeof(CREATED_DISPLAY_FORMAT)] = { 0 }; + time_t created_time = (time_t)created; + + if (!localtime_r(&created_time, &t)) { + ERROR("translate time for created failed: %s", strerror(errno)); + return NULL; + } + + nret = sprintf_s(formated_time, sizeof(formated_time), "%04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, + t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); + if (nret < 0) { + ERROR("format created time failed"); + return NULL; + } + + return util_strdup_s(formated_time); +} + +/* list print table */ +static void list_print_table(struct lcrc_image_info *images_list, const size_t size, const struct lengths *length) +{ + const struct lcrc_image_info *in = NULL; + size_t i = 0; + char *created = NULL; + char *digest = NULL; + char *image_size = NULL; + if (length == NULL) { + return; + } + /* print header */ + printf("%-*s ", (int)length->ref_length, "REF"); + printf("%-*s ", (int)length->digest_length, "IMAGE ID"); + printf("%-*s ", (int)length->created_length, "CREATED"); + printf("%-*s ", (int)length->size_length, "SIZE"); + printf("\n"); + + for (i = 0, in = images_list; i < size && in != NULL; i++, in++) { + printf("%-*s ", (int)length->ref_length, in->imageref ? in->imageref : "-"); + + digest = util_short_digest(in->digest); + printf("%-*s ", (int)length->digest_length, digest ? digest : "-"); + free(digest); + + created = trans_time(in->created); + printf("%-*s ", (int)length->created_length, created ? created : "-"); + free(created); + + image_size = util_human_size_decimal(in->size); + + printf("%-*s ", (int)length->size_length, image_size ? image_size : "-"); + free(image_size); + printf("\n"); + } +} + +/* list field width */ +static void list_field_width(const struct lcrc_image_info *images_list, const size_t size, struct lengths *l) +{ + const struct lcrc_image_info *in = NULL; + size_t i = 0; + char tmpbuffer[30]; + + for (i = 0, in = images_list; i < size && in != NULL; i++, in++) { + size_t len; + int slen; + if (in->imageref) { + len = strlen(in->imageref); + if (len > l->ref_length) { + l->ref_length = (unsigned int)len; + } + } + if (in->digest) { + len = SHORT_DIGEST_LEN; + if (len > l->digest_length) { + l->digest_length = (unsigned int)len; + } + } + if (in->created) { + len = strlen(CREATED_DISPLAY_FORMAT); + if (len > l->created_length) { + l->created_length = (unsigned int)len; + } + } + + slen = sprintf_s(tmpbuffer, sizeof(tmpbuffer), "%.2f", (float)(in->size) / (1024 * 1024)); + if (slen < 0) { + ERROR("sprintf tmpbuffer failed"); + return; + } + if ((unsigned int)slen > l->size_length) { + l->size_length = (unsigned int)slen; + } + } +} + +/* + * list all images from LCRD + */ +static void images_info_print(const struct lcrc_list_images_response *response) +{ + struct lengths max_len = { + .ref_length = 30, /* ref */ + .digest_length = 20, /* digest */ + .created_length = 20, /* created */ + .size_length = 10, /* size */ + }; + + list_field_width(response->images_list, (size_t)response->images_num, &max_len); + list_print_table(response->images_list, (size_t)response->images_num, &max_len); +} + +/* images info print quiet */ +static void images_info_print_quiet(const struct lcrc_list_images_response *response) +{ + struct lengths max_len = { + .ref_length = 30, /* ref */ + }; + + const struct lcrc_image_info *in = NULL; + size_t i = 0; + + for (i = 0, in = response->images_list; in != NULL && i < response->images_num; i++, in++) { + printf("%-*s ", (int)(max_len.ref_length), in->imageref ? in->imageref : "-"); + printf("\n"); + } +} + +/* +* used by qsort function for comparing image created time +*/ +static inline int lcrc_image_cmp(struct lcrc_image_info *first, struct lcrc_image_info *second) +{ + if (second->created > first->created) { + return 1; + } else if (second->created < first->created) { + return -1; + } else { + return second->created_nanos > first->created_nanos; + } +} + +/* + * list the images from remote + */ +static int list_images(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_list_images_request request = { 0 }; + struct lcrc_list_images_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_list_images_response)); + if (response == NULL) { + ERROR("Imagelist: Out of memory"); + return -1; + } + + ops = get_connect_client_ops(); + if (ops == NULL || ops->image.list == NULL) { + ERROR("Unimplemented image list op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->image.list(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + goto out; + } + + if (response->images_list != NULL && response->images_num > 0) { + qsort(response->images_list, (size_t)(response->images_num), sizeof(struct lcrc_image_info), + (int (*)(const void *, const void *))lcrc_image_cmp); + } + + if (args->dispname) { + images_info_print_quiet(response); + } else { + images_info_print(response); + } + +out: + lcrc_list_images_response_free(response); + return ret; +} + +/* cmd images main */ +int cmd_images_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + int exit_code = ECOMMON; + command_t cmd; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_images_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_images_args.progname = argv[0]; + struct command_option options[] = { + LOG_OPTIONS(lconf), + IMAGES_OPTIONS(g_cmd_images_args), + COMMON_OPTIONS(g_cmd_images_args) + }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_images_desc, + g_cmd_images_usage); + if (command_parse_args(&cmd, &g_cmd_images_args.argc, &g_cmd_images_args.argv)) { + exit(exit_code); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Images: log init failed"); + exit(exit_code); + } + + if (g_cmd_images_args.argc > 0) { + COMMAND_ERROR("%s: \"images\" requires 0 arguments.", g_cmd_images_args.progname); + exit(exit_code); + } + + if (list_images(&g_cmd_images_args)) { + ERROR("Can not list any images"); + exit(exit_code); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/images/images.h b/src/cmd/lcrc/images/images.h new file mode 100644 index 0000000..c415642 --- /dev/null +++ b/src/cmd/lcrc/images/images.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container images definition + ******************************************************************************/ +#ifndef __CMD_IMAGES_LIST_H +#define __CMD_IMAGES_LIST_H + +#include "arguments.h" + +extern const char g_cmd_images_desc[]; +extern const char g_cmd_images_usage[]; +extern struct client_arguments g_cmd_images_args; +int cmd_images_main(int argc, const char **argv); + +#endif /*__CMD_IMAGES_LIST_H*/ diff --git a/src/cmd/lcrc/images/load.c b/src/cmd/lcrc/images/load.c new file mode 100644 index 0000000..fa913f9 --- /dev/null +++ b/src/cmd/lcrc/images/load.c @@ -0,0 +1,174 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container load functions + ******************************************************************************/ +#include "load.h" +#include +#include +#include +#include +#include +#include +#include "securec.h" + +#include "utils.h" +#include "arguments.h" +#include "lcrc_connect.h" +#include "log.h" + +#ifdef ENABLE_EMBEDDED_IMAGE +const char g_cmd_load_desc[] = "load an image from a manifest or a tar archive"; +const char g_cmd_load_usage[] = "load [OPTIONS] --input=FILE --type=TYPE"; +#else +const char g_cmd_load_desc[] = "load an image from a tar archive"; +const char g_cmd_load_usage[] = "load [OPTIONS] --input=FILE"; +#endif + +struct client_arguments g_cmd_load_args = { 0 }; + +/* + * load the image from a manifest or a tar archive + */ +static int client_load_image(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_load_request request = { 0 }; + struct lcrc_load_response response = { 0 }; + client_connect_config_t config = { 0 }; + int ret = 0; + + request.file = args->file; + request.type = args->type; + request.tag = args->tag; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->image.load) { + ERROR("Unimplemented ops"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->image.load(&request, &response, &config); + if (ret) { + client_print_error(response.cc, response.server_errono, response.errmsg); + if (response.server_errono) { + ret = ESERVERERROR; + } + goto out; + } +out: + return ret; +} + +/* Waring: This function may modify image type */ +static bool valid_param() +{ + if (g_cmd_load_args.argc > 0) { + COMMAND_ERROR("%s: \"load\" requires 0 arguments.", g_cmd_load_args.progname); + return false; + } + + if (g_cmd_load_args.file == NULL) { + COMMAND_ERROR("missing image input, use -i,--input option"); + return false; + } + + if (g_cmd_load_args.type != NULL) { + if ((strcmp(g_cmd_load_args.type, "docker") != 0) && + (strcmp(g_cmd_load_args.type, "embedded") != 0)) { + COMMAND_ERROR("Invalid image type: image type must be embedded or docker, got %s", + g_cmd_load_args.type); + return false; + } + } + +#ifdef ENABLE_EMBEDDED_IMAGE + /* Treat the tar as docker archive if type is empty */ + if (g_cmd_load_args.type == NULL || strncmp(g_cmd_load_args.type, "docker", 7) == 0) { + /* Docker archive tar is loaded into oci type. */ + /* Do not free type here because type is not a allocated memory. */ + g_cmd_load_args.type = "oci"; + } else { + if (g_cmd_load_args.tag != NULL) { + COMMAND_ERROR("Option --tag can be used only when type is docker"); + return false; + } + } +#else + g_cmd_load_args.type = "oci"; +#endif + + return true; +} + +int cmd_load_main(int argc, const char **argv) +{ + int ret = 0; + char file[PATH_MAX] = { 0 }; + struct log_config lconf = { 0 }; + int exit_code = ECOMMON; + command_t cmd; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_load_args), + LOAD_OPTIONS(g_cmd_load_args), +#ifdef ENABLE_EMBEDDED_IMAGE + EMBEDDED_OPTIONS(g_cmd_load_args), +#endif + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_load_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_load_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_load_desc, + g_cmd_load_usage); + if (command_parse_args(&cmd, &g_cmd_load_args.argc, &g_cmd_load_args.argv)) { + exit(exit_code); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Load: log init failed"); + exit(exit_code); + } + + if (valid_param() != true) { + exit(exit_code); + } + + /* If it's not a absolute path, add cwd to be absolute path */ + if (g_cmd_load_args.file[0] != '/') { + char cwd[PATH_MAX] = { 0 }; + + if (!getcwd(cwd, sizeof(cwd))) { + COMMAND_ERROR("get cwd failed:%s", strerror(errno)); + exit(exit_code); + } + + if (sprintf_s(file, sizeof(file), "%s/%s", cwd, g_cmd_load_args.file) < 0) { + COMMAND_ERROR("filename too long"); + exit(exit_code); + } + g_cmd_load_args.file = file; + } + + ret = client_load_image(&g_cmd_load_args); + if (ret) { + exit(exit_code); + } + + printf("Load image from \"%s\" success\n", g_cmd_load_args.file); + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/images/load.h b/src/cmd/lcrc/images/load.h new file mode 100644 index 0000000..7eb7861 --- /dev/null +++ b/src/cmd/lcrc/images/load.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container load definition + ******************************************************************************/ +#ifndef __CMD_LOAD_H +#define __CMD_LOAD_H + +#include "arguments.h" + +#define LOAD_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "input", 'i', &(cmdargs).file, "Read from a manifest or an archive", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "tag", 0, &(cmdargs).tag, \ + "Name and optionally a tag in the 'name:tag' format, valid if type is docker", NULL } + +#define EMBEDDED_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "type", 't', &(cmdargs).type, "Image type, embedded or docker(default)", NULL } + +extern const char g_cmd_load_desc[]; +extern struct client_arguments g_cmd_load_args; +int cmd_load_main(int argc, const char **argv); + +#endif /*__CMD_LOAD_H*/ diff --git a/src/cmd/lcrc/images/login.c b/src/cmd/lcrc/images/login.c new file mode 100644 index 0000000..5f82f89 --- /dev/null +++ b/src/cmd/lcrc/images/login.c @@ -0,0 +1,232 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-6-18 + * Description: provide login + ********************************************************************************/ +#include "login.h" + +#include +#include +#include +#include +#include +#include +#include "securec.h" + +#include "utils.h" +#include "arguments.h" +#include "lcrc_connect.h" +#include "log.h" + +const char g_cmd_login_desc[] = "Log in to a Docker registry"; +const char g_cmd_login_usage[] = "login [OPTIONS] SERVER"; + +struct client_arguments g_cmd_login_args = {}; + +/* + * Login to a docker registry. + */ +int client_login(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_login_request request = { 0 }; + struct lcrc_login_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_login_response)); + if (response == NULL) { + ERROR("Out of memory"); + return ECOMMON; + } + + // Support type oci only currently. + request.type = "oci"; + request.server = args->server; + request.username = args->username; + request.password = args->password; + + ops = get_connect_client_ops(); + if (ops == NULL || ops->image.login == NULL) { + ERROR("Unimplemented ops"); + ret = ECOMMON; + goto out; + } + + config = get_connect_config(args); + ret = ops->image.login(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = ESERVERERROR; + goto out; + } + + COMMAND_ERROR("Login Succeeded"); + +out: + lcrc_login_response_free(response); + return ret; +} + +static int get_password_from_notty(struct client_arguments *args) +{ + if (g_cmd_login_args.username == NULL && g_cmd_login_args.password_stdin) { + COMMAND_ERROR("Must provide --username with --password-stdin"); + return -1; + } + + if (g_cmd_login_args.password != NULL) { + printf("WARNING! Using --password via the CLI is insecure. Use --password-stdin.\n"); + if (g_cmd_login_args.password_stdin) { + printf("--password and --password-stdin are mutually exclusive\n"); + return -1; + } + } + + // Try get password from notty input. + if (g_cmd_login_args.password_stdin) { + char password[LOGIN_PASSWORD_LEN + 1] = { 0 }; + int n = util_input_notty(password, sizeof(password)); + if (n == 0) { + COMMAND_ERROR("Error: Password Required"); + return -1; + } + if (n < 0) { + COMMAND_ERROR("Get password from notty stdin failed: %s", strerror(errno)); + return -1; + } + args->password = util_strdup_s(password); + if (memset_s(password, sizeof(password), 0, sizeof(password)) != EOK) { + ERROR("Failed to memset sensitive string memory"); + } + } + + return 0; +} + +static int get_auth_from_terminal(struct client_arguments *args) +{ + int n; + + if (args->username == NULL) { + char username[LOGIN_USERNAME_LEN + 1] = {0}; + printf("Username: "); + n = util_input_echo(username, sizeof(username)); + if (n == 0) { + ERROR("Error: Non-null Username Required\n"); + return -1; + } + if (n < 0) { + if (errno == ENOTTY) { + COMMAND_ERROR("Error: Cannot perform an interactive login from a non TTY device"); + return -1; + } + COMMAND_ERROR("Get username failed: %s", strerror(errno)); + return -1; + } + args->username = util_strdup_s(username); + } + + if (args->password == NULL) { + char password[LOGIN_PASSWORD_LEN + 1] = {0}; + printf("Password: "); + n = util_input_noecho(password, sizeof(password)); + if (n == 0) { + ERROR("Error: Password Required\n"); + return -1; + } + if (n < 0) { + if (errno == ENOTTY) { + COMMAND_ERROR("Error: Cannot perform an interactive login from a non TTY device"); + return -1; + } + COMMAND_ERROR("Get password failed: %s", strerror(errno)); + return -1; + } + args->password = util_strdup_s(password); + if (memset_s(password, sizeof(password), 0, sizeof(password)) != EOK) { + ERROR("Failed to memset sensitive string memory"); + } + } + + return 0; +} + +static int get_auth(struct client_arguments *args) +{ + // Try get password from notty input. + if (get_password_from_notty(&g_cmd_login_args)) { + return -1; + } + + // Try get username and password from terminal. + if (get_auth_from_terminal(&g_cmd_login_args)) { + return -1; + } + + if (args->username == NULL || args->password == NULL) { + // This should not happen. + COMMAND_ERROR("Missing username or password"); + return -1; + } + + return 0; +} + +int cmd_login_main(int argc, const char **argv) +{ + int ret = 0; + struct log_config lconf = { 0 }; + int exit_code = 1; /* exit 1 if failed to login */ + command_t cmd; + struct command_option options[] = { + COMMON_OPTIONS(g_cmd_login_args), + LOGIN_OPTIONS(g_cmd_login_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_login_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_login_args.progname = argv[0]; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_login_desc, + g_cmd_login_usage); + if (command_parse_args(&cmd, &g_cmd_login_args.argc, &g_cmd_login_args.argv)) { + exit(exit_code); + } + + if (log_init(&lconf)) { + COMMAND_ERROR("login: log init failed"); + exit(exit_code); + } + + if (g_cmd_login_args.argc != 1) { + COMMAND_ERROR("login requires 1 argument."); + exit(exit_code); + } + + g_cmd_login_args.server = g_cmd_login_args.argv[0]; + + ret = get_auth(&g_cmd_login_args); + if (ret != 0) { + exit(exit_code); + } + + ret = client_login(&g_cmd_login_args); + if (ret != 0) { + exit(exit_code); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/images/login.h b/src/cmd/lcrc/images/login.h new file mode 100644 index 0000000..23a95c0 --- /dev/null +++ b/src/cmd/lcrc/images/login.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Description: provide login definition + ******************************************************************************/ +#ifndef __CMD_LOGIN_H +#define __CMD_LOGIN_H + +#include "arguments.h" + +#define LOGIN_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "username", 'u', &(cmdargs).username, "Username", NULL }, \ + { CMD_OPT_TYPE_STRING, false, "password", 'p', &(cmdargs).password, "Password", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "password-stdin", 0, &(cmdargs).password_stdin, \ + "Take the password from stdin", NULL }, \ + + +extern const char g_cmd_login_desc[]; +extern const char g_cmd_login_usage[]; +extern struct client_arguments g_cmd_login_args; +int cmd_login_main(int argc, const char **argv); + +#endif /*__CMD_LOGIN_H*/ diff --git a/src/cmd/lcrc/images/logout.c b/src/cmd/lcrc/images/logout.c new file mode 100644 index 0000000..d09b239 --- /dev/null +++ b/src/cmd/lcrc/images/logout.c @@ -0,0 +1,115 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-6-18 + * Description: provide logout + ********************************************************************************/ +#include "logout.h" + +#include +#include +#include +#include +#include +#include "securec.h" + +#include "utils.h" +#include "arguments.h" +#include "lcrc_connect.h" +#include "log.h" + +const char g_cmd_logout_desc[] = "Log out from a Docker registry"; +const char g_cmd_logout_usage[] = "logout SERVER"; + +struct client_arguments g_cmd_logout_args = {}; + +/* + * Logout from a docker registry. + */ +int client_logout(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_logout_request request = { 0 }; + struct lcrc_logout_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_logout_response)); + if (response == NULL) { + ERROR("Out of memory"); + return ECOMMON; + } + + // Support type oci only currently. + request.type = "oci"; + request.server = args->server; + + ops = get_connect_client_ops(); + if (ops == NULL || ops->image.logout == NULL) { + ERROR("Unimplemented ops"); + ret = ECOMMON; + goto out; + } + + config = get_connect_config(args); + ret = ops->image.logout(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = ESERVERERROR; + goto out; + } + + COMMAND_ERROR("Logout Succeeded"); + +out: + lcrc_logout_response_free(response); + return ret; +} + +int cmd_logout_main(int argc, const char **argv) +{ + int ret = 0; + struct log_config lconf = { 0 }; + int exit_code = 1; /* exit 1 if failed to logout */ + command_t cmd; + struct command_option options[] = { COMMON_OPTIONS(g_cmd_logout_args) }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_logout_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_logout_args.progname = argv[0]; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_logout_desc, + g_cmd_logout_usage); + if (command_parse_args(&cmd, &g_cmd_logout_args.argc, &g_cmd_logout_args.argv)) { + exit(exit_code); + } + + if (log_init(&lconf)) { + COMMAND_ERROR("logout: log init failed"); + exit(exit_code); + } + + if (g_cmd_logout_args.argc != 1) { + COMMAND_ERROR("logout requires 1 argument."); + exit(exit_code); + } + + g_cmd_logout_args.server = g_cmd_logout_args.argv[0]; + ret = client_logout(&g_cmd_logout_args); + if (ret != 0) { + exit(exit_code); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/images/logout.h b/src/cmd/lcrc/images/logout.h new file mode 100644 index 0000000..85930e9 --- /dev/null +++ b/src/cmd/lcrc/images/logout.h @@ -0,0 +1,24 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Description: provide logout definition + ******************************************************************************/ +#ifndef __CMD_LOGOUT_H +#define __CMD_LOGOUT_H + +#include "arguments.h" + +extern const char g_cmd_logout_desc[]; +extern const char g_cmd_logout_usage[]; +extern struct client_arguments g_cmd_logout_args; +int cmd_logout_main(int argc, const char **argv); + +#endif /*__CMD_LOGOUT_H*/ diff --git a/src/cmd/lcrc/images/pull.c b/src/cmd/lcrc/images/pull.c new file mode 100644 index 0000000..8eca845 --- /dev/null +++ b/src/cmd/lcrc/images/pull.c @@ -0,0 +1,114 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2019-3-29 + * Description: provide pull image + ********************************************************************************/ +#include "pull.h" + +#include +#include +#include +#include +#include +#include "securec.h" + +#include "utils.h" +#include "arguments.h" +#include "lcrc_connect.h" +#include "log.h" + +const char g_cmd_pull_desc[] = "Pull an image or a repository from a registry"; +const char g_cmd_pull_usage[] = "pull [OPTIONS] NAME[:TAG|@DIGEST]"; + +struct client_arguments g_cmd_pull_args = {}; + +/* + * Pull an image or a repository from a registry + */ +int client_pull(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_pull_request request = { 0 }; + struct lcrc_pull_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_pull_response)); + if (response == NULL) { + ERROR("Out of memory"); + return ECOMMON; + } + + request.image_name = args->image_name; + + ops = get_connect_client_ops(); + if (ops == NULL || ops->image.pull == NULL) { + ERROR("Unimplemented ops"); + ret = ECOMMON; + goto out; + } + COMMAND_ERROR("Image \"%s\" pulling", request.image_name); + + config = get_connect_config(args); + ret = ops->image.pull(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = ESERVERERROR; + goto out; + } + + COMMAND_ERROR("Image \"%s\" pulled", response->image_ref); + +out: + lcrc_pull_response_free(response); + return ret; +} + +int cmd_pull_main(int argc, const char **argv) +{ + int ret = 0; + struct log_config lconf = { 0 }; + int exit_code = 1; /* exit 1 if failed to pull */ + command_t cmd; + struct command_option options[] = { COMMON_OPTIONS(g_cmd_pull_args) }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_pull_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_pull_args.progname = argv[0]; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_pull_desc, + g_cmd_pull_usage); + if (command_parse_args(&cmd, &g_cmd_pull_args.argc, &g_cmd_pull_args.argv)) { + exit(exit_code); + } + + if (log_init(&lconf)) { + COMMAND_ERROR("Pull: log init failed"); + exit(exit_code); + } + + if (g_cmd_pull_args.argc != 1) { + COMMAND_ERROR("pull requires 1 argument."); + exit(exit_code); + } + + g_cmd_pull_args.image_name = g_cmd_pull_args.argv[0]; + ret = client_pull(&g_cmd_pull_args); + if (ret != 0) { + exit(exit_code); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/images/pull.h b/src/cmd/lcrc/images/pull.h new file mode 100644 index 0000000..9ce3c0e --- /dev/null +++ b/src/cmd/lcrc/images/pull.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2019-3-29 + * Description: provide pull image + ********************************************************************************/ + +#ifndef __CMD_PULL_IMAGE_H +#define __CMD_PULL_IMAGE_H + +#include "arguments.h" + +extern const char g_cmd_pull_desc[]; +extern const char g_cmd_pull_usage[]; +extern struct client_arguments g_cmd_pull_args; +int client_pull(const struct client_arguments *args); + +int cmd_pull_main(int argc, const char **argv); + +#endif /*__CMD_PULL_IMAGE_H*/ diff --git a/src/cmd/lcrc/images/rmi.c b/src/cmd/lcrc/images/rmi.c new file mode 100644 index 0000000..a9272af --- /dev/null +++ b/src/cmd/lcrc/images/rmi.c @@ -0,0 +1,132 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container remove functions + ******************************************************************************/ +#include "rmi.h" +#include +#include +#include +#include +#include +#include "securec.h" + +#include "utils.h" +#include "arguments.h" +#include "lcrc_connect.h" +#include "log.h" + +const char g_cmd_rmi_desc[] = "Remove one or more images"; +const char g_cmd_rmi_usage[] = "rmi [OPTIONS] IMAGE [IMAGE...]"; + +struct client_arguments g_cmd_rmi_args = { .force = false }; + +/* + * remove a image from DB + */ +static int client_rmi(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_rmi_request request = { 0 }; + struct lcrc_rmi_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + if (strcmp(args->image_name, "none") == 0 || strcmp(args->image_name, "none:latest") == 0) { + COMMAND_ERROR("Can not remove image '%s', image name 'none' or 'none:latest' is reserved", args->image_name); + return -1; + } + + response = util_common_calloc_s(sizeof(struct lcrc_rmi_response)); + if (response == NULL) { + ERROR("Out of memory"); + return -1; + } + + request.image_name = args->image_name; + request.force = args->force; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->image.remove) { + ERROR("Unimplemented ops"); + ret = -1; + goto out; + } + config = get_connect_config(args); + ret = ops->image.remove(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + if (response->server_errono) { + ret = ESERVERERROR; + } + goto out; + } +out: + lcrc_rmi_response_free(response); + return ret; +} + +int cmd_rmi_main(int argc, const char **argv) +{ + int i = 0; + int err = 0; + struct log_config lconf = { 0 }; + int exit_code = 1; /* exit 1 if remove failed because docker return 1 */ + command_t cmd; + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_rmi_args), + RMI_OPTIONS(g_cmd_rmi_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_rmi_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_rmi_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_rmi_desc, + g_cmd_rmi_usage); + if (command_parse_args(&cmd, &g_cmd_rmi_args.argc, &g_cmd_rmi_args.argv)) { + exit(exit_code); + } + if (log_init(&lconf)) { + COMMAND_ERROR("RMI: log init failed"); + exit(exit_code); + } + + if (g_cmd_rmi_args.argc == 0) { + COMMAND_ERROR("\"rmi\" requires at least 1 image names"); + exit(exit_code); + } + + if (g_cmd_rmi_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many images to remove."); + exit(exit_code); + } + + for (i = 0; i < g_cmd_rmi_args.argc; i++) { + g_cmd_rmi_args.image_name = g_cmd_rmi_args.argv[i]; + int ret = client_rmi(&g_cmd_rmi_args); + if (ret != 0) { + ERROR("Image \"%s\" remove failed", g_cmd_rmi_args.image_name); + err = ret; + continue; + } + printf("Image \"%s\" removed\n", g_cmd_rmi_args.image_name); + } + + if (err) { + exit(exit_code); + } + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/images/rmi.h b/src/cmd/lcrc/images/rmi.h new file mode 100644 index 0000000..b8f1813 --- /dev/null +++ b/src/cmd/lcrc/images/rmi.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container remove definition + ******************************************************************************/ +#ifndef __CMD_REMOVE_IMAGE_H +#define __CMD_REMOVE_IMAGE_H + +#include "arguments.h" + +#define RMI_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "force", 'f', &(cmdargs).force, "Force removal of the image", NULL } + +extern const char g_cmd_rmi_desc[]; +extern const char g_cmd_rmi_usage[]; +extern struct client_arguments g_cmd_rmi_args; +int cmd_rmi_main(int argc, const char **argv); + +#endif /*__CMD_REMOVE_IMAGE_H*/ diff --git a/src/cmd/lcrc/information/CMakeLists.txt b/src/cmd/lcrc/information/CMakeLists.txt new file mode 100644 index 0000000..8c6cdbc --- /dev/null +++ b/src/cmd/lcrc/information/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} lcrc_information_srcs) + +set(LCRC_INFORMATION_SRCS + ${lcrc_information_srcs} + PARENT_SCOPE + ) diff --git a/src/cmd/lcrc/information/health.c b/src/cmd/lcrc/information/health.c new file mode 100644 index 0000000..bdbb7ae --- /dev/null +++ b/src/cmd/lcrc/information/health.c @@ -0,0 +1,103 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container health functions + ******************************************************************************/ +#include "health.h" +#include "securec.h" +#include "utils.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_health_check_desc[] = "LCRD health check"; +const char g_cmd_health_check_usage[] = "health [command options]"; + +struct client_arguments g_cmd_health_check_args = { + .service = NULL, +}; + +/* + * Create a health check request message and call RPC + */ +static int client_health_check(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_health_check_request request = { 0 }; + struct lcrc_health_check_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_health_check_response)); + if (response == NULL) { + ERROR("Health: Out of memory"); + return -1; + } + + request.service = args->service; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->health.check) { + ERROR("Unimplemented health op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->health.check(&request, response, &config); + if (ret || response->health_status != HEALTH_SERVING_STATUS_SERVING) { + ret = -1; + } +out: + lcrc_health_check_response_free(response); + return ret; +} + +int cmd_health_check_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + + lconf.name = argv[0]; + lconf.priority = "ERROR"; + lconf.file = NULL; + lconf.quiet = true; + lconf.driver = "stdout"; + if (log_init(&lconf)) { + COMMAND_ERROR("Health: log init failed"); + exit(ECOMMON); + } + + command_t cmd; + if (client_arguments_init(&g_cmd_health_check_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_health_check_args.progname = argv[0]; + struct command_option options[] = { + HEALTH_OPTIONS(g_cmd_health_check_args), + COMMON_OPTIONS(g_cmd_health_check_args) + }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, + g_cmd_health_check_desc, g_cmd_health_check_usage); + if (command_parse_args(&cmd, &g_cmd_health_check_args.argc, &g_cmd_health_check_args.argv)) { + exit(EINVALIDARGS); + } + + if (client_health_check(&g_cmd_health_check_args)) { + printf("LCRD with socket name '%s' is NOT SERVING\n", g_cmd_health_check_args.socket); + exit(ECOMMON); + } + + printf("LCRD with socket name '%s' is SERVING\n", g_cmd_health_check_args.socket); + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/information/health.h b/src/cmd/lcrc/information/health.h new file mode 100644 index 0000000..5bbb535 --- /dev/null +++ b/src/cmd/lcrc/information/health.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container health definition + ******************************************************************************/ +#ifndef __CMD_HEALTHCHECK_H +#define __CMD_HEALTHCHECK_H + +#include "arguments.h" + +#define HEALTH_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "service", 'S', &(cmdargs).service, "GRPC service name", NULL } + +extern const char g_cmd_health_check_desc[]; +extern const char g_cmd_health_check_usage[]; +extern struct client_arguments g_cmd_health_check_args; +int cmd_health_check_main(int argc, const char **argv); + +#endif diff --git a/src/cmd/lcrc/information/info.c b/src/cmd/lcrc/information/info.c new file mode 100644 index 0000000..b99b3e8 --- /dev/null +++ b/src/cmd/lcrc/information/info.c @@ -0,0 +1,150 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide container info functions + ******************************************************************************/ +#include "info.h" +#include +#include +#include "securec.h" + +#include "utils.h" +#include "arguments.h" +#include "log.h" +#include "config.h" +#include "lcrc_connect.h" + +const char g_cmd_info_desc[] = "Display system-wide information"; +const char g_cmd_info_usage[] = "info"; + +struct client_arguments g_cmd_info_args = {}; + +static void client_info_server(const struct lcrc_info_response *response) +{ + printf("Containers: %u\n", (unsigned int)(response->containers_num)); + printf(" Running: %u\n", (unsigned int)(response->c_running)); + printf(" Paused: %u\n", (unsigned int)(response->c_paused)); + printf(" Stopped: %u\n", (unsigned int)(response->c_stopped)); + printf("Images: %u\n", (unsigned int)(response->images_num)); + if (response->version != NULL) { + printf("Server Version: %s\n", response->version); + } + if (response->logging_driver != NULL) { + printf("Logging Driver: %s\n", response->logging_driver); + } + if (response->cgroup_driver != NULL) { + printf("Cgroup Driverr: %s\n", response->cgroup_driver); + } + if (response->huge_page_size != NULL) { + printf("Hugetlb Pagesize: %s\n", response->huge_page_size); + } + if (response->kversion != NULL) { + printf("Kernel Version: %s\n", response->kversion); + } + if (response->operating_system != NULL) { + printf("Operating System: %s\n", response->operating_system); + } + if (response->os_type != NULL) { + printf("OSType: %s\n", response->os_type); + } + if (response->architecture != NULL) { + printf("Architecture: %s\n", response->architecture); + } + + printf("CPUs: %u\n", (unsigned int)(response->cpus)); + printf("Total Memory: %u GB\n", (unsigned int)(response->total_mem)); + if (response->nodename != NULL) { + printf("Name: %s\n", response->nodename); + } + if (response->isulad_root_dir != NULL) { + printf("iSulad Root Dir: %s\n", response->isulad_root_dir); + } + if (response->http_proxy != NULL) { + printf("Http Proxy: %s\n", response->http_proxy); + } + if (response->https_proxy != NULL) { + printf("Https Proxy: %s\n", response->https_proxy); + } + if (response->no_proxy != NULL) { + printf("No Proxy: %s\n", response->no_proxy); + } +} + +static int client_info(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_info_request request = { 0 }; + struct lcrc_info_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_info_response)); + if (response == NULL) { + ERROR("Info: Out of memory"); + return -1; + } + + ops = get_connect_client_ops(); + if (ops == NULL || (ops->container.info) == NULL) { + ERROR("Unimplemented info op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.info(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + goto out; + } + + client_info_server(response); + +out: + lcrc_info_response_free(response); + return ret; +} + +int cmd_info_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + command_t cmd; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_info_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_info_args.progname = argv[0]; + struct command_option options[] = { LOG_OPTIONS(lconf), COMMON_OPTIONS(g_cmd_info_args) }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_info_desc, + g_cmd_info_usage); + if (command_parse_args(&cmd, &g_cmd_info_args.argc, &g_cmd_info_args.argv) != 0) { + exit(EINVALIDARGS); + } + if (log_init(&lconf) != 0) { + COMMAND_ERROR("Info: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_info_args.argc > 0) { + COMMAND_ERROR("%s: \"info\" requires 0 arguments.", g_cmd_info_args.progname); + exit(ECOMMON); + } + + if (client_info(&g_cmd_info_args) != 0) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/information/info.h b/src/cmd/lcrc/information/info.h new file mode 100644 index 0000000..c419bbd --- /dev/null +++ b/src/cmd/lcrc/information/info.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide container info definition + ******************************************************************************/ +#ifndef __CMD_INFO_H +#define __CMD_INFO_H + +#include "arguments.h" + +extern const char g_cmd_info_desc[]; +extern const char g_cmd_info_usage[]; +extern struct client_arguments g_cmd_info_args; +int cmd_info_main(int argc, const char **argv); + +#endif /* __CMD_INFO_H */ diff --git a/src/cmd/lcrc/information/inspect.c b/src/cmd/lcrc/information/inspect.c new file mode 100644 index 0000000..3db2a76 --- /dev/null +++ b/src/cmd/lcrc/information/inspect.c @@ -0,0 +1,814 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container inspect functions + ******************************************************************************/ +#include "error.h" +#include "inspect.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" +#include "console.h" +#include "utils.h" +#include "securec.h" +#include "json_common.h" +#include + +const char g_cmd_inspect_desc[] = "Return low-level information on a container or image"; +const char g_cmd_inspect_usage[] = "inspect [options] CONTAINER|IMAGE [CONTAINER|IMAGE...]"; + +struct client_arguments g_cmd_inspect_args = { + .format = NULL, + .time = 120, // timeout time +}; + +#define PRINTF_TAB_LEN 4 +#define TOP_LEVEL_OBJ 0x10 +#define IS_TOP_LEVEL_OBJ(value) ((value)&TOP_LEVEL_OBJ) + +#define LAST_ELEMENT_BIT 0x0F +#define NOT_LAST_ELEMENT 0x00 +#define LAST_ELEMENT 0x01 +#define IS_LAST_ELEMENT(value) (LAST_ELEMENT == ((value)&LAST_ELEMENT_BIT)) + +#define YAJL_TYPEOF(json) ((json)->type) + +#define CONTAINER_INSPECT_ERR (-1) +#define CONTAINER_NOT_FOUND (-2) + +typedef struct { + yajl_val tree_root; /* Should be free by yajl_tree_free() */ + yajl_val tree_print; /* Point to the object be printf */ +} container_tree_t; + +static yajl_val inspect_get_json_info(yajl_val element, char *key_string); +static void print_json_aux(yajl_val element, int indent, int flags); + +/* + * Parse text into a JSON tree. If text is valid JSON, returns a + * yajl_val structure, otherwise prints and error and returns null. + * Note: return tree need free by function yajl_tree_free (tree). + */ +#define ERR_BUF 1024 +static yajl_val inspect_load_json(const char *json_data) +{ + yajl_val tree = NULL; + char errbuf[ERR_BUF]; + + tree = yajl_tree_parse(json_data, errbuf, sizeof(errbuf)); + if (tree == NULL) { + ERROR("Parse json data failed %s\n", errbuf); + return NULL; + } + + return tree; +} + +static yajl_val json_get_val(yajl_val tree, const char *name, yajl_type type) +{ + const char *path[] = { name, NULL }; + return yajl_tree_get(tree, path, type); +} + +static yajl_val json_object(yajl_val element, char *key) +{ + yajl_val node = NULL; + char *top_key = key; + char *next_context = NULL; + + top_key = strtok_s(top_key, ".", &next_context); + if (top_key == NULL) { + return NULL; + } + + node = json_get_val(element, top_key, yajl_t_any); + if (node) { + node = inspect_get_json_info(node, next_context); + } + return node; +} + +static yajl_val json_array(yajl_val element, char *key) +{ + if (element == NULL || key == NULL) { + return NULL; + } + size_t i = 0; + size_t size = 0; + yajl_val node = NULL; + yajl_val value = NULL; + char *top_key = key; + char *next_context = NULL; + if (YAJL_GET_ARRAY(element) != NULL) { + size = YAJL_GET_ARRAY(element)->len; + } + top_key = strtok_s(top_key, ".", &next_context); + if (top_key == NULL) { + return NULL; + } + + for (i = 0; i < size; i++) { + value = element->u.array.values[i]; + + node = json_get_val(value, top_key, yajl_t_any); + if (node) { + node = inspect_get_json_info(node, next_context); + if (node) { + break; + } + } + } + return node; +} + +static yajl_val inspect_get_json_info(yajl_val element, char *key_string) +{ + yajl_val node = NULL; + + if (element == NULL) { + return NULL; + } + + /* If "key_string" equal to NULL, return the input element. */ + if ((key_string == NULL) || (strlen(key_string) == 0)) { + return element; + } + + switch (YAJL_TYPEOF(element)) { + case yajl_t_object: + node = json_object(element, key_string); + break; + case yajl_t_array: + node = json_array(element, key_string); + break; + case yajl_t_any: + case yajl_t_null: + case yajl_t_false: + case yajl_t_true: + case yajl_t_number: + case yajl_t_string: + default: + ERROR("unrecognized JSON type %d\n", YAJL_TYPEOF(element)); + break; + } + + return node; +} + +static bool inspect_filter_done(yajl_val root, const char *filter, container_tree_t *tree_array) +{ + yajl_val object = root; + char *key_string = NULL; + + if (filter != NULL) { + key_string = util_strdup_s(filter); + + object = inspect_get_json_info(root, key_string); + if (object == NULL) { + COMMAND_ERROR("Executing \"\" at <.%s>: map has no entry for key \"%s\"", filter, key_string); + free(key_string); + return false; + } + free(key_string); + } + + tree_array->tree_print = object; + + return true; +} + +/* + * RETURN VALUE: + * 0: inspect container success + * CONTAINER_INSPECT_ERR: have the container, but failed to inspect due to other reasons + * CONTAINER_NOT_FOUND: no such container +*/ +static int client_inspect_container(const struct lcrc_inspect_request *request, struct lcrc_inspect_response *response, + client_connect_config_t *config, const lcrc_connect_ops *ops) +{ + int ret = 0; + + ret = ops->container.inspect(request, response, config); + if (ret != 0) { + if ((strstr(response->errmsg, "Inspect invalid name") != NULL) || + (strstr(response->errmsg, "No such image or container or accelerator") != NULL)) { + return CONTAINER_NOT_FOUND; + } + + /* have the container, but failed to inspect due to other reasons */ + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = CONTAINER_INSPECT_ERR; + } + + return ret; +} + +static int client_inspect_image(const struct lcrc_inspect_request *request, struct lcrc_inspect_response *response, + client_connect_config_t *config, const lcrc_connect_ops *ops) +{ + int ret = 0; + + ret = ops->image.inspect(request, response, config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } + + return ret; +} + +/* + * Create a inspect request message and call RPC + */ +static int client_inspect(const struct client_arguments *args, const char *filter, container_tree_t *tree_array) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_inspect_request request = { 0 }; + struct lcrc_inspect_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + yajl_val tree = NULL; + + response = util_common_calloc_s(sizeof(struct lcrc_inspect_response)); + if (response == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + request.name = args->name; + request.bformat = args->format ? true : false; + request.timeout = args->time; + + ops = get_connect_client_ops(); + if (ops == NULL || ops->container.inspect == NULL || ops->image.inspect == NULL) { + ERROR("Unimplemented ops"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = client_inspect_container(&request, response, &config, ops); + if (ret == CONTAINER_NOT_FOUND) { + lcrc_inspect_response_free(response); + response = NULL; + + response = util_common_calloc_s(sizeof(struct lcrc_inspect_response)); + if (response == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + ret = client_inspect_image(&request, response, &config, ops); + } + + if (ret != 0) { + goto out; + } + + if (response == NULL || response->json == NULL) { + ERROR("Container or image json is empty"); + ret = -1; + goto out; + } + + tree = inspect_load_json(response->json); + if (tree == NULL) { + ret = -1; + goto out; + } + + if (!inspect_filter_done(tree, filter, tree_array)) { + ret = -1; + yajl_tree_free(tree); + goto out; + } + + tree_array->tree_root = tree; + +out: + lcrc_inspect_response_free(response); + return ret; +} + +static void print_json_string(yajl_val element, int flags) +{ + const char *str = YAJL_GET_STRING(element); + const char *hexchars = "0123456789ABCDEF"; + char hex[7] = { '\\', 'u', '0', '0', '\0', '\0', '\0' }; + + putchar('"'); + if (str == NULL) { + goto out; + } + + for (; *str != '\0'; str++) { + const char *escapestr = NULL; + switch (*str) { + case '\r': + escapestr = "\\r"; + break; + case '\n': + escapestr = "\\n"; + break; + case '\\': + escapestr = "\\\\"; + break; + case '"': + escapestr = "\\\""; + break; + case '\f': + escapestr = "\\f"; + break; + case '\b': + escapestr = "\\b"; + break; + case '\t': + escapestr = "\\t"; + break; + default: + if ((unsigned char)(*str) < 0x20) { + hex[4] = hexchars[(unsigned char)(*str) >> 4]; + hex[5] = hexchars[(unsigned char)(*str) & 0x0F]; + escapestr = hex; + } + break; + } + if (escapestr != NULL) { + printf("%s", escapestr); + } else { + putchar(*str); + } + } + +out: + putchar('"'); + if (!IS_LAST_ELEMENT((unsigned int)flags)) { + putchar(','); + } +} + +static void print_json_number(yajl_val element, int flags) +{ + if (IS_LAST_ELEMENT((unsigned int)flags)) { + printf("%s", YAJL_GET_NUMBER(element)); + } else { + printf("%s,", YAJL_GET_NUMBER(element)); + } +} + +static void print_json_true(int flags) +{ + if (IS_LAST_ELEMENT((unsigned int)flags)) { + printf("true"); + } else { + printf("true,"); + } +} + +static void print_json_false(int flags) +{ + if (IS_LAST_ELEMENT((unsigned int)flags)) { + printf("false"); + } else { + printf("false,"); + } +} + +static void print_json_null(int flags) +{ + if (IS_LAST_ELEMENT((unsigned int)flags)) { + printf("null"); + } else { + printf("null,"); + } +} + +static void print_json_indent(int indent, bool new_line) +{ + int i = 0; + + if (new_line) { + printf("\n"); + } + + for (i = 0; i < indent; i++) { + putchar(' '); + } +} + +static void print_json_object(yajl_val element, int indent, int flags) +{ + size_t size = 0; + const char *objkey = NULL; + yajl_val value = NULL; + size_t i = 0; + if (element == NULL) { + return; + } + if (YAJL_GET_OBJECT(element) != NULL) { + size = YAJL_GET_OBJECT(element)->len; + } + if (IS_TOP_LEVEL_OBJ((unsigned int)flags)) { + print_json_indent(indent, false); + } + + if (size == 0) { + if (IS_LAST_ELEMENT((unsigned int)flags)) { + printf("{}"); + } else { + printf("{},"); + } + return; + } + + printf("{"); + + for (i = 0; i < size; i++) { + print_json_indent(indent + PRINTF_TAB_LEN, true); + objkey = element->u.object.keys[i]; + value = element->u.object.values[i]; + + printf("\"%s\": ", objkey); + if ((i + 1) == size) { + print_json_aux(value, indent + PRINTF_TAB_LEN, LAST_ELEMENT); + } else { + print_json_aux(value, indent + PRINTF_TAB_LEN, NOT_LAST_ELEMENT); + } + } + + print_json_indent(indent, true); + + if (IS_LAST_ELEMENT((unsigned int)flags)) { + printf("}"); + } else { + printf("},"); + } +} + +static void print_json_array(yajl_val element, int indent, int flags) +{ + size_t i = 0; + size_t size = 0; + yajl_val value = NULL; + + if (element == NULL) { + return; + } + if (YAJL_GET_ARRAY(element) != NULL) { + size = YAJL_GET_ARRAY(element)->len; + } + + if (IS_TOP_LEVEL_OBJ((unsigned int)flags)) { + print_json_indent(indent, false); + } + + if (size == 0) { + if (IS_LAST_ELEMENT((unsigned int)flags)) { + printf("[]"); + } else { + printf("[],"); + } + return; + } + + printf("["); + + for (i = 0; i < size; i++) { + print_json_indent(indent + PRINTF_TAB_LEN, true); + value = element->u.array.values[i]; + + if ((i + 1) == size) { + print_json_aux(value, indent + PRINTF_TAB_LEN, LAST_ELEMENT); + } else { + print_json_aux(value, indent + PRINTF_TAB_LEN, NOT_LAST_ELEMENT); + } + } + print_json_indent(indent, true); + + if (IS_LAST_ELEMENT((unsigned int)flags)) { + printf("]"); + } else { + printf("],"); + } +} + +static void print_json_aux(yajl_val element, int indent, int flags) +{ + if (element == NULL) { + return; + } + + switch (YAJL_TYPEOF(element)) { + case yajl_t_object: + print_json_object(element, indent, flags); + break; + case yajl_t_array: + print_json_array(element, indent, flags); + break; + case yajl_t_string: + print_json_string(element, flags); + break; + case yajl_t_number: + print_json_number(element, flags); + break; + case yajl_t_true: + print_json_true(flags); + break; + case yajl_t_false: + print_json_false(flags); + break; + case yajl_t_null: + print_json_null(flags); + break; + case yajl_t_any: + default: + ERROR("unrecognized JSON type %d\n", YAJL_TYPEOF(element)); + break; + } +} + +/* + * Print yajl tree as JSON format. + */ +static void print_json(yajl_val tree, int indent) +{ + if (tree == NULL) { + return; + } + + print_json_aux(tree, indent, LAST_ELEMENT | TOP_LEVEL_OBJ); +} + +static void inspect_show_result(int show_nums, const container_tree_t *tree_array, const char *format) +{ + int i = 0; + yajl_val tree = NULL; + int indent = 0; + + if (show_nums == 0) { + if (format == NULL) { + printf("[]\n"); + } + return; + } + + if (format == NULL) { + printf("[\n"); + indent = PRINTF_TAB_LEN; + } + + for (i = 0; i < show_nums; i++) { + tree = tree_array[i].tree_print; + print_json(tree, indent); + + if ((i + 1) != show_nums) { + if (format == NULL) { + printf(",\n"); + } else { + printf("\n"); + } + } + } + + if (format == NULL) { + printf("\n]\n"); + } else { + printf("\n"); + } +} + +static void inspect_free_trees(int tree_nums, container_tree_t *tree_array) +{ + int i = 0; + + for (i = 0; i < tree_nums; i++) { + if (tree_array[i].tree_root) { + yajl_tree_free(tree_array[i].tree_root); + tree_array[i].tree_root = NULL; + tree_array[i].tree_print = NULL; + } + } +} + +/* arg string format: "{{json .State.Running}}" + * ret_string should be free outside by free(). + */ +static char *inspect_pause_filter(const char *arg) +{ + char *input_str = NULL; + char *p = NULL; + char *ret_string = NULL; + char *next_context = NULL; + + input_str = util_strdup_s(arg); + + p = strtok_s(input_str, ".", &next_context); + if (p == NULL) { + goto out; + } + + p = next_context; + if (p == NULL) { + goto out; + } + + p = strtok_s(p, " }", &next_context); + if (p == NULL) { + goto out; + } + + ret_string = util_strdup_s(p); + +out: + if (input_str != NULL) { + free(input_str); + } + + return ret_string; +} + +#define MATCH_NUM 1 +#define CHECK_FAILED (-1) +#define JSON_ARGS "^\\s*\\{\\s*\\{\\s*json\\s+[^\\s]+\\s*.*\\}\\s*\\}\\s*$" + +static int inspect_check(const char *json_str, const char *regex) +{ + int status = 0; + regmatch_t pmatch[MATCH_NUM] = { { 0 } }; + regex_t reg; + + if (json_str == NULL) { + ERROR("Filter string is NULL."); + return CHECK_FAILED; + } + + regcomp(®, regex, REG_EXTENDED); + + status = regexec(®, json_str, MATCH_NUM, pmatch, 0); + regfree(®); + + if (status != 0) { + /* Log by caller */ + return CHECK_FAILED; + } + + return 0; +} + +static int inspect_check_format_f(const char *json_str) +{ + int ret = 0; + + if (json_str == NULL) { + ERROR("Filter string is NULL."); + return CHECK_FAILED; + } + + /* check "{{json .xxx.xxx}}" */ + ret = inspect_check(json_str, "^\\s*\\{\\s*\\{\\s*json\\s+(\\.\\w+)+\\s*\\}\\s*\\}\\s*$"); + if (ret == 0) { + return 0; + } + + /* check "{{ ... }}" */ + ret = inspect_check(json_str, "^\\s*\\{\\s*\\{\\s*[^{}]*\\s*\\}\\s*\\}\\s*$"); + if (ret != 0) { + COMMAND_ERROR("Unexpected \"{\" or \"}\" in operand, should be \"{{ ... }}\"."); + goto out; + } + + /* json args. */ + ret = inspect_check(json_str, "^\\s*\\{\\s*\\{\\s*json\\s*\\}\\s*\\}\\s*$"); + if (ret == 0) { + COMMAND_ERROR("Executing \"\" at : wrong number of args for json: want 1 got 0."); + goto out; + } + + /* check "{{json... }}" */ + ret = inspect_check(json_str, "^\\s*\\{\\s*\\{\\s*json\\W.*\\s*\\}\\s*\\}\\s*$"); + if (ret != 0) { + COMMAND_ERROR("Output mode error," + "support function \"json\" only. E.g \"{{json ... }}\" is right."); + goto out; + } + + /* json args. */ + ret = inspect_check(json_str, JSON_ARGS); + if (ret != 0) { + COMMAND_ERROR("Executing \"\" at : wrong number of args for json: want 1 got 0."); + goto out; + } + + /* "{{json .xxx.xxx }}" check failed log */ + COMMAND_ERROR("Unexpected <.> in operand. E.g \"{{json .xxx.xxx }}\" is right."); + +out: + + return CHECK_FAILED; +} + +int cmd_inspect_main(int argc, const char **argv) +{ + int i = 0; + int status = 0; + struct log_config lconf = { 0 }; + int success_counts = 0; + char *filter_string = NULL; + container_tree_t *tree_array = NULL; + size_t array_size = 0; + command_t cmd; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_inspect_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_inspect_args.progname = argv[0]; + struct command_option options[] = { + LOG_OPTIONS(lconf), + INSPECT_OPTIONS(g_cmd_inspect_args), + COMMON_OPTIONS(g_cmd_inspect_args) + }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_inspect_desc, + g_cmd_inspect_usage); + if (command_parse_args(&cmd, &g_cmd_inspect_args.argc, &g_cmd_inspect_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed"); + exit(ECOMMON); + } + + if (g_cmd_inspect_args.argc == 0) { + COMMAND_ERROR("\"inspect\" requires a minimum of 1 argument."); + exit(EINVALIDARGS); + } + + if (g_cmd_inspect_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many arguments."); + exit(EINVALIDARGS); + } + + if ((size_t)g_cmd_inspect_args.argc > SIZE_MAX / sizeof(container_tree_t) - 1) { + COMMAND_ERROR("The number of parameters of inspect is too large"); + exit(ECOMMON); + } + array_size = sizeof(container_tree_t) * (size_t)(g_cmd_inspect_args.argc + 1); + tree_array = (container_tree_t *)util_common_calloc_s(array_size); + if (tree_array == NULL) { + ERROR("Malloc failed\n"); + exit(ECOMMON); + } + + if (g_cmd_inspect_args.format != NULL) { + int ret; + ret = inspect_check_format_f(g_cmd_inspect_args.format); + if (ret != 0) { + free(tree_array); + tree_array = NULL; + exit(ECOMMON); + } + + filter_string = inspect_pause_filter(g_cmd_inspect_args.format); + + if (filter_string == NULL) { + COMMAND_ERROR("Inspect format parameter invalid: %s", g_cmd_inspect_args.format); + free(tree_array); + tree_array = NULL; + exit(EINVALIDARGS); + } + } + + for (i = 0; i < g_cmd_inspect_args.argc; i++) { + g_cmd_inspect_args.name = g_cmd_inspect_args.argv[i]; + + if (client_inspect(&g_cmd_inspect_args, filter_string, &tree_array[i])) { + status = -1; + break; + } + success_counts++; + } + + if (tree_array != NULL) { + inspect_show_result(success_counts, tree_array, g_cmd_inspect_args.format); + inspect_free_trees(success_counts, tree_array); + } + free(tree_array); + free(filter_string); + + if (status) { + exit(ECOMMON); + } + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/information/inspect.h b/src/cmd/lcrc/information/inspect.h new file mode 100644 index 0000000..b4a12fe --- /dev/null +++ b/src/cmd/lcrc/information/inspect.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container inspect definition + ******************************************************************************/ +#ifndef __CMD_INSPECT_H +#define __CMD_INSPECT_H + +#include "arguments.h" + +#define INSPECT_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_STRING, false, "format", 'f', &(cmdargs).format, \ + "Format the output using the given go template", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "time", 't', &(cmdargs).time, \ + "Seconds to wait for inspect timeout (default 120)", command_convert_int } + +extern const char g_cmd_inspect_desc[]; +extern const char g_cmd_inspect_usage[]; +extern struct client_arguments g_cmd_inspect_args; +int cmd_inspect_main(int argc, const char **argv); + +#endif /* __CMD_INSPECT_H */ diff --git a/src/cmd/lcrc/information/logs.c b/src/cmd/lcrc/information/logs.c new file mode 100644 index 0000000..7985d66 --- /dev/null +++ b/src/cmd/lcrc/information/logs.c @@ -0,0 +1,152 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container logs functions + ******************************************************************************/ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "error.h" + +#include "securec.h" +#include "logs.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_logs_desc[] = "Fetch the logs of a container"; +const char g_cmd_logs_usage[] = "logs [OPTIONS] CONTAINER"; + +struct client_arguments g_cmd_logs_args = { + .follow = false, + .tail = -1, +}; + +static int do_logs(const struct client_arguments *args) +{ +#define DISABLE_ERR_MESSAGE "disable console log" + lcrc_connect_ops *ops = NULL; + struct lcrc_logs_request *request = NULL; + struct lcrc_logs_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_container_conf_response)); + if (response == NULL) { + ERROR("Log: Out of memory"); + return -1; + } + request = util_common_calloc_s(sizeof(struct lcrc_logs_request)); + if (request == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + ops = get_connect_client_ops(); + if (ops == NULL || ops->container.logs == NULL) { + ERROR("Unimplemented logs op"); + ret = -1; + goto out; + } + + request->id = util_strdup_s(args->name); + request->runtime = util_strdup_s(args->runtime); + request->follow = args->follow; + request->tail = (int64_t)args->tail; + + config = get_connect_config(args); + ret = ops->container.logs(request, response, &config); + if (ret != 0) { + if (strncmp(response->errmsg, DISABLE_ERR_MESSAGE, strlen(DISABLE_ERR_MESSAGE)) == 0) { + fprintf(stdout, "[WARNING]: Container %s disable console log!\n", args->name); + ret = 0; + goto out; + } + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = -1; + goto out; + } + +out: + lcrc_logs_response_free(response); + lcrc_logs_request_free(request); + return ret; +} + +int callback_tail(command_option_t *option, const char *arg) +{ + if (util_safe_llong(arg, option->data)) { + *(long long *)option->data = -1; + } + return 0; +} + +static int cmd_logs_init(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + command_t cmd; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_logs_args)) { + COMMAND_ERROR("client arguments init failed\n"); + return ECOMMON; + } + g_cmd_logs_args.progname = argv[0]; + struct command_option options[] = { + LOG_OPTIONS(lconf), + LOGS_OPTIONS(g_cmd_logs_args), + COMMON_OPTIONS(g_cmd_logs_args) + }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_logs_desc, + g_cmd_logs_usage); + if (command_parse_args(&cmd, &g_cmd_logs_args.argc, &g_cmd_logs_args.argv)) { + return EINVALIDARGS; + } + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed\n"); + g_cmd_logs_args.name = g_cmd_logs_args.argv[0]; + return ECOMMON; + } + + if (g_cmd_logs_args.argc != 1) { + COMMAND_ERROR("Logs needs one container name"); + return ECOMMON; + } + + return 0; +} + +int cmd_logs_main(int argc, const char **argv) +{ + int ret = 0; + + ret = cmd_logs_init(argc, argv); + if (ret != 0) { + exit(ret); + } + + g_cmd_logs_args.name = g_cmd_logs_args.argv[0]; + ret = do_logs(&g_cmd_logs_args); + if (ret != 0) { + exit(ECOMMON); + } + return 0; +} diff --git a/src/cmd/lcrc/information/logs.h b/src/cmd/lcrc/information/logs.h new file mode 100644 index 0000000..ee94c82 --- /dev/null +++ b/src/cmd/lcrc/information/logs.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container logs definition + ******************************************************************************/ +#ifndef __CMD_LOGS_H +#define __CMD_LOGS_H + +#include "arguments.h" + +#define LOGS_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "follow", 'f', &(cmdargs).follow, "Follow log output", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "tail", 0, &(cmdargs).tail, \ + "Number of lines to show from the end of the logs", callback_tail } + +extern const char g_cmd_logs_desc[]; +extern const char g_cmd_logs_usage[]; +extern struct client_arguments g_cmd_logs_args; + +int callback_tail(command_option_t *option, const char *arg); +int cmd_logs_main(int argc, const char **argv); +#endif /* __CMD_LOGS_H */ diff --git a/src/cmd/lcrc/information/ps.c b/src/cmd/lcrc/information/ps.c new file mode 100644 index 0000000..faeb2d6 --- /dev/null +++ b/src/cmd/lcrc/information/ps.c @@ -0,0 +1,411 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container ps functions + ******************************************************************************/ +#include +#include +#include "securec.h" +#include "arguments.h" +#include "ps.h" +#include "utils.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_list_desc[] = "List containers"; +const char g_cmd_list_usage[] = "ps [command options]"; + +#define COMMAND_LENGTH_MAX 22 +#define TIME_DURATION_MAX_LEN 32 + +struct client_arguments g_cmd_list_args = { + .dispname = false, + .list_all = false, +}; + +/* keep track of field widths for printing. */ +struct lengths { + unsigned int id_length; + unsigned int state_length; + unsigned int image_length; + unsigned int command_length; + unsigned int init_length; + unsigned int exit_length; + unsigned int rscont_length; + unsigned int startat_length; + unsigned int finishat_length; + unsigned int runtime_length; + unsigned int name_length; +}; + +const char * const g_containerstatusstr[] = { "unknown", "inited", "starting", "running", + "exited", "paused", "restarting" + }; + +static const char *lcrc_lcrsta2str(Container_Status sta) +{ + if (sta >= CONTAINER_STATUS_MAX_STATE) { + return NULL; + } + return g_containerstatusstr[sta]; +} + +static void list_print_quiet(struct lcrc_container_summary_info **info, const size_t size, + const struct lengths *length) +{ + const char *status = NULL; + size_t i = 0; + + for (i = 0; i < size; i++) { + const struct lcrc_container_summary_info *in = NULL; + in = info[i]; + status = lcrc_lcrsta2str(in->status); + if (status == NULL) { + continue; + } + + printf("%-*s ", (int)length->id_length, in->id ? in->id : "-"); + printf("\n"); + } +} + +static int mix_container_status(const struct lcrc_container_summary_info *in, char *status, size_t len) +{ + int ret = 0; + const char *container_status = NULL; + + container_status = lcrc_lcrsta2str(in->status); + if (container_status == NULL) { + ret = strcpy_s(status, len, "-"); + if (ret < 0) { + ERROR("Failed to copy string"); + ret = -1; + goto out; + } + } else { + if (strcpy_s(status, len, container_status) != EOK) { + ERROR("Failed to copy string"); + ret = -1; + goto out; + } + if (in->health_state != NULL) { + if (strcat_s(status, len, in->health_state) != EOK) { + ERROR("Failed to cat string"); + ret = -1; + goto out; + } + } + } + +out: + return ret; +} + +static void ps_print_header(struct lengths *length) +{ + /* print header */ + printf("%-*s ", (int)length->state_length, "STATUS"); + printf("%-*s ", (int)length->init_length, "PID"); + printf("%-*s ", (int)length->image_length, "IMAGE"); + if (length->command_length > COMMAND_LENGTH_MAX) { + printf("%-*s ", COMMAND_LENGTH_MAX, "COMMAND"); + length->command_length = COMMAND_LENGTH_MAX; + } else { + printf("%-*s ", (int)length->command_length, "COMMAND"); + } + printf("%-*s ", (int)length->exit_length, "EXIT_CODE"); + printf("%-*s ", (int)length->rscont_length, "RESTART_COUNT"); + printf("%-*s ", (int)length->startat_length, "STARTAT"); + printf("%-*s ", (int)length->finishat_length, "FINISHAT"); + printf("%-*s ", (int)length->runtime_length, "RUNTIME"); + printf("%-*s ", (int)length->id_length, "ID"); + printf("%-*s ", (int)length->name_length, "NAMES"); + printf("\n"); +} + +static void ps_print_container_info_pre(const struct lcrc_container_summary_info *in, const char *status, + const struct lengths *length) +{ + const char *cmd = (in->command != NULL) ? in->command : "-"; + int cmd_len = (int)strlen(cmd); + + printf("%-*s ", (int)length->state_length, status); + if (in->has_pid) { + printf("%-*u ", (int)length->init_length, in->pid); + } else { + printf("%-*s ", (int)length->init_length, "-"); + } + printf("%-*s ", (int)length->image_length, in->image ? in->image : "none"); + if (cmd_len > COMMAND_LENGTH_MAX - 2) { + printf("\"%-*.*s...\" ", COMMAND_LENGTH_MAX - 5, COMMAND_LENGTH_MAX - 5, cmd); + } else { + int space_len = ((int)(length->command_length) - cmd_len) - 2; + printf("\"%-*.*s\"%*s ", cmd_len, cmd_len, cmd, space_len, (space_len == 0) ? "" : " "); + } +} + +static void ps_print_container_info(const struct lcrc_container_summary_info *in, const char *status, + const struct lengths *length) +{ + char finishat_duration[TIME_DURATION_MAX_LEN] = { 0 }; + char startat_duration[TIME_DURATION_MAX_LEN] = { 0 }; + + ps_print_container_info_pre(in, status, length); + + printf("%-*u ", (int)length->exit_length, in->exit_code); + printf("%-*u ", (int)length->rscont_length, in->restart_count); + time_format_duration(in->startat, startat_duration, sizeof(startat_duration)); + printf("%-*s ", (int)length->startat_length, in->startat ? startat_duration : "-"); + time_format_duration(in->finishat, finishat_duration, sizeof(finishat_duration)); + printf("%-*s ", (int)length->finishat_length, in->finishat ? finishat_duration : "-"); + printf("%-*s ", (int)length->runtime_length, in->runtime ? in->runtime : "lcr"); + printf("%-*.*s ", (int)length->id_length, (int)length->id_length, in->id ? in->id : "-"); + printf("%-*s ", (int)length->name_length, in->name ? in->name : "-"); + printf("\n"); +} + +static void list_print_table(struct lcrc_container_summary_info **info, const size_t size, struct lengths *length) +{ + const struct lcrc_container_summary_info *in = NULL; + size_t i = 0; + char status[32] = { 0 }; + + ps_print_header(length); + + for (i = 0; i < size; i++) { + in = info[i]; + if (mix_container_status(in, status, sizeof(status))) { + return; + } + + ps_print_container_info(in, status, length); + } +} + +static void calculate_str_length(const char *str, unsigned int *length) +{ + size_t len = 0; + + if (str == NULL) { + return; + } + + len = strlen(str); + if (len > (*length)) { + *length = (unsigned int)len; + } +} + +static void calculate_status_str_length(const struct lcrc_container_summary_info *in, unsigned int *length) +{ + const char *status = NULL; + + status = lcrc_lcrsta2str(in->status); + if (status != NULL) { + size_t len; + len = strlen(status); + if (in->health_state != NULL) { + len += strlen(in->health_state); + } + if (len > (*length)) { + *length = (unsigned int)len; + } + } +} + +static void calculate_uint_str_length(uint32_t data, unsigned int *length) +{ + int len = 0; + char tmpbuffer[UINT_LEN + 1] = { 0 }; + + len = sprintf_s(tmpbuffer, sizeof(tmpbuffer), "%u", data); + if (len < 0) { + ERROR("sprintf buffer failed"); + return; + } + if ((unsigned int)len > (*length)) { + *length = (unsigned int)len; + } +} + +static void calculate_time_str_length(const char *str, unsigned int *length) +{ + size_t len = 0; + char time_duration[TIME_DURATION_MAX_LEN]; + + if (time_format_duration(str, time_duration, sizeof(time_duration)) < 0) { + ERROR("Format time duration failed"); + } + len = strlen(time_duration); + if (len > (*length)) { + *length = (unsigned int)len; + } +} + +static void list_field_width(struct lcrc_container_summary_info **info, const size_t size, struct lengths *l) +{ + size_t i = 0; + const struct lcrc_container_summary_info *in = NULL; + + if (info == NULL || l == NULL) { + return; + } + + for (i = 0; i < size; i++, in++) { + in = info[i]; + calculate_str_length(in->name, &l->name_length); + calculate_str_length(in->runtime, &l->runtime_length); + calculate_status_str_length(in, &l->state_length); + if (in->pid != -1) { + calculate_uint_str_length(in->pid, &l->init_length); + } + + calculate_str_length(in->image, &l->image_length); + if (in->command != NULL) { + size_t cmd_len; + cmd_len = strlen(in->command) + 2; + if (cmd_len > l->command_length) { + l->command_length = (unsigned int)cmd_len; + } + } + + calculate_uint_str_length(in->exit_code, &l->exit_length); + + calculate_uint_str_length(in->restart_count, &l->rscont_length); + + if (in->startat != NULL) { + calculate_time_str_length(in->startat, &l->startat_length); + } + + if (in->finishat != NULL) { + calculate_time_str_length(in->finishat, &l->finishat_length); + } + } +} + +/* +* used by qsort function for comparing container start time +*/ +static inline int lcrc_container_cmp(struct lcrc_container_summary_info **first, + struct lcrc_container_summary_info **second) +{ + return strcmp((*second)->startat, (*first)->startat); +} + +/* +* Create a list request message and call RPC +*/ +static int client_list(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_list_request request = { 0 }; + struct lcrc_list_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + struct lengths max_len = { + .name_length = 5, /* NAMES */ + .id_length = 12, /* ID */ + .state_length = 5, /* STATE */ + .image_length = 5, /* IMAGE */ + .command_length = 7, /* COMMAND */ + .init_length = 3, /* PID */ + .exit_length = 9, /* EXIT_CODE*/ + .rscont_length = 13, /* RESTART_COUNT*/ + .startat_length = 7, /* STARTAT*/ + .finishat_length = 8, /* FINISHAT*/ + .runtime_length = 7, /* RUNTIME */ + }; + + response = util_common_calloc_s(sizeof(struct lcrc_list_response)); + if (response == NULL) { + ERROR("Out of memory"); + return -1; + } + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.list) { + ERROR("Unimplemented ops"); + ret = -1; + goto out; + } + + if (args->filters != NULL) { + request.filters = lcrc_filters_parse_args((const char **)args->filters, util_array_len(args->filters)); + if (!request.filters) { + ERROR("Failed to parse filters args"); + ret = -1; + goto out; + } + } + request.all = args->list_all; + + config = get_connect_config(args); + ret = ops->container.list(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + goto out; + } + if (response->container_num != 0) + qsort(response->container_summary, (size_t)(response->container_num), + sizeof(struct lcrc_container_summary_info *), (int (*)(const void *, const void *))lcrc_container_cmp); + + if (args->dispname) { + list_print_quiet(response->container_summary, response->container_num, &max_len); + } else { + list_field_width(response->container_summary, response->container_num, &max_len); + list_print_table(response->container_summary, response->container_num, &max_len); + } + +out: + lcrc_filters_free(request.filters); + lcrc_list_response_free(response); + return ret; +} + +int cmd_list_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + command_t cmd; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_list_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_list_args.progname = argv[0]; + struct command_option options[] = { + LOG_OPTIONS(lconf), + LIST_OPTIONS(g_cmd_list_args), + COMMON_OPTIONS(g_cmd_list_args) + }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_list_desc, + g_cmd_list_usage); + if (command_parse_args(&cmd, &g_cmd_list_args.argc, &g_cmd_list_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("PS: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_list_args.argc > 0) { + COMMAND_ERROR("%s: \"ps\" requires 0 arguments.", g_cmd_list_args.progname); + exit(ECOMMON); + } + if (client_list(&g_cmd_list_args)) { + ERROR("Can not ps any containers"); + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/information/ps.h b/src/cmd/lcrc/information/ps.h new file mode 100644 index 0000000..e7d2bf4 --- /dev/null +++ b/src/cmd/lcrc/information/ps.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container ps definition + ******************************************************************************/ +#ifndef __CMD_LIST_H +#define __CMD_LIST_H + +#include "arguments.h" + +#define LIST_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_BOOL, false, "all", 'a', &(cmdargs).list_all, \ + "Display all containers (default shows just running)", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "quiet", 'q', &(cmdargs).dispname, "Only display numeric IDs", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "filter", 'f', &(cmdargs).filters, \ + "Filter output based on conditions provided", command_append_array } + +extern const char g_cmd_list_desc[]; +extern const char g_cmd_list_usage[]; +extern struct client_arguments g_cmd_list_args; +int cmd_list_main(int argc, const char **argv); + +#endif /* __CMD_LIST_H */ diff --git a/src/cmd/lcrc/information/top.c b/src/cmd/lcrc/information/top.c new file mode 100644 index 0000000..e91d61e --- /dev/null +++ b/src/cmd/lcrc/information/top.c @@ -0,0 +1,156 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container remove functions + ******************************************************************************/ +#include "top.h" +#include +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" +#include "commands.h" +#include "console.h" +#include "utils.h" +#include "securec.h" +#include "container_inspect.h" +#include "attach.h" +#include "commander.h" + +const char g_cmd_top_desc[] = "Display the running processes of a container"; +const char g_cmd_top_usage[] = "top [OPTIONS] CONTAINER [ps OPTIONS]"; + +struct client_arguments g_cmd_top_args = {}; +static void client_top_info_server(const struct lcrc_top_response *response) +{ + size_t i; + + if (response->titles != NULL) { + printf("%s\n", response->titles); + } + + if (response->processes_len == 0 || response->processes == NULL) { + return; + } + + for (i = 0; i < response->processes_len; i++) { + printf("%s\n", response->processes[i]); + } +} + +/* +* Create a rm request message and call RPC +*/ +static int client_top(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_top_request request = { 0 }; + struct lcrc_top_response *response = NULL; + container_inspect *inspect_data = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_top_response)); + if (response == NULL) { + ERROR("TOP: Out of memory"); + return -1; + } + + if (inspect_container(args, &inspect_data)) { + ERROR("inspect data error"); + ret = -1; + goto out; + } + + if (inspect_data == NULL) { + ERROR("inspect data is null"); + ret = -1; + goto out; + } + + if (inspect_data->state != NULL && !inspect_data->state->running) { + COMMAND_ERROR("You cannot attach to a stopped container, start it first"); + ret = -1; + goto out; + } + + if (inspect_data->state != NULL && inspect_data->state->restarting) { + COMMAND_ERROR("You cannot attach to a restarting container, wait until it is running"); + ret = -1; + goto out; + } + + request.name = args->name; + request.ps_argc = args->argc; + request.ps_args = (char **)args->argv; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.top) { + ERROR("Unimplemented top op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + + ret = ops->container.top(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } + client_top_info_server(response); + +out: + free_container_inspect(inspect_data); + lcrc_top_response_free(response); + return ret; +} + +int cmd_top_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + command_t cmd; + struct command_option options[] = { LOG_OPTIONS(lconf), COMMON_OPTIONS(g_cmd_top_args) }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_top_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_top_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_top_desc, + g_cmd_top_usage); + + if (command_parse_args(&cmd, &g_cmd_top_args.argc, &g_cmd_top_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Top: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_top_args.argc < 1) { + COMMAND_ERROR("\"%s top\" requires at least 1 argument(s).", g_cmd_top_args.progname); + COMMAND_ERROR("See '%s top --help'.", g_cmd_top_args.progname); + exit(EINVALIDARGS); + } else { + g_cmd_top_args.name = g_cmd_top_args.argv[0]; + g_cmd_top_args.argc--; + g_cmd_top_args.argv++; + } + + if (client_top(&g_cmd_top_args) != 0) { + ERROR("Container \"%s\" top failed", g_cmd_top_args.name); + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} + diff --git a/src/cmd/lcrc/information/top.h b/src/cmd/lcrc/information/top.h new file mode 100644 index 0000000..9da190c --- /dev/null +++ b/src/cmd/lcrc/information/top.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container top definition + ******************************************************************************/ +#ifndef __CMD_TOP_H +#define __CMD_TOP_H + +#include "arguments.h" + +extern const char g_cmd_top_desc[]; +extern const char g_cmd_top_usage[]; +extern struct client_arguments g_cmd_top_args; +int cmd_top_main(int argc, const char **argv); + +#endif /* __CMD_TOP_H */ diff --git a/src/cmd/lcrc/information/version.c b/src/cmd/lcrc/information/version.c new file mode 100644 index 0000000..03581ac --- /dev/null +++ b/src/cmd/lcrc/information/version.c @@ -0,0 +1,124 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container version functions + ******************************************************************************/ +#include "version.h" +#include +#include +#include "securec.h" + +#include "utils.h" +#include "arguments.h" +#include "log.h" +#include "config.h" +#include "lcrc_connect.h" + +const char g_cmd_version_desc[] = "Display information about lcrc"; +const char g_cmd_version_usage[] = "version"; + +struct client_arguments g_cmd_version_args = {}; + +static void client_version_info_client() +{ + printf("Client:\n"); + printf(" Version:\t%s\n", VERSION); + printf(" Git commit:\t%s\n", LCRD_GIT_COMMIT); + printf(" Built:\t%s\n", LCRD_BUILD_TIME); + printf("\n"); +} +static void client_version_info_oci_config() +{ + printf("OCI config:\n"); + printf(" Version:\t%s\n", OCI_VERSION); + printf(" Default file:\t%s\n", OCICONFIG_PATH); + printf("\n"); +} +static void client_version_info_server(const struct lcrc_version_response *response) +{ + printf("Server:\n"); + printf(" Version:\t%s\n", response->version ? response->version : ""); + printf(" Git commit:\t%s\n", response->git_commit ? response->git_commit : ""); + printf(" Built:\t%s\n", response->build_time ? response->build_time : ""); + printf("\n"); +} + +static int client_version(const struct client_arguments *args) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_version_request request = { 0 }; + struct lcrc_version_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_version_response)); + if (response == NULL) { + ERROR("Version: Out of memory"); + return -1; + } + + client_version_info_client(); + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.version) { + ERROR("Unimplemented version op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.version(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + goto out; + } + + client_version_info_server(response); + client_version_info_oci_config(); +out: + lcrc_version_response_free(response); + return ret; +} + +int cmd_version_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + command_t cmd; + struct command_option options[] = { LOG_OPTIONS(lconf), COMMON_OPTIONS(g_cmd_version_args) }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_version_args)) { + COMMAND_ERROR("client arguments init failed\n"); + exit(ECOMMON); + } + g_cmd_version_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_version_desc, + g_cmd_version_usage); + if (command_parse_args(&cmd, &g_cmd_version_args.argc, &g_cmd_version_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Version: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_version_args.argc > 0) { + COMMAND_ERROR("%s: \"version\" requires 0 arguments.", g_cmd_version_args.progname); + exit(ECOMMON); + } + + if (client_version(&g_cmd_version_args)) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/information/version.h b/src/cmd/lcrc/information/version.h new file mode 100644 index 0000000..fa6291b --- /dev/null +++ b/src/cmd/lcrc/information/version.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container version definition + ******************************************************************************/ +#ifndef __CMD_VERSION_H +#define __CMD_VERSION_H + +#include "arguments.h" + +extern const char g_cmd_version_desc[]; +extern const char g_cmd_version_usage[]; +extern struct client_arguments g_cmd_version_args; +int cmd_version_main(int argc, const char **argv); + +#endif /* __CMD_VERSION_H */ diff --git a/src/cmd/lcrc/information/wait.c b/src/cmd/lcrc/information/wait.c new file mode 100644 index 0000000..9b29281 --- /dev/null +++ b/src/cmd/lcrc/information/wait.c @@ -0,0 +1,133 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container wait functions + ******************************************************************************/ +#include "error.h" +#include "securec.h" +#include "wait.h" +#include "arguments.h" +#include "log.h" +#include "lcrc_connect.h" + +const char g_cmd_wait_desc[] = "Block until one or more containers stop, then print their exit codes"; +const char g_cmd_wait_usage[] = "wait [OPTIONS] CONTAINER [CONTAINER...]"; + +struct client_arguments g_cmd_wait_args = {}; + +/* +* Create a delete request message and call RPC +*/ +int client_wait(const struct client_arguments *args, unsigned int *exit_code) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_wait_request request = { 0 }; + struct lcrc_wait_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_wait_response)); + if (response == NULL) { + ERROR("Wait: Out of memory"); + return -1; + } + + request.id = args->name; + if (args->custom_conf.auto_remove == false) { + request.condition = WAIT_CONDITION_STOPPED; + } else { + request.condition = WAIT_CONDITION_REMOVED; + } + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.wait) { + ERROR("Unimplemented wait op"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.wait(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + if (response->cc) { + ret = ESERVERERROR; + } else { + ret = ECOMMON; + } + goto out; + } + if (exit_code != NULL) { + *exit_code = (unsigned int)response->exit_code; + } + +out: + lcrc_wait_response_free(response); + return ret; +} + +int cmd_wait_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + unsigned int exit_code = 0; + int i = 0; + int status = 0; + command_t cmd; + struct command_option options[] = { LOG_OPTIONS(lconf), COMMON_OPTIONS(g_cmd_wait_args) }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_wait_args)) { + COMMAND_ERROR("client arguments init failed"); + exit(ECOMMON); + } + g_cmd_wait_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_wait_desc, + g_cmd_wait_usage); + if (command_parse_args(&cmd, &g_cmd_wait_args.argc, &g_cmd_wait_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("Wait: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_wait_args.socket == NULL) { + COMMAND_ERROR("Missing --host,-H option"); + exit(EINVALIDARGS); + } + + if (g_cmd_wait_args.argc == 0) { + COMMAND_ERROR("Wait requires at least 1 container names"); + exit(EINVALIDARGS); + } + + if (g_cmd_wait_args.argc >= MAX_CLIENT_ARGS) { + COMMAND_ERROR("You specify too many containers to wait."); + exit(EINVALIDARGS); + } + + for (i = 0; i < g_cmd_wait_args.argc; i++) { + g_cmd_wait_args.name = g_cmd_wait_args.argv[i]; + if (client_wait(&g_cmd_wait_args, &exit_code)) { + ERROR("Container \"%s\" wait failed", g_cmd_wait_args.name); + status = -1; + continue; + } + printf("%u\n", exit_code); + } + + if (status) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/information/wait.h b/src/cmd/lcrc/information/wait.h new file mode 100644 index 0000000..658f73e --- /dev/null +++ b/src/cmd/lcrc/information/wait.h @@ -0,0 +1,26 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container wait definition + ******************************************************************************/ +#ifndef __CMD_WAIT_H +#define __CMD_WAIT_H + +#include "arguments.h" + +extern const char g_cmd_wait_desc[]; +extern const char g_cmd_wait_usage[]; +extern struct client_arguments g_cmd_wait_args; +int cmd_wait_main(int argc, const char **argv); +int client_wait(const struct client_arguments *args, unsigned int *exit_code); + +#endif /* __CMD_WAIT_H */ diff --git a/src/cmd/lcrc/main.c b/src/cmd/lcrc/main.c new file mode 100644 index 0000000..6fe1590 --- /dev/null +++ b/src/cmd/lcrc/main.c @@ -0,0 +1,188 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide init process of lcrc + ******************************************************************************/ + +#include +#include +#include +#include +#include "commands.h" +#include "create.h" +#include "ps.h" +#include "rm.h" +#include "start.h" +#include "inspect.h" +#include "stop.h" +#include "exec.h" +#include "run.h" +#include "images.h" +#include "rmi.h" +#include "wait.h" +#include "restart.h" +#include "logs.h" +#include "kill.h" +#include "load.h" +#include "update.h" +#include "attach.h" +#include "info.h" +#include "stats.h" +#include "export.h" +#include "cp.h" +#include "top.h" +#include "pull.h" +#include "login.h" +#include "logout.h" +#include "lcrc_connect.h" +#include "version.h" +#include "rename.h" +#include "utils.h" + +// The list of our supported commands +struct command g_commands[] = { + { + // `create` sub-command + "create", cmd_create_main, g_cmd_create_desc, NULL, &g_cmd_create_args + }, + { + // `rm` sub-command + "rm", cmd_delete_main, g_cmd_delete_desc, NULL, &g_cmd_delete_args + }, + { + // `ps` sub-command + "ps", cmd_list_main, g_cmd_list_desc, NULL, &g_cmd_list_args + }, + { + // `start` sub-command + "start", cmd_start_main, g_cmd_start_desc, NULL, &g_cmd_start_args + }, + { + // `run` sub-command + "run", cmd_run_main, g_cmd_run_desc, NULL, &g_cmd_run_args + }, + { + // `restart` sub-command + "restart", cmd_restart_main, g_cmd_restart_desc, NULL, &g_cmd_restart_args + }, + { + // `inspect` sub-command + "inspect", cmd_inspect_main, g_cmd_inspect_desc, NULL, &g_cmd_inspect_args + }, +#ifdef ENABLE_OCI_IMAGE + { + // `stats` sub-command + "stats", + cmd_stats_main, + g_cmd_stats_desc, + NULL, + &g_cmd_stats_args + }, + { + // `cp` sub-command + "cp", cmd_cp_main, g_cmd_cp_desc, NULL, &g_cmd_cp_args + }, +#endif + { + // `stop` sub-command + "stop", cmd_stop_main, g_cmd_stop_desc, NULL, &g_cmd_stop_args + }, + { + // `version` sub-command + "version", cmd_version_main, g_cmd_version_desc, NULL, &g_cmd_version_args + }, + { + // `exec` sub-command + "exec", cmd_exec_main, g_cmd_exec_desc, NULL, &g_cmd_exec_args + }, + { + // `images` sub-command + "images", cmd_images_main, g_cmd_images_desc, NULL, &g_cmd_images_args + }, +#ifdef ENABLE_OCI_IMAGE + { + // `info` sub-command + "info", cmd_info_main, g_cmd_info_desc, NULL, &g_cmd_info_args + }, +#endif + { + // `remove images` sub-command + "rmi", cmd_rmi_main, g_cmd_rmi_desc, NULL, &g_cmd_rmi_args + }, +#ifdef ENABLE_OCI_IMAGE + { + // `wait` sub-command + "wait", cmd_wait_main, g_cmd_wait_desc, NULL, &g_cmd_wait_args + }, + { + // `wait` sub-command + "logs", cmd_logs_main, g_cmd_logs_desc, NULL, &g_cmd_logs_args + }, +#endif + { + // `kill` sub-command + "kill", cmd_kill_main, g_cmd_kill_desc, NULL, &g_cmd_kill_args + }, + { + // `load` sub-command + "load", cmd_load_main, g_cmd_load_desc, NULL, &g_cmd_load_args + }, +#ifdef ENABLE_OCI_IMAGE + { + // `update` sub-command + "update", cmd_update_main, g_cmd_update_desc, NULL, &g_cmd_update_args + }, +#endif + { + // `attach` sub-command + "attach", cmd_attach_main, g_cmd_attach_desc, NULL, &g_cmd_attach_args + }, +#ifdef ENABLE_OCI_IMAGE + { + // `export` sub-command + "export", cmd_export_main, g_cmd_export_desc, NULL, &g_cmd_export_args + }, + { + // `top` sub-command + "top", cmd_top_main, g_cmd_top_desc, NULL, &g_cmd_top_args + }, + { + // `rename` sub-command + "rename", + cmd_rename_main, + g_cmd_rename_desc, + NULL, + &g_cmd_rename_args + }, + { + // `pull` sub-command + "pull", cmd_pull_main, g_cmd_pull_desc, NULL, &g_cmd_pull_args + }, + { + // `login` sub-command + "login", cmd_login_main, g_cmd_login_desc, NULL, &g_cmd_login_args + }, + { + // `logout` sub-command + "logout", cmd_logout_main, g_cmd_logout_desc, NULL, &g_cmd_logout_args + }, +#endif + { NULL, NULL, NULL, NULL, NULL } // End of the list +}; + +int main(int argc, char **argv) +{ + if (connect_client_ops_init()) { + return ECOMMON; + } + return run_command(g_commands, argc, (const char **)argv); +} diff --git a/src/cmd/lcrc/stream/CMakeLists.txt b/src/cmd/lcrc/stream/CMakeLists.txt new file mode 100644 index 0000000..18e2eda --- /dev/null +++ b/src/cmd/lcrc/stream/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} lcrc_stream_srcs) + +set(LCRC_STREAM_SRCS + ${lcrc_stream_srcs} + PARENT_SCOPE + ) diff --git a/src/cmd/lcrc/stream/attach.c b/src/cmd/lcrc/stream/attach.c new file mode 100644 index 0000000..05706d7 --- /dev/null +++ b/src/cmd/lcrc/stream/attach.c @@ -0,0 +1,389 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container attach functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include "securec.h" +#include "arguments.h" +#include "exec.h" +#include "log.h" +#include "lcrc_connect.h" +#include "console.h" +#include "utils.h" +#include "attach.h" +#include "commands.h" + +const char g_cmd_attach_desc[] = "Attach to a running container"; +const char g_cmd_attach_usage[] = "attach [OPTIONS] CONTAINER"; + +struct client_arguments g_cmd_attach_args = { 0 }; + +static int check_tty(bool tty, struct termios *oldtios, bool *reset_tty) +{ + int istty = 0; + + if (!tty) { + return 0; + } + + istty = isatty(0); + if (istty) { + if (setup_tios(0, oldtios)) { + ERROR("Failed to setup terminal properties"); + return -1; + } + *reset_tty = true; + } else { + INFO("the input device is not a TTY"); + return 0; + } + return 0; +} + +int inspect_container(const struct client_arguments *args, container_inspect **inspect_data) +{ + int ret = 0; + struct lcrc_inspect_request inspect_request = { 0 }; + struct lcrc_inspect_response *inspect_response = NULL; + client_connect_config_t config = { 0 }; + lcrc_connect_ops *ops = NULL; + parser_error perr = NULL; + + inspect_response = util_common_calloc_s(sizeof(struct lcrc_inspect_response)); + if (inspect_response == NULL) { + COMMAND_ERROR("Out of memory"); + return -1; + } + + inspect_request.name = args->name; + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.inspect) { + COMMAND_ERROR("Unimplemented ops"); + ret = -1; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.inspect(&inspect_request, inspect_response, &config); + if (ret) { + client_print_error(inspect_response->cc, inspect_response->server_errono, inspect_response->errmsg); + goto out; + } + + /* parse oci container json */ + if (inspect_response == NULL || inspect_response->json == NULL) { + COMMAND_ERROR("Inspect data is empty"); + ret = -1; + goto out; + } + + *inspect_data = container_inspect_parse_data(inspect_response->json, NULL, &perr); + if (*inspect_data == NULL) { + COMMAND_ERROR("Can not parse inspect json: %s", perr); + ret = -1; + goto out; + } + +out: + lcrc_inspect_response_free(inspect_response); + free(perr); + return ret; +} +static int inspect_container_and_check_state(const struct client_arguments *args, + container_inspect **container_inspect_data) +{ + int ret = 0; + container_inspect *inspect_data = NULL; + + if (args->name == NULL || container_inspect_data == NULL) { + ERROR("input name or inspect_data is null"); + ret = -1; + goto out; + } + + if (inspect_container(args, &inspect_data)) { + ERROR("inspect data error"); + ret = -1; + goto out; + } + + if (inspect_data == NULL) { + ERROR("inspect data is null"); + ret = -1; + goto out; + } + + if (inspect_data->state != NULL && !inspect_data->state->running) { + COMMAND_ERROR("You cannot attach to a stopped container, start it first"); + ret = -1; + goto out; + } + + if (inspect_data->state != NULL && inspect_data->state->paused) { + COMMAND_ERROR("You cannot attach to a paused container, unpause it first"); + ret = -1; + goto out; + } + + if (inspect_data->state != NULL && inspect_data->state->restarting) { + COMMAND_ERROR("You cannot attach to a restarting container, wait until it is running"); + ret = -1; + goto out; + } + *container_inspect_data = inspect_data; + +out: + return ret; +} + +static int attach_cmd_init(int argc, const char **argv) +{ + command_t cmd; + struct log_config lconf = { 0 }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_attach_args)) { + COMMAND_ERROR("client arguments init failed\n"); + exit(ECOMMON); + } + g_cmd_attach_args.progname = argv[0]; + struct command_option options[] = { LOG_OPTIONS(lconf), COMMON_OPTIONS(g_cmd_attach_args) }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_attach_desc, + g_cmd_attach_usage); + if (command_parse_args(&cmd, &g_cmd_attach_args.argc, &g_cmd_attach_args.argv)) { + return EINVALIDARGS; + } + if (log_init(&lconf)) { + COMMAND_ERROR("log init failed"); + return ECOMMON; + } + + if (g_cmd_attach_args.argc != 1) { + COMMAND_ERROR("\"%s attach\" requires exactly 1 argument(s).", g_cmd_attach_args.progname); + return ECOMMON; + } + g_cmd_attach_args.name = util_strdup_s(g_cmd_attach_args.argv[0]); + + return 0; +} + +struct wait_thread_arg { + struct client_arguments *client_args; + client_connect_config_t *config; + uint32_t *exit_code; + sem_t *sem_started; + sem_t *sem_exited; +}; + +static void *container_wait_thread_main(void *thread_arg) +{ + int ret = -1; + lcrc_connect_ops *ops = NULL; + struct lcrc_wait_request request = { 0 }; + struct lcrc_wait_response *response = NULL; + struct wait_thread_arg *arg = (struct wait_thread_arg *)thread_arg; + client_connect_config_t config = { 0 }; + + request.id = arg->client_args->name; + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Set thread detach fail"); + goto cleanup; + } + prctl(PR_SET_NAME, "AttachWaitThread"); + + response = util_common_calloc_s(sizeof(struct lcrc_wait_response)); + if (response == NULL) { + ERROR("Wait: Out of memory"); + goto cleanup; + } + + request.condition = WAIT_CONDITION_STOPPED; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.wait) { + ERROR("Unimplemented wait op"); + goto cleanup; + } + + config = get_connect_config(arg->client_args); + (void)sem_post(arg->sem_started); + arg->sem_started = NULL; + ret = ops->container.wait(&request, response, &config); + if (ret != 0) { + ERROR("Wait failed"); + goto cleanup; + } + + *arg->exit_code = (uint32_t)response->exit_code; + +cleanup: + if (arg->sem_started != NULL) { + (void)sem_post(arg->sem_started); + } + if (ret == 0) { + (void)sem_post(arg->sem_exited); + } + lcrc_wait_response_free(response); + free(arg); + return NULL; +} + +static int container_wait_thread(struct client_arguments *args, uint32_t *exit_code, sem_t *sem_exited) +{ + int ret = 0; + pthread_t tid; + struct wait_thread_arg *arg = NULL; + sem_t sem_started; + + arg = util_common_calloc_s(sizeof(struct wait_thread_arg)); + if (arg == NULL) { + ERROR("Out of memory"); + return -1; + } + if (sem_init(&sem_started, 0, 0) != 0) { + COMMAND_ERROR("Can not init sem"); + free(arg); + return -1; + } + arg->client_args = args; + arg->exit_code = exit_code; + arg->sem_started = &sem_started; + arg->sem_exited = sem_exited; + ret = pthread_create(&tid, NULL, container_wait_thread_main, arg); + if (ret != 0) { + free(arg); + (void)sem_destroy(&sem_started); + return -1; + } + (void)sem_wait(&sem_started); + (void)sem_destroy(&sem_started); + return 0; +} + +static int client_attach(struct client_arguments *args, uint32_t *exit_code) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_attach_request request = { 0 }; + struct lcrc_attach_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + struct termios oldtios = { 0 }; + bool reset_tty = false; + container_inspect *inspect_data = NULL; + sem_t sem_exited; + struct timespec ts; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.attach) { + COMMAND_ERROR("Unimplemented attach operation"); + ret = ECOMMON; + goto out; + } + + if (inspect_container_and_check_state(args, &inspect_data)) { + ERROR("Failed to get inspect info!"); + ret = ECOMMON; + goto out; + } + + if (sem_init(&sem_exited, 0, 0) != 0) { + COMMAND_ERROR("Can not init sem"); + ret = ECOMMON; + goto out; + } + + response = util_common_calloc_s(sizeof(struct lcrc_attach_response)); + if (response == NULL) { + ERROR("Attach: Out of memory"); + ret = ECOMMON; + goto out; + } + + free(args->name); + args->name = util_strdup_s(inspect_data->id); + + request.name = args->name; + request.attach_stdin = true; + request.attach_stdout = true; + request.attach_stderr = true; + + if (check_tty(inspect_data->config->tty, &oldtios, &reset_tty) != 0) { + ret = ECOMMON; + goto out; + } + + config = get_connect_config(args); + container_wait_thread(args, exit_code, &sem_exited); + ret = ops->container.attach(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = ECOMMON; + goto out; + } + + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + ERROR("Failed to get real time"); + ret = ECOMMON; + goto out; + } + ts.tv_sec++; + + if (sem_timedwait(&sem_exited, &ts) != 0) { + if (errno == ETIMEDOUT) { + COMMAND_ERROR("Wait container status timeout."); + } else { + COMMAND_ERROR("Failed to wait sem: %s", strerror(errno)); + } + ret = ECOMMON; + goto out; + } +out: + (void)sem_destroy(&sem_exited); + free_container_inspect(inspect_data); + lcrc_attach_response_free(response); + if (reset_tty) { + if (tcsetattr(0, TCSAFLUSH, &oldtios) < 0) { + ERROR("Failed to reset terminal properties"); + return -1; + } + } + return ret; +} + +int cmd_attach_main(int argc, const char **argv) +{ + int ret = 0; + unsigned int exit_code = 0; + + ret = attach_cmd_init(argc, argv); + if (ret != 0) { + goto out; + } + + ret = client_attach(&g_cmd_attach_args, &exit_code); + if (ret != 0) { + ERROR("Failed to execute command attach"); + goto out; + } + +out: + exit((exit_code != 0) ? (int)exit_code : ret); +} diff --git a/src/cmd/lcrc/stream/attach.h b/src/cmd/lcrc/stream/attach.h new file mode 100644 index 0000000..e83a89f --- /dev/null +++ b/src/cmd/lcrc/stream/attach.h @@ -0,0 +1,27 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container attach definition + ******************************************************************************/ +#ifndef __CMD_ATTACH_H +#define __CMD_ATTACH_H + +#include "arguments.h" +#include "container_inspect.h" +#include "wait.h" + +extern const char g_cmd_attach_desc[]; +extern const char g_cmd_attach_usage[]; +extern struct client_arguments g_cmd_attach_args; +int inspect_container(const struct client_arguments *args, container_inspect **inspect_data); +int cmd_attach_main(int argc, const char **argv); +#endif /* __CMD_ATTACH_H */ diff --git a/src/cmd/lcrc/stream/cp.c b/src/cmd/lcrc/stream/cp.c new file mode 100644 index 0000000..ae5a10f --- /dev/null +++ b/src/cmd/lcrc/stream/cp.c @@ -0,0 +1,350 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2019-04-17 + * Description: provide container cp functions + ******************************************************************************/ +#include +#include +#include +#include + +#include "error.h" +#include "cp.h" +#include "arguments.h" +#include "log.h" +#include "path.h" +#include "lcrc_connect.h" +#include "securec.h" +#include "lcrdtar.h" + +#define FromContainer 0x01u +#define ToContainer 0x10u +#define AcrossContainers 0x11u + +const char g_cmd_cp_desc[] = "Copy files/folders between a container and the local filesystem"; + +const char g_cmd_cp_usage[] = "cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH\n" + " cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH"; + +struct client_arguments g_cmd_cp_args = {}; + +static char *resolve_local_path(const char *path) +{ + char abs_path[PATH_MAX] = { 0 }; + + if (cleanpath(path, abs_path, sizeof(abs_path)) == NULL) { + ERROR("Failed to clean path"); + return NULL; + } + + return preserve_trailing_dot_or_separator(abs_path, path); +} + +static void print_copy_from_container_error(const char *ops_err, const char *archive_err, int ret, + const struct client_arguments *args) +{ + if (ops_err != NULL) { + if (strcmp(ops_err, errno_to_error_message(LCRD_ERR_CONNECT)) == 0) { + COMMAND_ERROR("%s", ops_err); + } else { + client_print_error(0, LCRD_ERR_EXEC, ops_err); + } + } else if (archive_err != NULL) { + COMMAND_ERROR("Failed exact archive: %s", archive_err); + } else if (ret != 0) { + COMMAND_ERROR("Failed to copy from container"); + } +} + +static int client_copy_from_container(const struct client_arguments *args, const char *id, const char *srcpath, + const char *destpath) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_copy_from_container_request request = { 0 }; + struct lcrc_copy_from_container_response *response = NULL; + int ret = 0; + int nret = 0; + char *archive_err = NULL; + char *ops_err = NULL; + char *resolved = NULL; + struct archive_copy_info *srcinfo = NULL; + client_connect_config_t config; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.copy_from_container) { + COMMAND_ERROR("Unimplemented copy from container operation"); + return -1; + } + + response = util_common_calloc_s(sizeof(struct lcrc_copy_from_container_response)); + if (response == NULL) { + ERROR("Event: Out of memory"); + return -1; + } + + request.id = (char *)id; + request.runtime = args->runtime; + request.srcpath = (char *)srcpath; + + config = get_connect_config(args); + ret = ops->container.copy_from_container(&request, response, &config); + if (ret) { + ops_err = (response->errmsg != NULL) ? util_strdup_s(response->errmsg) : NULL; + goto out; + } + + resolved = resolve_local_path(destpath); + if (resolved == NULL) { + ret = -1; + goto out; + } + + srcinfo = util_common_calloc_s(sizeof(struct archive_copy_info)); + if (srcinfo == NULL) { + ret = -1; + goto out; + } + srcinfo->exists = true; + srcinfo->path = util_strdup_s(srcpath); + srcinfo->isdir = S_ISDIR(response->stat->mode); + + nret = archive_copy_to(&response->reader, false, srcinfo, resolved, &archive_err); + if (nret != 0) { + ret = nret; + } + + nret = response->reader.close(response->reader.context, &ops_err); + if (nret != 0) { + ret = nret; + } + +out: + print_copy_from_container_error(ops_err, archive_err, ret, args); + free(resolved); + free(archive_err); + free(ops_err); + free_archive_copy_info(srcinfo); + lcrc_copy_from_container_response_free(response); + return ret; +} + +static void print_copy_to_container_error(const struct lcrc_copy_to_container_response *response, + const char *archive_err, int ret, const struct client_arguments *args) +{ + if (response->cc != LCRD_SUCCESS || response->errmsg != NULL || response->server_errono != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + } else if (archive_err) { + COMMAND_ERROR("Failed to archive: %s", archive_err); + } else if (ret != 0) { + COMMAND_ERROR("Can not copy to container"); + } +} + +static int client_copy_to_container(const struct client_arguments *args, const char *id, const char *srcpath, + const char *destpath) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_copy_to_container_request request = { 0 }; + struct lcrc_copy_to_container_response *response = NULL; + int ret = 0; + int nret = 0; + char *archive_err = NULL; + char *resolved = NULL; + struct archive_copy_info *srcinfo = NULL; + struct io_read_wrapper archive_reader = { 0 }; + client_connect_config_t config = { 0 }; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.copy_to_container) { + COMMAND_ERROR("Unimplemented copy to container operation"); + return -1; + } + + response = util_common_calloc_s(sizeof(struct lcrc_copy_to_container_response)); + if (response == NULL) { + ERROR("Event: Out of memory"); + return -1; + } + + request.id = (char *)id; + request.runtime = args->runtime; + request.dstpath = (char *)destpath; + + resolved = resolve_local_path(srcpath); + if (resolved == NULL) { + ret = -1; + goto out; + } + + srcinfo = copy_info_source_path(resolved, false, &archive_err); + if (srcinfo == NULL) { + ret = -1; + goto out; + } + + nret = tar_resource(srcinfo, &archive_reader, &archive_err); + if (nret != 0) { + ret = -1; + goto out; + } + + request.srcpath = srcinfo->path; + request.srcisdir = srcinfo->isdir; + request.srcrebase = srcinfo->rebase_name; + request.reader.context = archive_reader.context; + request.reader.read = archive_reader.read; + request.reader.close = archive_reader.close; + + config = get_connect_config(args); + ret = ops->container.copy_to_container(&request, response, &config); + if (ret) { + goto out; + } + + nret = archive_reader.close(archive_reader.context, &archive_err); + if (nret < 0) { + ret = nret; + } + +out: + print_copy_to_container_error(response, archive_err, ret, args); + free(resolved); + free(archive_err); + free_archive_copy_info(srcinfo); + lcrc_copy_to_container_response_free(response); + return ret; +} + +static void ignore_sigpipe() +{ + struct sigaction sa; + + /* + * Ignore SIGPIPE so the current process still exists after child process exited. + */ + if (memset_s(&sa, sizeof(struct sigaction), 0, sizeof(struct sigaction)) != EOK) { + WARN("Failed to set memory"); + } + + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + WARN("Failed to ignore SIGPIPE"); + } +} + +static int client_run_copy(const struct client_arguments *args, const char *source, const char *destination) +{ + int ret = 0; + char *dup_src = NULL; + char *dup_dest = NULL; + char *colon = NULL; + char *src_container = NULL; + char *src_path = NULL; + char *dest_container = NULL; + char *dest_path = NULL; + char *container = NULL; + unsigned int direction = 0; + + ignore_sigpipe(); + + dup_src = util_strdup_s(source); + src_path = dup_src; + colon = strchr(dup_src, ':'); + if (colon != NULL) { + *colon++ = '\0'; + src_container = dup_src; + src_path = colon; + } + + dup_dest = util_strdup_s(destination); + dest_path = dup_dest; + colon = strchr(dup_dest, ':'); + if (colon != NULL) { + *colon++ = '\0'; + dest_container = dup_dest; + dest_path = colon; + } + + if (src_container != NULL && src_container[0] != '\0') { + direction |= FromContainer; + container = src_container; + } + if (dest_container != NULL && dest_container[0] != '\0') { + direction |= ToContainer; + container = dest_container; + } + + if (direction == FromContainer) { + ret = client_copy_from_container(args, container, src_path, dest_path); + goto cleanup; + } + + if (direction == ToContainer) { + ret = client_copy_to_container(args, container, src_path, dest_path); + goto cleanup; + } + + if (direction == AcrossContainers) { + COMMAND_ERROR("copying between containers is not supported"); + goto cleanup; + } + + COMMAND_ERROR("must specify at least one container source"); + +cleanup: + free(dup_src); + free(dup_dest); + return ret; +} + +int cmd_cp_main(int argc, const char **argv) +{ + struct log_config lconf = { 0 }; + command_t cmd; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_cp_args)) { + COMMAND_ERROR("client arguments init failed\n"); + exit(ECOMMON); + } + g_cmd_cp_args.progname = argv[0]; + struct command_option options[] = { LOG_OPTIONS(lconf), COMMON_OPTIONS(g_cmd_cp_args) }; + + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_cp_desc, + g_cmd_cp_usage); + if (command_parse_args(&cmd, &g_cmd_cp_args.argc, &g_cmd_cp_args.argv)) { + exit(EINVALIDARGS); + } + if (log_init(&lconf)) { + COMMAND_ERROR("cp: log init failed"); + exit(ECOMMON); + } + + if (g_cmd_cp_args.argc != 2) { + COMMAND_ERROR("\"%s cp\" requires exactly 2 arguments.", g_cmd_cp_args.progname); + exit(ECOMMON); + } + + if (g_cmd_cp_args.socket == NULL) { + COMMAND_ERROR("Missing --host,-H option"); + exit(EINVALIDARGS); + } + + if (client_run_copy(&g_cmd_cp_args, g_cmd_cp_args.argv[0], g_cmd_cp_args.argv[1]) != 0) { + exit(ECOMMON); + } + + exit(EXIT_SUCCESS); +} diff --git a/src/cmd/lcrc/stream/cp.h b/src/cmd/lcrc/stream/cp.h new file mode 100644 index 0000000..4a22dbe --- /dev/null +++ b/src/cmd/lcrc/stream/cp.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2019-04-17 + * Description: provide container cp definition + ******************************************************************************/ +#ifndef __CMD_COPY_H +#define __CMD_COPY_H + +#include "arguments.h" + +extern const char g_cmd_cp_desc[]; +extern const char g_cmd_cp_usage[]; +extern struct client_arguments g_cmd_cp_args; +int cmd_cp_main(int argc, const char **argv); + +#endif /* __CMD_COPY_H */ diff --git a/src/cmd/lcrc/stream/exec.c b/src/cmd/lcrc/stream/exec.c new file mode 100644 index 0000000..a78d58c --- /dev/null +++ b/src/cmd/lcrc/stream/exec.c @@ -0,0 +1,358 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container exec functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include "securec.h" +#include "arguments.h" +#include "exec.h" +#include "log.h" +#include "lcrc_connect.h" +#include "console.h" +#include "utils.h" +#include "commands.h" +#include "container_inspect.h" + +const char g_cmd_exec_desc[] = "Run a command in a running container"; +const char g_cmd_exec_usage[] = "exec [OPTIONS] CONTAINER COMMAND [ARG...]"; + +sem_t g_command_waitopen_sem; +sem_t g_command_waitexit_sem; + +struct client_arguments g_cmd_exec_args = {}; + +static int client_exec(const struct client_arguments *args, const struct command_fifo_config *fifos, + uint32_t *exit_code) +{ + lcrc_connect_ops *ops = NULL; + struct lcrc_exec_request request = { 0 }; + struct lcrc_exec_response *response = NULL; + client_connect_config_t config = { 0 }; + int ret = 0; + + response = util_common_calloc_s(sizeof(struct lcrc_exec_response)); + if (response == NULL) { + ERROR("Out of memory"); + return ECOMMON; + } + + request.name = args->name; + request.tty = args->custom_conf.tty; + request.open_stdin = args->custom_conf.open_stdin; + request.attach_stdin = args->custom_conf.attach_stdin; + request.attach_stdout = args->custom_conf.attach_stdout; + request.attach_stderr = args->custom_conf.attach_stderr; + if (fifos != NULL) { + request.stdin = fifos->stdin_name; + request.stdout = fifos->stdout_name; + request.stderr = fifos->stderr_name; + } + + request.argc = args->argc; + request.argv = (char **)args->argv; + + /* environment variables */ + request.env_len = util_array_len(args->extra_env); + request.env = args->extra_env; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.exec) { + ERROR("Unimplemented ops"); + ret = ECOMMON; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.exec(&request, response, &config); + if (ret) { + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = ECOMMON; + goto out; + } +out: + if (response->exit_code) { + *exit_code = response->exit_code; + } + + lcrc_exec_response_free(response); + return ret; +} + +static int exec_cmd_init(int argc, const char **argv) +{ + command_t cmd; + struct log_config lconf = { 0 }; + + struct command_option options[] = { + LOG_OPTIONS(lconf), + COMMON_OPTIONS(g_cmd_exec_args), + EXEC_OPTIONS(g_cmd_exec_args) + }; + + set_default_command_log_config(argv[0], &lconf); + if (client_arguments_init(&g_cmd_exec_args)) { + COMMAND_ERROR("client arguments init failed\n"); + exit(ECOMMON); + } + g_cmd_exec_args.progname = argv[0]; + command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_exec_desc, + g_cmd_exec_usage); + + if (command_parse_args(&cmd, &g_cmd_exec_args.argc, &g_cmd_exec_args.argv)) { + return EINVALIDARGS; + } + if (log_init(&lconf)) { + COMMAND_ERROR("Exec: log init failed"); + return ECOMMON; + } + + if (g_cmd_exec_args.argc < 1) { + COMMAND_ERROR("Exec needs a container name"); + return ECOMMON; + } else { + g_cmd_exec_args.name = g_cmd_exec_args.argv[0]; + g_cmd_exec_args.argc--; + g_cmd_exec_args.argv++; + } + + if (sem_init(&g_command_waitopen_sem, 0, 0)) { + ERROR("Semaphore initialization failed"); + return ECOMMON; + } + + if (sem_init(&g_command_waitexit_sem, 0, 0)) { + ERROR("Semaphore initialization failed"); + sem_destroy(&g_command_waitopen_sem); + return ECOMMON; + } + + return 0; +} + +static int exec_prepare_console(struct command_fifo_config **command_fifos, bool *reset_tty, struct termios *oldtios, + struct custom_configs *custom_cfg) +{ + int ret = 0; + int istty = 0; + container_inspect *inspect_data = NULL; + + if (inspect_container(&g_cmd_exec_args, &inspect_data)) { + ERROR("inspect data error"); + ret = ECOMMON; + goto out; + } + + if (inspect_data->id != NULL) { + g_cmd_exec_args.name = util_strdup_s(inspect_data->id); + } + + istty = isatty(0); + if (istty && custom_cfg->tty && custom_cfg->attach_stdin) { + if (setup_tios(0, oldtios)) { + ERROR("Failed to setup terminal properties"); + ret = ECOMMON; + goto out; + } + *reset_tty = true; + } + if (!istty) { + INFO("The input device is not a TTY"); + } + + if (custom_cfg->attach_stdin || custom_cfg->attach_stdout || custom_cfg->attach_stderr) { + if (create_console_fifos(custom_cfg->attach_stdin, custom_cfg->attach_stdout, custom_cfg->attach_stderr, + g_cmd_exec_args.name, "exec", command_fifos)) { + ERROR("Container \"%s\" create console FIFO failed", g_cmd_exec_args.name); + ret = ECOMMON; + goto out; + } + + (*command_fifos)->wait_open = &g_command_waitopen_sem; + (*command_fifos)->wait_exit = &g_command_waitexit_sem; + if (start_client_console_thread((*command_fifos), custom_cfg->tty && (istty != 0))) { + ERROR("Container \"%s\" start console thread failed", g_cmd_exec_args.name); + ret = ECOMMON; + goto out; + } + } + +out: + free_container_inspect(inspect_data); + return ret; +} + +static int remote_cmd_exec_setup_tty(const struct client_arguments *args, bool *reset_tty, + struct termios *oldtios) +{ + int istty = 0; + + istty = isatty(0); + if (istty && args->custom_conf.tty && args->custom_conf.attach_stdin) { + if (setup_tios(0, oldtios)) { + ERROR("Failed to setup terminal properties"); + return -1; + } + *reset_tty = true; + } + if (!istty) { + INFO("The input device is not a TTY"); + } + return 0; +} + +static int remote_cmd_exec(const struct client_arguments *args, uint32_t *exit_code) +{ + int ret = 0; + lcrc_connect_ops *ops = NULL; + struct lcrc_exec_request request = { 0 }; + struct lcrc_exec_response *response = NULL; + client_connect_config_t config = { 0 }; + struct termios oldtios; + bool reset_tty = false; + container_inspect *inspect_data = NULL; + + ops = get_connect_client_ops(); + if (ops == NULL || !ops->container.remote_exec) { + ERROR("Unimplemented ops"); + return ECOMMON; + } + + response = util_common_calloc_s(sizeof(struct lcrc_exec_response)); + if (response == NULL) { + ERROR("Out of memory"); + return ECOMMON; + } + + if (inspect_container(args, &inspect_data)) { + ERROR("inspect data error"); + ret = ECOMMON; + goto out; + } + + g_cmd_exec_args.name = util_strdup_s(inspect_data->id); + + request.name = args->name; + request.tty = args->custom_conf.tty; + request.open_stdin = args->custom_conf.open_stdin; + request.attach_stdin = args->custom_conf.attach_stdin; + request.attach_stdout = args->custom_conf.attach_stdout; + request.attach_stderr = args->custom_conf.attach_stderr; + + request.argc = args->argc; + request.argv = (char **)args->argv; + + /* environment variables */ + request.env_len = util_array_len(args->extra_env); + request.env = args->extra_env; + + if (remote_cmd_exec_setup_tty(args, &reset_tty, &oldtios) < 0) { + ret = ECOMMON; + goto out; + } + + config = get_connect_config(args); + ret = ops->container.remote_exec(&request, response, &config); + if (ret != 0) { + client_print_error(response->cc, response->server_errono, response->errmsg); + ret = ECOMMON; + goto out; + } + +out: + free_container_inspect(inspect_data); + if (reset_tty && tcsetattr(0, TCSAFLUSH, &oldtios) < 0) { + WARN("Failed to reset terminal properties: %s.", strerror(errno)); + } + if (response->exit_code != 0) { + *exit_code = response->exit_code; + } + lcrc_exec_response_free(response); + return ret; +} + +static int local_cmd_exec(struct client_arguments *args, uint32_t *exit_code) +{ + bool reset_tty = false; + int ret = 0; + struct termios oldtios = { 0 }; + struct command_fifo_config *command_fifos = NULL; + + ret = exec_prepare_console(&command_fifos, &reset_tty, &oldtios, &args->custom_conf); + if (ret) { + goto out; + } + + ret = client_exec(args, command_fifos, exit_code); + if (!ret && + (args->custom_conf.attach_stdin || args->custom_conf.attach_stdout || args->custom_conf.attach_stderr)) { + sem_wait(&g_command_waitexit_sem); + } +out: + delete_command_fifo(command_fifos); + sem_destroy(&g_command_waitopen_sem); + sem_destroy(&g_command_waitexit_sem); + if (reset_tty && tcsetattr(0, TCSAFLUSH, &oldtios) < 0) { + WARN("Failed to reset terminal properties: %s.", strerror(errno)); + } + return ret; +} + +int cmd_exec_main(int argc, const char **argv) +{ + int ret = 0; + uint32_t exit_code = 0; + struct custom_configs *custom_cfg = NULL; + + ret = exec_cmd_init(argc, argv); + if (ret) { + goto out; + } + + custom_cfg = &g_cmd_exec_args.custom_conf; + + custom_cfg->tty = true; + custom_cfg->open_stdin = true; + custom_cfg->attach_stdout = true; + custom_cfg->attach_stderr = false; + custom_cfg->attach_stdin = custom_cfg->open_stdin; + + if (g_cmd_exec_args.detach) { + custom_cfg->attach_stdin = false; + custom_cfg->attach_stdout = false; + custom_cfg->attach_stderr = false; + custom_cfg->open_stdin = false; + } + + if (strncmp(g_cmd_exec_args.socket, "tcp://", strlen("tcp://")) == 0) { + ret = remote_cmd_exec(&g_cmd_exec_args, &exit_code); + if (ret != 0) { + ERROR("Failed to execute command with remote exec"); + goto out; + } + } else { + ret = local_cmd_exec(&g_cmd_exec_args, &exit_code); + if (ret != 0) { + ERROR("Failed to execute command with local exec"); + goto out; + } + } + +out: + exit(exit_code ? (int)exit_code : ret); +} diff --git a/src/cmd/lcrc/stream/exec.h b/src/cmd/lcrc/stream/exec.h new file mode 100644 index 0000000..76f3387 --- /dev/null +++ b/src/cmd/lcrc/stream/exec.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container exec definition + ******************************************************************************/ +#ifndef __CMD_EXEC_H +#define __CMD_EXEC_H + +#include "arguments.h" +#include "attach.h" + +#define EXEC_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_CALLBACK, false, "env", 'e', &(cmdargs).extra_env, \ + "Set environment variables", command_append_array }, \ + { CMD_OPT_TYPE_BOOL, false, "detach", 'd', &(cmdargs).detach, "Run container in background", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "tty", 't', &(cmdargs).custom_conf.tty, "Allocate a pseudo-TTY", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "interactive", 'i', &(cmdargs).custom_conf.open_stdin, \ + "Keep STDIN open even if not attached", NULL } + +extern const char g_cmd_exec_desc[]; +extern const char g_cmd_exec_usage[]; +extern struct client_arguments g_cmd_exec_args; +int cmd_exec_main(int argc, const char **argv); + +#endif /* __CMD_EXEC_H */ diff --git a/src/cmd/lcrd/CMakeLists.txt b/src/cmd/lcrd/CMakeLists.txt new file mode 100644 index 0000000..19e21a9 --- /dev/null +++ b/src/cmd/lcrd/CMakeLists.txt @@ -0,0 +1,5 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} top_srcs) + +set(CMD_LCRD_SRCS ${top_srcs} PARENT_SCOPE) +set(CMD_LCRD_INCS ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) diff --git a/src/cmd/lcrd/arguments.c b/src/cmd/lcrd/arguments.c new file mode 100644 index 0000000..4567999 --- /dev/null +++ b/src/cmd/lcrd/arguments.c @@ -0,0 +1,244 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container server arguments functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include "error.h" +#include "utils.h" +#include "arguments.h" +#include "commands.h" +#include "liblcrd.h" +#include "constants.h" +#include "lcrd_config.h" + +static int set_daemon_default_tls_options(struct service_arguments *args) +{ + int ret = -1; + char *tls = NULL; + char *tmp_path = NULL; + char *tls_verify = NULL; + char *cert_path = NULL; + char *ca_file = NULL; + char *cert_file = NULL; + char *key_file = NULL; + + tls = getenv("ISULAD_TLS"); + args->json_confs->tls = (tls != NULL && strlen(tls) != 0 && strcmp(tls, "0") != 0); + tls = NULL; + + tls_verify = getenv("ISULAD_TLS_VERIFY"); + args->json_confs->tls_verify = (tls_verify != NULL && strlen(tls_verify) != 0 && strcmp(tls_verify, "0") != 0); + tls_verify = NULL; + + tmp_path = getenv("ISULAD_CERT_PATH"); + if (tmp_path == NULL || strlen(tmp_path) == 0) { + cert_path = util_strdup_s(ISULAD_CONFIG); + } else { + cert_path = util_strdup_s(tmp_path); + } + tmp_path = NULL; + + args->json_confs->tls_config = + (isulad_daemon_configs_tls_config *)util_common_calloc_s(sizeof(isulad_daemon_configs_tls_config)); + if (args->json_confs->tls_config == NULL) { + goto out; + } + + ca_file = util_path_join(cert_path, DEFAULT_CA_FILE); + if (ca_file == NULL) { + goto out; + } + free(args->json_confs->tls_config->ca_file); + args->json_confs->tls_config->ca_file = ca_file; + + key_file = util_path_join(cert_path, DEFAULT_KEY_FILE); + if (key_file == NULL) { + goto out; + } + free(args->json_confs->tls_config->key_file); + args->json_confs->tls_config->key_file = key_file; + + cert_file = util_path_join(cert_path, DEFAULT_CERT_FILE); + if (cert_file == NULL) { + goto out; + } + free(args->json_confs->tls_config->cert_file); + args->json_confs->tls_config->cert_file = cert_file; + + ret = 0; + +out: + free(cert_path); + return ret; +} + +int service_arguments_init(struct service_arguments *args) +{ +#define DEFAULT_LOG_OPTS_LEN 3 + + int ret = -1; + if (args == NULL) { + return -1; + } + args->argc = 0; + args->argv = NULL; + + args->progname = util_strdup_s("lcrd"); + args->quiet = true; + args->help = false; + args->version = false; + + args->json_confs = (isulad_daemon_configs *)util_common_calloc_s(sizeof(isulad_daemon_configs)); + if (args->json_confs == NULL) { + goto free_out; + } + args->json_confs->engine = util_strdup_s("lcr"); + args->json_confs->group = util_strdup_s("lcrd"); + args->json_confs->graph = util_strdup_s(LCRD_ROOT_PATH); + args->json_confs->state = util_strdup_s(LCRD_STATE_PATH); + args->json_confs->log_level = util_strdup_s("INFO"); + args->json_confs->log_driver = util_strdup_s("file"); + args->json_confs->log_opts = (json_map_string_string *)util_common_calloc_s(sizeof(json_map_string_string)); + if (args->json_confs->log_opts == NULL) { + goto free_out; + } + args->json_confs->log_opts->keys = (char **)util_common_calloc_s(sizeof(char *) * DEFAULT_LOG_OPTS_LEN); + if (args->json_confs->log_opts->keys == NULL) { + goto free_out; + } + args->json_confs->log_opts->values = (char **)util_common_calloc_s(sizeof(char *) * DEFAULT_LOG_OPTS_LEN); + if (args->json_confs->log_opts->values == NULL) { + goto free_out; + } + args->json_confs->log_opts->len = DEFAULT_LOG_OPTS_LEN; + args->json_confs->log_opts->keys[0] = util_strdup_s("log-file-mode"); + args->json_confs->log_opts->values[0] = util_strdup_s("0600"); + args->json_confs->log_opts->keys[1] = util_strdup_s("max-file"); + args->json_confs->log_opts->values[1] = util_strdup_s("7"); + args->json_confs->log_opts->keys[2] = util_strdup_s("max-size"); + args->json_confs->log_opts->values[2] = util_strdup_s("1MB"); + args->log_file_mode = 0600; + args->max_file = 7; + args->max_size = 1024 * 1024; + + args->json_confs->pidfile = util_strdup_s("/var/run/lcrd.pid"); + args->json_confs->storage_driver = util_strdup_s("overlay2"); + args->json_confs->native_umask = util_strdup_s(UMASK_SECURE); + args->json_confs->image_service = true; + args->json_confs->image_layer_check = false; + args->json_confs->use_decrypted_key = (bool *)util_common_calloc_s(sizeof(bool)); + if (args->json_confs->use_decrypted_key == NULL) { + goto free_out; + } + *(args->json_confs->use_decrypted_key) = true; + args->json_confs->insecure_skip_verify_enforce = false; + + args->im_opt_timeout = 5 * 60; // default image operation timeout 300s + if (set_daemon_default_tls_options(args) != 0) { + goto free_out; + } + + args->default_ulimit = NULL; + args->default_ulimit_len = 0; + + ret = 0; + +free_out: + if (ret != 0) { + service_arguments_free(args); + } + return ret; +} + +/* service arguments free */ +void service_arguments_free(struct service_arguments *args) +{ + if (args == NULL) { + return; + } + free(args->progname); + args->progname = NULL; + + free(args->logpath); + args->logpath = NULL; + + util_free_array(args->hosts); + args->hosts = NULL; + args->hosts_len = 0; + + free_isulad_daemon_configs(args->json_confs); + args->json_confs = NULL; + + free_oci_runtime_spec_hooks(args->hooks); + args->hooks = NULL; + + free_default_ulimit(args->default_ulimit); + args->default_ulimit = NULL; + args->default_ulimit_len = 0; +} + +/* server log opt parser */ +int server_log_opt_parser(struct service_arguments *args, const char *option) +{ + int ret = -1; + char *key = NULL; + char *value = NULL; + char *tmp = NULL; + size_t len = 0; + size_t total_len = 0; + + if (option == NULL || args == NULL) { + goto out; + } + + // option format: key=value + total_len = strlen(option); + if (args == NULL || total_len <= 2) { + goto out; + } + + tmp = util_strdup_s(option); + key = tmp; + value = strchr(tmp, '='); + // option do not contain '=' + if (value == NULL) { + goto out; + } + + len = (size_t)(value - key); + // if option is '=key' + if (len == 0) { + goto out; + } + // if option is 'key=' + if (total_len == len + 1) { + goto out; + } + tmp[len] = '\0'; + value += 1; + + ret = parse_log_opts(args, key, value); + + if (ret == 0 && args->json_confs != NULL && args->json_confs->log_opts != NULL) { + ret = append_json_map_string_string(args->json_confs->log_opts, key, value); + } + +out: + free(tmp); + return ret; +} diff --git a/src/cmd/lcrd/arguments.h b/src/cmd/lcrd/arguments.h new file mode 100644 index 0000000..0021411 --- /dev/null +++ b/src/cmd/lcrd/arguments.h @@ -0,0 +1,71 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container server arguments definition + ******************************************************************************/ +#ifndef __LCRD_ARGUMENTS_H +#define __LCRD_ARGUMENTS_H + +#include +#include +#include "isulad_daemon_configs.h" +#include "oci_runtime_hooks.h" +#include "commander.h" + +#ifdef ENABLE_OCI_IMAGE +#include "driver.h" +#endif + +typedef void(*service_arguments_help_t)(void); + +struct service_arguments { + char *progname; + service_arguments_help_t print_help; + + bool quiet; + bool help; + bool version; + char **hosts; + size_t hosts_len; + + // struct service_arguments *server_conf; + isulad_daemon_configs *json_confs; +#ifdef ENABLE_OCI_IMAGE + struct graphdriver *driver; +#endif + /* parsed configs */ + oci_runtime_spec_hooks *hooks; + + unsigned int start_timeout; + unsigned int im_opt_timeout; + + /* log-opts */ + unsigned int log_file_mode; + char *logpath; + int64_t max_size; + int max_file; + + /* default configs */ + host_config_ulimits_element **default_ulimit; + size_t default_ulimit_len; + + // remaining arguments + char * const *argv; + + int argc; +}; + +int service_arguments_init(struct service_arguments *args); +void service_arguments_free(struct service_arguments *args); +int server_log_opt_parser(struct service_arguments *args, const char *option); + +#endif /*__LCRD_ARGUMENTS_H*/ diff --git a/src/cmd/lcrd/commands.c b/src/cmd/lcrd/commands.c new file mode 100644 index 0000000..9f8adee --- /dev/null +++ b/src/cmd/lcrd/commands.c @@ -0,0 +1,545 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container commands functions + ******************************************************************************/ +#include +#include +#include +#include + +#include "commands.h" +#include "config.h" +#include "log.h" +#include "path.h" +#include "liblcrd.h" + +#ifdef ENABLE_OCI_IMAGE +#include "driver.h" +#endif + +#include "utils.h" +#include "constants.h" +#include "isulad_daemon_configs.h" + +const char lcrd_desc[] = "GLOBAL OPTIONS:"; +const char lcrd_usage[] = "[global options]"; + +void print_version() +{ + printf("Version %s, commit %s\n", VERSION, LCRD_GIT_COMMIT); +} + +int server_callback_log_opt(command_option_t *option, const char *value) +{ + int ret = 0; + struct service_arguments *args = NULL; + + if (option == NULL || value == NULL) { + COMMAND_ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + args = (struct service_arguments *)option->data; + ret = server_log_opt_parser(args, value); + if (ret != 0) { + COMMAND_ERROR("Invalid value \"%s\" for flag --%s", value, option->large); + } + +out: + return ret; +} + +static void command_init_lcrd(command_t *self, command_option_t *options, int options_len, int argc, const char **argv, + const char *description, const char *usage) +{ + self->name = argv[0]; + self->argc = argc - 1; + self->argv = argv + 1; + self->usage = usage; + self->description = description; + self->options = options; + self->option_count = options_len; + self->type = "lcrd"; +} + +// Tries to execute a command in the command list. +int parse_args(struct service_arguments *args, int argc, const char **argv) +{ + command_t cmd = { 0 }; + struct command_option options[] = { LCRD_OPTIONS(args) }; + command_init_lcrd(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, lcrd_desc, + lcrd_usage); + if (command_parse_args(&cmd, &args->argc, &args->argv)) { + exit(EINVALIDARGS); + } + + if (args->argc > 0) { + printf("unresolved arguments: %s;\t" + "run `%s --help` or `%s -h` for help.\n", + args->argv[0], argv[0], argv[0]); + return -1; + } + + if (args->version == true) { + print_version(); + exit(0); + } + + return 0; +} + +static bool check_file_mode(unsigned int mode) +{ + unsigned int work = 0x01ff; + + if ((mode & (~work)) == 0) { + return true; + } + return false; +} + +static int check_args_log_conf(const struct service_arguments *args) +{ + int ret = 0; + + /* validate log-file-mode */ + if (!check_file_mode(args->log_file_mode)) { + ERROR("Invalid log file mode: %lu", args->log_file_mode); + ret = -1; + goto out; + } + + /* validate max-size */ + if ((args->json_confs->log_driver && strcasecmp("file", args->json_confs->log_driver) == 0) && + (args->max_size < (4 * 1024))) { + ERROR("Max-size \"%lld\" must large than 4KB.", args->max_size); + ret = -1; + goto out; + } +out: + return ret; +} + +static int check_args_hosts_conf(const char **array, size_t size) +{ + int ret = 0; + size_t i; + + /* validate unix/tcp socket name */ + for (i = 0; i < size; i++) { + if (!util_validate_socket(array[i])) { + lcrd_set_error_message("Invalid socket name: %s", array[i]); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int check_args_graph_path(struct service_arguments *args) +{ + int ret = 0; + char dstpath[PATH_MAX] = { 0 }; + + ret = util_validate_absolute_path(args->json_confs->graph); + if (ret) { + ERROR("Invalid absolute root directory path:(%s).", args->json_confs->graph); + ret = -1; + goto out; + } + if (cleanpath(args->json_confs->graph, dstpath, sizeof(dstpath)) == NULL) { + ERROR("failed to get clean path"); + ret = -1; + goto out; + } + free(args->json_confs->graph); + args->json_confs->graph = util_strdup_s(dstpath); + +out: + return ret; +} + +static int check_args_state_path(struct service_arguments *args) +{ + int ret = 0; + char dstpath[PATH_MAX] = { 0 }; + + ret = util_validate_absolute_path(args->json_confs->state); + if (ret != 0) { + ERROR("Invalid absolute state directory path:(%s).", args->json_confs->state); + ret = -1; + goto out; + } + if (cleanpath(args->json_confs->state, dstpath, sizeof(dstpath)) == NULL) { + ERROR("failed to get clean path"); + ret = -1; + goto out; + } + free(args->json_confs->state); + args->json_confs->state = util_strdup_s(dstpath); + +out: + return ret; +} + +static int check_args_umask(const struct service_arguments *args) +{ + int ret = 0; + + if (args->json_confs->native_umask != NULL) { + if (strcmp(args->json_confs->native_umask, UMASK_NORMAL) != 0 && + strcmp(args->json_confs->native_umask, UMASK_SECURE) != 0) { + COMMAND_ERROR("Invalid native umask: %s", args->json_confs->native_umask); + ERROR("Invalid native umask: %s", args->json_confs->native_umask); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int check_args_auth_plugin(const struct service_arguments *args) +{ + int ret = 0; + + if (args->json_confs->authorization_plugin != NULL) { + if (strcmp(args->json_confs->authorization_plugin, AUTH_PLUGIN)) { + COMMAND_ERROR("Invalid authorization plugin '%s'", args->json_confs->authorization_plugin); + ERROR("Invalid authorization plugin '%s'", args->json_confs->authorization_plugin); + ret = -1; + goto out; + } + } +out: + return ret; +} + +int check_args(struct service_arguments *args) +{ + int ret = 0; + + if (args->json_confs == NULL) { + ERROR("Empty json configs"); + ret = -1; + goto out; + } + + args->hosts_len = util_array_len(args->hosts); + args->json_confs->storage_opts_len = util_array_len(args->json_confs->storage_opts); + args->json_confs->registry_mirrors_len = util_array_len(args->json_confs->registry_mirrors); + args->json_confs->insecure_registries_len = util_array_len(args->json_confs->insecure_registries); + + /* validate log-file-mode */ + if (check_args_log_conf(args) != 0) { + ret = -1; + goto out; + } + + if (check_args_hosts_conf((const char **)(args->hosts), args->hosts_len) != 0) { + ret = -1; + goto out; + } + + /* validate pid file format */ + if (util_validate_absolute_path(args->json_confs->pidfile) != 0) { + ERROR("Invalid absolute pid file path:(%s).", args->json_confs->pidfile); + ret = -1; + goto out; + } + + /* validate rootpath format */ + if (check_args_graph_path(args) != 0) { + ret = -1; + goto out; + } + + /* validate statepath format */ + if (check_args_state_path(args) != 0) { + ret = -1; + goto out; + } + + if (check_args_umask(args) != 0) { + ret = -1; + goto out; + } + + if (check_args_auth_plugin(args) != 0) { + ret = -1; + goto out; + } + +out: + return ret; +} + +static int do_merge_conf_hosts_into_global(struct service_arguments *args) +{ +#define DEFAULT_HOSTS_LEN 2 + + if (args->json_confs->hosts_len != 0) { + args->hosts = args->json_confs->hosts; + args->hosts_len = args->json_confs->hosts_len; + args->json_confs->hosts = NULL; + args->json_confs->hosts_len = 0; + return 0; + } + + if (args->hosts_len == 0) { + /* set default host */ + args->hosts = (char **)util_common_calloc_s(sizeof(char *) * DEFAULT_HOSTS_LEN); + if (args->hosts == NULL) { + ERROR("Out of memory"); + return -1; + } + args->hosts[0] = util_strdup_s(DEFAULT_UNIX_SOCKET); + args->hosts_len++; + } + + return 0; +} + +static int check_hosts_specified_conflict(const struct service_arguments *args) +{ + int ret = 0; + char *flag_hosts = NULL; + char *file_hosts = NULL; + + if (args->hosts_len != 0 && args->json_confs->hosts_len != 0) { + flag_hosts = util_string_join(" ", (const char **)args->hosts, args->hosts_len); + if (flag_hosts == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + file_hosts = util_string_join(" ", (const char **)args->json_confs->hosts, args->json_confs->hosts_len); + if (file_hosts == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + COMMAND_ERROR("unable to configure the lcrd with file %s: " + "the following directives are specified both as a flag and in the configuration file: hosts: " + "(from flag: [%s], from file: [%s])", + ISULAD_DAEMON_JSON_CONF_FILE, flag_hosts, file_hosts); + + ret = -1; + } + +out: + free(flag_hosts); + free(file_hosts); + return ret; +} + +static int do_merge_conf_default_ulimit_into_global(struct service_arguments *args) +{ + size_t i, j, json_default_ulimit_len; + isulad_daemon_configs_default_ulimits_element *ptr = NULL; + + if (args->json_confs->default_ulimits == NULL) { + return 0; + } + + json_default_ulimit_len = args->json_confs->default_ulimits->len; + for (i = 0; i < json_default_ulimit_len; i++) { + ptr = args->json_confs->default_ulimits->values[i]; + for (j = 0; j < args->default_ulimit_len; j++) { + if (strcmp(ptr->name, args->default_ulimit[j]->name) == 0) { + break; + } + } + + if (j < args->default_ulimit_len) { + args->default_ulimit[j]->soft = ptr->soft; + args->default_ulimit[j]->hard = ptr->hard; + continue; + } + if (ulimit_array_append(&args->default_ulimit, (host_config_ulimits_element *)ptr, + args->default_ulimit_len) != 0) { + ERROR("merge json confs default ulimit config failed"); + return -1; + } + + args->default_ulimit_len++; + } + + return 0; +} + +static int ulimit_flag_join(char* out_msg, const size_t msg_len, const size_t default_ulimit_len, + host_config_ulimits_element **default_ulimit) +{ + int ret = -1; + size_t i; + char *tmp = NULL; + + if (sprintf_s(out_msg, msg_len, "[") < 0) { + ERROR("Failed to print string"); + goto out; + } + + for (i = 0; i < default_ulimit_len; i++) { + tmp = util_strdup_s(out_msg); + if (sprintf_s(out_msg, msg_len, "%s %s=%lld:%lld", tmp, + default_ulimit[i]->name, (long long int)default_ulimit[i]->soft, + (long long int)default_ulimit[i]->hard) < 0) { + ERROR("Failed to print string"); + goto out; + } + free(tmp); + tmp = NULL; + } + + tmp = util_strdup_s(out_msg); + if (sprintf_s(out_msg, msg_len, "%s ]", tmp) < 0) { + ERROR("Failed to print string"); + goto out; + } + + ret = 0; + +out: + free(tmp); + return ret; +} + +static int ulimit_file_join(char* out_msg, const size_t msg_len, + isulad_daemon_configs_default_ulimits_element **default_ulimits, + size_t default_ulimits_len) +{ + int ret = -1; + size_t i; + char *tmp = NULL; + isulad_daemon_configs_default_ulimits_element *ptr = NULL; + + if (sprintf_s(out_msg, msg_len, "[") < 0) { + ERROR("Failed to print string"); + goto out; + } + for (i = 0; i < default_ulimits_len; i++) { + ptr = default_ulimits[i]; + tmp = util_strdup_s(out_msg); + if (sprintf_s(out_msg, msg_len, "%s %s=%lld:%lld", tmp, ptr->name, + (long long int)(ptr->soft), (long long int)(ptr->hard)) < 0) { + ERROR("Failed to print string"); + goto out; + } + free(tmp); + tmp = NULL; + } + + tmp = util_strdup_s(out_msg); + if (sprintf_s(out_msg, msg_len, "%s ]", tmp) < 0) { + ERROR("Failed to print string"); + goto out; + } + + ret = 0; +out: + free(tmp); + return ret; +} + + +static int check_conf_default_ulimit(const struct service_arguments *args) +{ +#define ULIMIT_MSG_MAX 1024 + int ret = 0; + size_t i; + char *type = NULL; + isulad_daemon_configs_default_ulimits_element *ptr = NULL; + + if (args->json_confs->default_ulimits == NULL) { + ret = 0; + goto out; + } + + /* check json_confs default_ulimits */ + for (i = 0; i < args->json_confs->default_ulimits->len; i++) { + type = args->json_confs->default_ulimits->keys[i]; + ptr = args->json_confs->default_ulimits->values[i]; + if (ptr->soft > ptr->hard) { + COMMAND_ERROR("Ulimit soft limit must be less than or equal to hard limit: %lld > %lld in %s", + (long long int)(ptr->soft), (long long int)(ptr->hard), ISULAD_DAEMON_JSON_CONF_FILE); + ret = -1; + goto out; + } + if (strcmp(ptr->name, type) != 0) { + COMMAND_ERROR("Ulimit Name \"%s\" must same as type \"%s\" in %s", ptr->name, type, + ISULAD_DAEMON_JSON_CONF_FILE); + ret = -1; + goto out; + } + + ret = check_default_ulimit_type(type); + if (ret != 0) { + goto out; + } + } + + /* check conflict */ + if (args->default_ulimit_len != 0) { + char flag_def_ulimit[ULIMIT_MSG_MAX] = { 0 }; + char file_def_ulimit[ULIMIT_MSG_MAX] = { 0 }; + + if (ulimit_flag_join(flag_def_ulimit, ULIMIT_MSG_MAX, args->default_ulimit_len, + args->default_ulimit) != 0) { + ret = -1; + goto out; + } + + if (ulimit_file_join(file_def_ulimit, ULIMIT_MSG_MAX, args->json_confs->default_ulimits->values, + args->json_confs->default_ulimits->len) != 0) { + ret = -1; + goto out; + } + + COMMAND_ERROR("unable to configure the lcrd with file %s: " + "the following directives are specified both as a flag and in the configuration file: " + "default-ulimits: (from flag: %s, from file: %s)", + ISULAD_DAEMON_JSON_CONF_FILE, flag_def_ulimit, file_def_ulimit); + ret = -1; + } + +out: + return ret; +} + +int update_hosts(struct service_arguments *args) +{ + args->hosts_len = util_array_len(args->hosts); + + if (check_hosts_specified_conflict(args) != 0) { + return -1; + } + + return do_merge_conf_hosts_into_global(args); +} + +int update_default_ulimit(struct service_arguments *args) +{ + args->default_ulimit_len = ulimit_array_len(args->default_ulimit); + + if (check_conf_default_ulimit(args) != 0) { + return -1; + } + + return do_merge_conf_default_ulimit_into_global(args); +} + + diff --git a/src/cmd/lcrd/commands.h b/src/cmd/lcrd/commands.h new file mode 100644 index 0000000..9038d2f --- /dev/null +++ b/src/cmd/lcrd/commands.h @@ -0,0 +1,102 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container commands definition + ******************************************************************************/ +#ifndef __LCRD_COMMAND_H +#define __LCRD_COMMAND_H +#include "arguments.h" + +void print_common_help(); +void print_version(); + +// Default help command if implementation doesn't prvide one +int commmand_default_help(const char * const program_name, int argc, char **argv); +int command_lcrd_valid_socket(command_option_t *option, const char *arg); +int parse_args(struct service_arguments *args, int argc, const char **argv); +int check_args(struct service_arguments *args); +int update_hosts(struct service_arguments *args); +int update_default_ulimit(struct service_arguments *args); + + +#define LCRD_OPTIONS(cmdargs) \ + { CMD_OPT_TYPE_CALLBACK, false, "host", 'H', &(cmdargs)->hosts, \ + "The socket name used to create gRPC server", command_valid_socket_append_array }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "pidfile", 'p', &(cmdargs)->json_confs->pidfile, \ + "Save pid into this file", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "help", 0, &(cmdargs)->help, "Show help", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "hook-spec", 0, &(cmdargs)->json_confs->hook_spec, \ + "Default hook spec file applied to all containers", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "graph", 'g', &(cmdargs)->json_confs->graph, \ + "Root directory of the LCRD runtime", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "state", 'S', &(cmdargs)->json_confs->state, \ + "Root directory for execution state files", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "start-timeout", 0, &(cmdargs)->json_confs->start_timeout, \ + "timeout duration for waiting on a container to start before it is killed", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "engine", 'e', &(cmdargs)->json_confs->engine, "Select backend engine", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "log-level", 'l', &(cmdargs)->json_confs->log_level, \ + "Set log level, the levels can be: FATAL ALERT CRIT ERROR WARN NOTICE INFO DEBUG TRACE", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "log-driver", 0, &(cmdargs)->json_confs->log_driver, \ + "Set daemon log driver, such as: file", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "log-opt", 0, (cmdargs), \ + "Set daemon log driver options, such as: log-path=/tmp/logs/ to set directory where to store daemon logs", \ + server_callback_log_opt }, \ + { CMD_OPT_TYPE_BOOL, false, "version", 'V', &(cmdargs)->version, "Print the version", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "group", 'G', &(cmdargs)->json_confs->group, \ + "Group for the unix socket(default is lcrd)", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "storage-driver", 0, &(cmdargs)->json_confs->storage_driver, \ + "Storage driver to use(default overlay2)", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "storage-opt", 's', &(cmdargs)->json_confs->storage_opts, \ + "Storage driver options", command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "registry-mirrors", 0, &(cmdargs)->json_confs->registry_mirrors, \ + "Registry to be prepended when pulling unqualified images, can be specified multiple times", \ + command_append_array }, \ + { CMD_OPT_TYPE_CALLBACK, false, "insecure-registry", 0, &(cmdargs)->json_confs->insecure_registries, \ + "Disable TLS verification for the given registry", command_append_array }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "native.umask", 0, &(cmdargs)->json_confs->native_umask, \ + "Default file mode creation mask (umask) for containers", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "cgroup-parent", 0, &(cmdargs)->json_confs->cgroup_parent, \ + "Set parent cgroup for all containers", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "pod-sandbox-image", 0, &(cmdargs)->json_confs->pod_sandbox_image, \ + "The image whose network/ipc namespaces containers in each pod will use. " \ + "(default \"rnd-dockerhub.huawei.com/library/pause-${machine}:3.0\")", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "image-opt-timeout", 0, &(cmdargs)->json_confs->im_opt_timeout, \ + "Max timeout(default 5m) for image operation", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "network-plugin", 0, &(cmdargs)->json_confs->network_plugin, \ + "Set network plugin, default is null, suppport null and cni", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "cni-bin-dir", 0, &(cmdargs)->json_confs->cni_bin_dir, \ + "The full path of the directory in which to search for CNI plugin binaries. Default: /opt/cni/bin", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "cni-conf-dir", 0, &(cmdargs)->json_confs->cni_conf_dir, \ + "The full path of the directory in which to search for CNI config files. Default: /etc/cni/net.d", NULL }, \ + { CMD_OPT_TYPE_BOOL, false, "image-layer-check", 0, &(cmdargs)->json_confs->image_layer_check, \ + "Check layer intergrity when needed", NULL}, \ + { CMD_OPT_TYPE_BOOL, false, "insecure-skip-verify-enforce", 0, \ + &(cmdargs)->json_confs->insecure_skip_verify_enforce, \ + "Force to skip the insecure verify(default false)", NULL}, \ + { CMD_OPT_TYPE_BOOL, false, "use-decrypted-key", 0, (cmdargs)->json_confs->use_decrypted_key, \ + "Use decrypted private key by default(default true)", NULL}, \ + { CMD_OPT_TYPE_STRING_DUP, false, "authorization-plugin", 0, &(cmdargs)->json_confs->authorization_plugin, \ + "Use authorization plugin", NULL}, \ + { CMD_OPT_TYPE_BOOL, false, "tls", 0, &(cmdargs)->json_confs->tls, \ + "Use TLS; implied by --tlsverify", NULL}, \ + { CMD_OPT_TYPE_BOOL, false, "tlsverify", 0, &(cmdargs)->json_confs->tls_verify, \ + "Use TLS and verify the remote", NULL}, \ + { CMD_OPT_TYPE_STRING_DUP, false, "tlscacert", 0, &(cmdargs)->json_confs->tls_config->ca_file, \ + "Trust certs signed only by this CA (default \"/root/.iSulad/ca.pem\")", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "tlscert", 0, &(cmdargs)->json_confs->tls_config->cert_file, \ + "Path to TLS certificate file (default \"/root/.iSulad/cert.pem\")", NULL }, \ + { CMD_OPT_TYPE_STRING_DUP, false, "tlskey", 0, &(cmdargs)->json_confs->tls_config->key_file, \ + "Path to TLS key file (default \"/root/.iSulad/key.pem\")", NULL }, \ + { CMD_OPT_TYPE_CALLBACK, false, "default-ulimit", 0, &(cmdargs)->default_ulimit, \ + "Default ulimits for containers (default [])", command_default_ulimit_append } + +#endif /* __COMMAND_H */ diff --git a/src/cmd/lcrd/main.c b/src/cmd/lcrd/main.c new file mode 100644 index 0000000..d6d8b51 --- /dev/null +++ b/src/cmd/lcrd/main.c @@ -0,0 +1,1702 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide init process of lcrd + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SYSTEMD_NOTIFY +#include +#endif + +#include "constants.h" +#include "liblcrd.h" +#include "securec.h" +#include "collector.h" +#include "commands.h" +#include "log.h" +#include "engine.h" +#include "utils.h" +#include "lcrd_config.h" +#include "image.h" +#include "sysinfo.h" +#include "verify.h" +#include "monitord.h" +#include "service_common.h" +#include "callback.h" +#include "log_gather.h" +#include "containers_store.h" +#include "restore.h" +#include "supervisor.h" +#include "containers_gc.h" +#include "plugin.h" + + +#ifdef ENABLE_OCI_IMAGE +#include "driver.h" +#endif + +#ifdef GRPC_CONNECTOR +#include "clibcni/api.h" +#endif + +#ifdef ENABLE_EMBEDDED_IMAGE +#include "db_common.h" +#endif + +sem_t g_daemon_shutdown_sem; +sem_t g_print_backtrace_sem; +int g_backtrace_log_fd = -1; + +static int create_client_run_path(const char *group) +{ + int ret = 0; + const char *rundir = "/var/run/lcrc"; + if (group == NULL) { + return -1; + } + ret = util_mkdir_p(rundir, DEFAULT_SECURE_DIRECTORY_MODE); + if (ret < 0) { + ERROR("Unable to create client run directory %s.", rundir); + return ret; + } + + ret = chmod(rundir, DEFAULT_SECURE_DIRECTORY_MODE); + if (ret < 0) { + ERROR("Failed to chmod for client run path: %s", rundir); + } + + return ret; +} + +static int mount_rootfs_mnt_dir(const char *mountdir) +{ + int ret = -1; + char *p = NULL; + char *rootfsdir = NULL; + mountinfo_t **minfos = NULL; + mountinfo_t *info = NULL; + + if (mountdir == NULL) { + ERROR("parent mount path is NULL"); + goto out; + } + + rootfsdir = util_strdup_s(mountdir); + + ret = util_mkdir_p(rootfsdir, ROOTFS_MNT_DIRECTORY_MODE); + if (ret < 0) { + ERROR("Failed to create rootfs directory:%s", rootfsdir); + goto out; + } + + // find parent directory + p = strrchr(rootfsdir, '/'); + if (p == NULL) { + ERROR("Failed to find parent directory for %s", rootfsdir); + goto out; + } + *p = '\0'; + + minfos = getmountsinfo(); + if (minfos == NULL) { + ERROR("Failed to get mounts info"); + goto out; + } + + info = find_mount_info(minfos, rootfsdir); + if (info == NULL) { + ret = mount(rootfsdir, rootfsdir, "bind", MS_BIND | MS_REC, NULL); + if (ret < 0) { + ERROR("Failed to mount parent directory %s:%s", rootfsdir, strerror(errno)); + goto out; + } + } + ret = 0; + +out: + free(rootfsdir); + free_mounts_info(minfos); + return ret; +} + +#ifdef ENABLE_OCI_IMAGE +static int umount_rootfs_mnt_dir(const char *mntdir) +{ + int ret = -1; + char *p = NULL; + char *dir = NULL; + + dir = util_strdup_s(mntdir); + + // find parent directory + p = strrchr(dir, '/'); + if (p == NULL) { + ERROR("Failed to find parent directory for %s", dir); + goto out; + } + *p = '\0'; + + ret = umount(dir); + if (ret < 0 && errno != EINVAL) { + WARN("Failed to umount parent directory %s:%s", dir, strerror(errno)); + goto out; + } + + ret = 0; + +out: + free(dir); + return ret; +} + +static void umount_daemon_mntpoint() +{ + char *mntdir = NULL; + + mntdir = conf_get_lcrd_mount_rootfs(); + if (mntdir == NULL) { + ERROR("Out of memory"); + } else { + umount_rootfs_mnt_dir(mntdir); + free(mntdir); + mntdir = NULL; + } + + graphdriver_umount_mntpoint(); +} +#endif + +static void daemon_shutdown() +{ + char *pidfile = NULL; + char *checked_flag = NULL; + +#ifdef ENABLE_EMBEDDED_IMAGE + /* shutdown db */ + db_common_finish(); +#endif + + /* shutdown server */ + server_common_shutdown(); + +#ifdef ENABLE_OCI_IMAGE + umount_daemon_mntpoint(); +#endif + + /* remove image checked file */ + checked_flag = conf_get_graph_check_flag_file(); + if (checked_flag == NULL) { + ERROR("Failed to get image checked flag file path"); + } else if (unlink(checked_flag) && errno != ENOENT) { + ERROR("Unlink file: %s error: %s", checked_flag, strerror(errno)); + } + free(checked_flag); + checked_flag = NULL; + + /* remove pid file */ + pidfile = conf_get_lcrd_pidfile(); + if (pidfile == NULL) { + ERROR("Failed to get LCRD pid file path"); + } else if (unlink(pidfile) && errno != ENOENT) { + WARN("Unlink file: %s error: %s", pidfile, strerror(errno)); + } + free(pidfile); + pidfile = NULL; +} + +static void sigint_handler(int x) +{ + INFO("Got SIGINT; exiting"); + sem_post(&g_daemon_shutdown_sem); +} + +static void sigterm_handler(int signo) +{ + INFO("Got SIGTERM; exiting"); + sem_post(&g_daemon_shutdown_sem); +} + +#define BT_BUF_SIZE 100 +#define MAX_BT_SIZE (3 * 1024) +static void print_callstack(void) +{ + int j = 0; + int nptrs = 0; + int nret = 0; + void *buffer[BT_BUF_SIZE] = { NULL }; + char msg[MAX_BT_SIZE] = { 0 }; + char tname[16] = { 0 }; + char **strings = NULL; + pid_t tid = 0; + size_t avalid_size = 0; + + prctl(PR_GET_NAME, tname); + tid = (pid_t)syscall(__NR_gettid); + + nptrs = backtrace(buffer, BT_BUF_SIZE); + strings = backtrace_symbols(buffer, nptrs); + if (strings == NULL) { + ERROR("backtrace_symbols return nothing"); + goto out; + } + + nret = sprintf_s(msg, MAX_BT_SIZE, "[%s] tid:%d backtrace:\n", tname, tid); + if (nret < 0 || nret > MAX_BT_SIZE) { + ERROR("Failed to print [%s] tid:%d backtrace headinfo", tname, tid); + goto out; + } + + for (j = 0; j < nptrs; j++) { + avalid_size = MAX_BT_SIZE - strlen(msg); + if ((strlen(strings[j]) + strlen(" \n")) <= (avalid_size - 1)) { + nret = sprintf_s(msg + strlen(msg), avalid_size, " %s\n", strings[j]); + if (nret < 0 || (size_t)nret > avalid_size) { + ERROR("Failed to print backtrace %s", strings[j]); + goto out; + } + } else { + break; + } + } + nret = (int)write(g_backtrace_log_fd, msg, strlen(msg)); + if (nret < 0) { + ERROR("Failed to write backtrace info: %s", strerror(errno)); + goto out; + } + +out: + if (sem_wait(&g_print_backtrace_sem) == -1) { + ERROR("Failed to wait"); + } + + free(strings); + return; +} + +static void sigusr1_handler(int signo) +{ + INFO("Got SIGUSER1; print back trace"); + print_callstack(); + return; +} + +static int create_isulad_monitor_log_file() +{ + int ret = 0; + int tmp_fd = -1; + char *root_dir = NULL; + struct tm *tm_now = NULL; + time_t currtime = time(0); + char log_file[PATH_MAX] = { 0 }; + char fn[PATH_MAX] = { 0 }; + + root_dir = conf_get_lcrd_rootdir(); + if (root_dir == NULL) { + ERROR("Get rootpath failed"); + ret = -1; + goto out; + } + + tm_now = localtime(&currtime); + if (tm_now == NULL) { + ERROR("Failed to get current time"); + ret = -1; + goto out; + } + + if (strftime(log_file, sizeof(log_file), "%Y%m%d%H%M%S", tm_now) == 0) { + ret = -1; + goto out; + } + + ret = sprintf_s(fn, sizeof(fn), "%s/%s/%s", root_dir, "isulad-monitor", log_file); + if (ret < 0) { + ERROR("Failed to print string"); + ret = -1; + goto out; + } + + ret = util_build_dir(fn); + if (ret < 0) { + WARN("Failed to create directory for log file: %s", fn); + ret = -1; + goto out; + } + + tmp_fd = util_open(fn, O_RDWR | O_CREAT, DEFAULT_SECURE_FILE_MODE); + if (tmp_fd < 0) { + WARN("Failed to open log file: %s", fn); + ret = -1; + goto out; + } + + if (g_backtrace_log_fd != -1) { + close(g_backtrace_log_fd); + } + g_backtrace_log_fd = tmp_fd; + + ret = 0; + +out: + free(root_dir); + return ret; +} + +static void send_dump_req(void) +{ + int ret = 0; + size_t subdir_num = 0; + size_t i = 0; + char **subdir = NULL; + pid_t tid = 0; + pid_t pid = 0; + + ret = create_isulad_monitor_log_file(); + if (ret != 0) { + goto out; + } + + ret = util_list_all_subdir("/proc/self/task", &subdir); + if (ret < 0) { + ERROR("Failed to read /proc/self/task' subdirectory"); + goto out; + } + subdir_num = util_array_len(subdir); + if (subdir_num == 0) { + goto out; + } + + pid = getpid(); + if (pid < 0) { + goto out; + } + + ret = sem_init(&g_print_backtrace_sem, 0, (unsigned int)subdir_num); + if (ret != 0) { + goto out; + } + + for (i = 0; i < subdir_num; i++) { + ret = util_safe_int(subdir[i], &tid); + if (ret < 0) { + (void)sem_wait(&g_print_backtrace_sem); + continue; + } + ret = (int)syscall(SYS_tgkill, pid, tid, SIGUSR1); + if (ret < 0) { + ERROR("Failed to send SIGUSR1 to thread id:%d in process:%d", tid, pid); + (void)sem_wait(&g_print_backtrace_sem); + } + } + +out: + util_free_array(subdir); + return; +} + +static void sigrtmin_handler(int signo) +{ + int tmp_sval = 0; + + if (sem_getvalue(&g_print_backtrace_sem, &tmp_sval) == 0) { + if (tmp_sval == 0) { + send_dump_req(); + } + } + + return; +} + +static int ignore_signals() +{ + struct sigaction sa; + + /* + * Ignore SIGHUP so lcrd process still exists after + * terminal die. + */ + if (memset_s(&sa, sizeof(struct sigaction), 0, sizeof(struct sigaction)) != EOK) { + ERROR("Failed to set memory"); + return -1; + } + + sa.sa_handler = SIG_IGN; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGHUP, &sa, NULL) < 0) { + ERROR("Failed to ignore SIGHUP"); + return -1; + } + + if (sigaction(SIGPIPE, &sa, NULL) < 0) { + ERROR("Failed to ignore SIGPIPE"); + return -1; + } + + return 0; +} + +static int add_shutdown_signal_handler() +{ + struct sigaction sa; + + if (memset_s(&sa, sizeof(struct sigaction), 0, sizeof(struct sigaction)) != EOK) { + ERROR("Failed to set memory"); + return -1; + } + + if (sem_init(&g_daemon_shutdown_sem, 0, 0) == -1) { + ERROR("Failed to init daemon shutdown sem"); + return -1; + } + + sa.sa_handler = sigint_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGINT, &sa, NULL) < 0) { + ERROR("Failed to add handler for SIGINT"); + return -1; + } + + if (memset_s(&sa, sizeof(struct sigaction), 0, sizeof(struct sigaction)) != EOK) { + ERROR("Failed to set memory"); + return -1; + } + + sa.sa_handler = sigterm_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGTERM, &sa, NULL) < 0) { + ERROR("Failed to add handler for SIGTERM"); + return -1; + } + + return 0; +} + +static int add_print_bt_handler() +{ + struct sigaction sa; + + if (memset_s(&sa, sizeof(struct sigaction), 0, sizeof(struct sigaction)) != EOK) { + ERROR("Failed to set memory"); + return -1; + } + + if (sem_init(&g_print_backtrace_sem, 0, 0) == -1) { + ERROR("Failed to init"); + return -1; + } + + sa.sa_handler = sigusr1_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGUSR1, &sa, NULL) < 0) { + ERROR("Failed to add handler for SIGUSR1"); + return -1; + } + + if (memset_s(&sa, sizeof(struct sigaction), 0, sizeof(struct sigaction)) != EOK) { + ERROR("Failed to set memory"); + return -1; + } + + sa.sa_handler = sigrtmin_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGRTMIN, &sa, NULL) < 0) { + ERROR("Failed to add handler for SIGRTMIN"); + return -1; + } + + return 0; +} + +static int add_sighandler() +{ + if (ignore_signals() != 0) { + ERROR("Failed to ignore signals"); + return -1; + } + + if (add_shutdown_signal_handler() != 0) { + ERROR("Failed to add shutdown signals"); + return -1; + } + + if (add_print_bt_handler() != 0) { + ERROR("Failed to add print back trace signals"); + return -1; + } + + return 0; +} + +static int daemonize() +{ + int ret = 0; + struct service_arguments *args = NULL; + + umask(0000); + + if (lcrd_server_conf_rdlock()) { + ret = -1; + goto out; + } + + args = conf_get_server_conf(); + if (args == NULL) { + ERROR("Failed to get lcrd server config"); + ret = -1; + goto unlock_out; + } + + if (args->json_confs != NULL && create_client_run_path(args->json_confs->group)) { + ERROR("Create client run directory failed"); + ret = -1; + goto unlock_out; + } + + /* + * close all file descriptors + */ + if (util_check_inherited(true, -1)) { + ERROR("Failed to close fds."); + ret = -1; + } +unlock_out: + if (lcrd_server_conf_unlock()) { + ret = -1; + } +out: + umask(0022); + return ret; +} + +int check_and_save_pid(const char *fn) +{ + int fd = -1; + int ret = 0; + int len = 0; + struct flock lk; + char pidbuf[LCRD_NUMSTRLEN64] = { 0 }; + + if (fn == NULL) { + ERROR("Input error"); + return -1; + } + + ret = util_build_dir(fn); + if (ret) { + WARN("Failed to create directory for pid file: %s", fn); + return -1; + } + + fd = util_open(fn, O_RDWR | O_CREAT, DEFAULT_SECURE_FILE_MODE); + if (fd < 0) { + WARN("Failed to open pid file: %s", fn); + return -1; + } + + lk.l_type = F_WRLCK; + lk.l_whence = SEEK_SET; + lk.l_start = 0; + lk.l_len = 0; + if (fcntl(fd, F_SETLK, &lk) != 0) { + /* another daemonize instance is already running, don't start up */ + COMMAND_ERROR("Pid file found, ensure lcrd is not running or delete pid file %s" + " or please specify another pid file", fn); + ret = -1; + goto out; + } + + ret = ftruncate(fd, 0); + if (ret != 0) { + ERROR("Failed to truncate pid file:%s to 0: %s", fn, strerror(errno)); + ret = -1; + goto out; + } + + len = sprintf_s(pidbuf, sizeof(pidbuf), "%lu\n", (unsigned long)getpid()); + if (len < 0) { + ERROR("failed sprint pidbuf"); + ret = -1; + goto out; + } + + len = (int)write(fd, pidbuf, strlen(pidbuf)); + if (len < 0) { + ERROR("Failed to write pid to file:%s: %s", fn, strerror(errno)); + ret = -1; + } +out: + if (ret < 0) { + close(fd); + } + return ret; +} + +int check_and_set_default_lcrd_log_file(struct service_arguments *args) +{ + if (args == NULL) { + return -1; + } + if (args != NULL && args->json_confs != NULL && args->json_confs->log_driver != NULL && + strcasecmp("file", args->json_confs->log_driver) == 0) { + if ((args->logpath == NULL || strcmp("", args->logpath) == 0) && args->json_confs->graph != NULL) { + free(args->logpath); + args->logpath = util_strdup_s(args->json_confs->graph); + } + } + if (args != NULL && util_validate_absolute_path(args->logpath)) { + ERROR("Daemon log path \"%s\" must be abosulte path.", args->logpath); + return -1; + } + return 0; +} + +static int set_parent_mount_dir(struct service_arguments *args) +{ + int ret = -1; + int nret; + size_t len; + char *rootfsdir = NULL; + + if (args->json_confs == NULL) { + ERROR("Empty json configs"); + goto out; + } + if (strlen(args->json_confs->graph) > (SIZE_MAX - strlen("/mnt/rootfs")) - 1) { + ERROR("Root directory of the LCRD runtime is too long"); + goto out; + } + len = strlen(args->json_confs->graph) + strlen("/mnt/rootfs") + 1; + if (len > PATH_MAX) { + ERROR("The size of path exceeds the limit"); + goto out; + } + rootfsdir = util_common_calloc_s(len); + if (rootfsdir == NULL) { + ERROR("Out of memory"); + goto out; + } + nret = sprintf_s(rootfsdir, len, "%s/mnt/rootfs", args->json_confs->graph); + if (nret < 0) { + ERROR("Failed to print string"); + goto out; + } + + free(args->json_confs->rootfsmntdir); + args->json_confs->rootfsmntdir = util_strdup_s(rootfsdir); + + ret = 0; + +out: + free(rootfsdir); + return ret; +} + +static int check_hook_spec_file(const char *hook_spec) +{ + struct stat hookstat = { 0 }; + + if (hook_spec == NULL) { + return 0; + } + if (util_validate_absolute_path(hook_spec)) { + ERROR("Hook path \"%s\" must be an absolute path", hook_spec); + return -1; + } + if (stat(hook_spec, &hookstat)) { + ERROR("Stat hook spec file failed: %s", strerror(errno)); + return -1; + } + if ((hookstat.st_mode & S_IFMT) != S_IFREG) { + ERROR("Hook spec file must be a regular text file"); + return -1; + } + return 0; +} + +static int parse_hook_spec(const char *specfile, oci_runtime_spec_hooks **phooks) +{ + int ret = 0; + parser_error err = NULL; + oci_runtime_spec_hooks *hooks = NULL; + + if (check_hook_spec_file(specfile)) { + ret = -1; + goto out; + } + + hooks = oci_runtime_spec_hooks_parse_file(specfile, NULL, &err); + if (hooks == NULL) { + ERROR("Failed to parse hook-spec file: %s", err); + lcrd_set_error_message("Invalid hook-spec file(%s) in server conf.", specfile); + ret = -1; + goto out; + } + + ret = verify_oci_hook(hooks); + if (ret) { + ERROR("Verify hook file failed"); + free_oci_runtime_spec_hooks(hooks); + goto out; + } + + *phooks = hooks; + +out: + free(err); + err = NULL; + return ret; +} + +static void update_isulad_rlimits() +{ +#define __ULIMIT_CONFIG_VAL_ 1048576 + struct rlimit limit; + + /* set ulimit of process */ + limit.rlim_cur = __ULIMIT_CONFIG_VAL_; + limit.rlim_max = __ULIMIT_CONFIG_VAL_; + if (setrlimit(RLIMIT_NOFILE, &limit)) { + WARN("Can not set ulimit of RLIMIT_NOFILE: %s", strerror(errno)); + } + + if (setrlimit(RLIMIT_NPROC, &limit)) { + WARN("Can not set ulimit of RLIMIT_NPROC: %s", strerror(errno)); + } + limit.rlim_cur = RLIM_INFINITY; + limit.rlim_max = RLIM_INFINITY; + if (setrlimit(RLIMIT_CORE, &limit)) { + WARN("Can not set ulimit of RLIMIT_CORE: %s", strerror(errno)); + } +} + +static int validate_time_duration(const char *value) +{ + regex_t preg; + int status = 0; + regmatch_t regmatch = { 0 }; + + if (value == NULL) { + return -1; + } + + if (regcomp(&preg, "^([1-9][0-9]*)+([s,m])$", REG_NOSUB | REG_EXTENDED)) { + ERROR("Failed to compile the regex\n"); + return -1; + } + + status = regexec(&preg, value, 1, ®match, 0); + regfree(&preg); + if (status != 0) { + ERROR("Error start-timeout value: %s\n", value); + COMMAND_ERROR("Invalid time duration value(%s) in server conf, " + "only ^([1-9][0-9]*)+([s,m])$ are allowed.", + value); + return -1; + } + return 0; +} + +static int parse_time_duration(const char *value, unsigned int *seconds) +{ + int ret = 0; + unsigned int tmp = 0; + char unit = 0; + size_t len = 0; + char *num_str = NULL; + + if (validate_time_duration(value) != 0) { + return -1; + } + + num_str = util_strdup_s(value); + + len = strlen(value); + unit = *(value + len - 1); + *(num_str + len - 1) = '\0'; + ret = util_safe_uint(num_str, &tmp); + if (ret < 0) { + ERROR("Illegal unsigned integer: %s", num_str); + COMMAND_ERROR("Illegal unsigned integer:%s:%s", num_str, strerror(-ret)); + ret = -1; + goto out; + } + + if (tmp == 0) { + goto out; + } + + switch (unit) { + case 'm': + if (UINT_MAX / tmp > 60) { + tmp *= 60; + } else { + ERROR("The time duration value(%s) is too large, please reset it!", num_str); + COMMAND_ERROR("The time duration value(%s) is too large, please reset it!", num_str); + ret = -1; + } + break; + case 's': + break; + default: + COMMAND_ERROR("Unsupported unit:%c", unit); + ret = -1; + break; + } + + *seconds = tmp; +out: + free(num_str); + return ret; +} + +// update values for options after flag parsing is complete +static int update_tls_options(struct service_arguments *args) +{ + int ret = 0; + char *ca_real_file = NULL; + char *cert_real_file = NULL; + char *key_real_file = NULL; + + if (args->json_confs->tls_verify) { + args->json_confs->tls = true; + } + + if (!args->json_confs->tls) { + free_isulad_daemon_configs_tls_config(args->json_confs->tls_config); + args->json_confs->tls_config = NULL; + } else { + if (args->json_confs->tls_verify) { + ca_real_file = verify_file_and_get_real_path(args->json_confs->tls_config->ca_file); + if (ca_real_file == NULL) { + ERROR("Invalid CaFile(%s)!", args->json_confs->tls_config->ca_file); + COMMAND_ERROR("Invalid CaFile(%s)", args->json_confs->tls_config->ca_file); + ret = -1; + goto out; + } + free(args->json_confs->tls_config->ca_file); + args->json_confs->tls_config->ca_file = ca_real_file; + } + cert_real_file = verify_file_and_get_real_path(args->json_confs->tls_config->cert_file); + if (cert_real_file == NULL) { + ERROR("Invalid CertFile(%s)", args->json_confs->tls_config->cert_file); + COMMAND_ERROR("Invalid CertFile(%s)", args->json_confs->tls_config->cert_file); + ret = -1; + goto out; + } + free(args->json_confs->tls_config->cert_file); + args->json_confs->tls_config->cert_file = cert_real_file; + + key_real_file = verify_file_and_get_real_path(args->json_confs->tls_config->key_file); + if (key_real_file == NULL) { + ERROR("Invalid KeyFile(%s)", args->json_confs->tls_config->key_file); + COMMAND_ERROR("Invalid CertFile(%s)", args->json_confs->tls_config->key_file); + ret = -1; + goto out; + } + free(args->json_confs->tls_config->key_file); + args->json_confs->tls_config->key_file = key_real_file; + } +out: + return ret; +} + +static int update_set_default_log_file(struct service_arguments *args) +{ + int ret = 0; + + if (args->json_confs->log_driver && strcasecmp("stdout", args->json_confs->log_driver) == 0) { + args->quiet = false; + } + + if (check_and_set_default_lcrd_log_file(args)) { + ret = -1; + goto out; + } + +out: + return ret; +} + +static int parse_conf_hooks(struct service_arguments *args) +{ + int ret = 0; + + if (args->json_confs->hook_spec != NULL && parse_hook_spec(args->json_confs->hook_spec, &args->hooks) != 0) { + ret = -1; + goto out; + } + +out: + return ret; +} + +static int parse_conf_time_duration(struct service_arguments *args) +{ + int ret = 0; + + /* parse start timeout */ + if (args->json_confs->start_timeout != NULL && + parse_time_duration(args->json_confs->start_timeout, &args->start_timeout)) { + ret = -1; + goto out; + } + + /* parse image opt timeout */ + if (args->json_confs->im_opt_timeout != NULL && + parse_time_duration(args->json_confs->im_opt_timeout, &args->im_opt_timeout)) { + ret = -1; + goto out; + } + +out: + return ret; +} + +static int update_server_args(struct service_arguments *args) +{ + int ret = 0; + + if (update_tls_options(args)) { + ret = -1; + goto out; + } + + if (update_set_default_log_file(args) != 0) { + ret = -1; + goto out; + } + + if (update_hosts(args) != 0) { + ret = -1; + goto out; + } + + if (update_default_ulimit(args) != 0) { + ret = -1; + goto out; + } + + /* check args */ + if (check_args(args)) { + ret = -1; + goto out; + } + + if (set_parent_mount_dir(args)) { + ret = -1; + goto out; + } + + /* parse hook spec */ + if (parse_conf_hooks(args) != 0) { + ret = -1; + goto out; + } + + /* parse image opt timeout */ + if (parse_conf_time_duration(args) != 0) { + ret = -1; + goto out; + } + +#ifdef ENABLE_OCI_IMAGE + args->driver = graphdriver_init(args->json_confs->storage_driver, args->json_confs->storage_opts, + args->json_confs->storage_opts_len); + if (args->driver == NULL) { + ret = -1; + goto out; + } +#endif + +out: + return ret; +} + +static int server_conf_parse_save(int argc, const char **argv) +{ + int ret = 0; + struct service_arguments *args = NULL; + + args = util_common_calloc_s(sizeof(struct service_arguments)); + if (args == NULL) { + ERROR("memory out"); + ret = -1; + goto out; + } + + /* Step1: set default value to configs */ + if (service_arguments_init(args) != 0) { + ERROR("Failed to init service arguments"); + ret = -1; + goto out; + } + + /* Step2: load json configs and merge into global configs */ + if (merge_json_confs_into_global(args) != 0) { + ret = -1; + goto out; + } + + /* Step3: option from command line override configuration file */ + if (parse_args(args, argc, argv)) { + ERROR("parse args failed"); + ret = -1; + goto out; + } + + if (update_server_args(args) != 0) { + ret = -1; + goto out; + } + + if (save_args_to_conf(args)) { + ERROR("Failed to save arguments"); + ret = -1; + goto out; + } + +out: + if (ret != 0) { + service_arguments_free(args); + free(args); + } + return ret; +} + +static int init_log_gather_thread(const char *log_full_path, struct log_config *plconf, + const struct service_arguments *args) +{ + pthread_t log_thread = { 0 }; + struct log_gather_conf lgconf = { 0 }; + int log_gather_exitcode = -1; + + lgconf.log_file_mode = args->log_file_mode; + lgconf.fifo_path = plconf->file; + lgconf.g_log_driver = plconf->driver; + lgconf.log_path = log_full_path; + lgconf.max_size = args->max_size; + lgconf.max_file = args->max_file; + lgconf.exitcode = &log_gather_exitcode; + if (pthread_create(&log_thread, NULL, log_gather, &lgconf)) { + printf("Failed to create log monitor thread\n"); + return -1; + } + while (1) { + usleep_nointerupt(1000); + if (log_gather_exitcode >= 0) { + break; + } + } + + return log_gather_exitcode; +} + +static int lcrd_get_log_path(char **log_full_path, char **fifo_full_path) +{ + int ret = 0; + + *log_full_path = conf_get_lcrd_log_file(); + if (*log_full_path == NULL) { + ret = -1; + goto out; + } + *fifo_full_path = conf_get_lcrd_log_gather_fifo_path(); + if (*fifo_full_path == NULL) { + ret = -1; + goto out; + } +out: + return ret; +} + +static int lcrd_server_init_log(const struct service_arguments *args, const char *log_full_path, + const char *fifo_full_path) +{ +#define FIFO_DRIVER "fifo" + int ret = -1; + struct log_config lconf = { 0 }; + + lconf.name = args->progname; + lconf.file = fifo_full_path; + lconf.driver = FIFO_DRIVER; + lconf.priority = args->json_confs->log_level; + if (log_init(&lconf) != 0) { + ERROR("Failed to init log"); + goto out; + } + +#ifdef GRPC_CONNECTOR + /* init clibcni log */ + if (cni_log_init(FIFO_DRIVER, fifo_full_path, args->json_confs->log_level) != 0) { + ERROR("Failed to init cni log"); + goto out; + } +#endif + + lconf.driver = args->json_confs->log_driver; + if (init_log_gather_thread(log_full_path, &lconf, args)) { + ERROR("Log gather start failed"); + goto out; + } + + ret = 0; +out: + return ret; +} + +static int lcrd_server_pre_init(const struct service_arguments *args, const char *log_full_path, + const char *fifo_full_path) +{ + int ret = 0; + + if (check_and_save_pid(args->json_confs->pidfile) != 0) { + ERROR("Failed to save pid"); + ret = -1; + goto out; + } + + if (lcrd_server_init_log(args, log_full_path, fifo_full_path) != 0) { + ret = -1; + goto out; + } + + if (util_mkdir_p(args->json_confs->state, DEFAULT_SECURE_FILE_MODE) != 0) { + ERROR("Unable to create state directory %s.", args->json_confs->state); + ret = -1; + goto out; + } + + if (util_mkdir_p(args->json_confs->graph, CONFIG_DIRECTORY_MODE) != 0) { + ERROR("Unable to create root directory %s.", args->json_confs->graph); + ret = -1; + goto out; + } + + if (mount_rootfs_mnt_dir(args->json_confs->rootfsmntdir)) { + ERROR("Create and mount parent directory failed"); + ret = -1; + goto out; + } + +#ifdef ENABLE_EMBEDDED_IMAGE + if (db_common_init(args->json_confs->graph)) { + ERROR("Failed to init database"); + ret = -1; + goto out; + } +#endif + + if (service_callback_init()) { + ERROR("Failed to init service callback"); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int lcrd_server_init_common() +{ + int ret = -1; + struct service_arguments *args = NULL; + char *log_full_path = NULL; + char *fifo_full_path = NULL; + + if (lcrd_get_log_path(&log_full_path, &fifo_full_path) != 0) { + goto out; + } + + if (lcrd_server_conf_rdlock()) { + goto out; + } + + args = conf_get_server_conf(); + if (args == NULL) { + ERROR("Failed to get lcrd server config"); + goto unlock_out; + } + + if (lcrd_server_pre_init(args, log_full_path, fifo_full_path) != 0) { + goto unlock_out; + } + +#ifdef ENABLE_OCI_IMAGE + /* update status of graphdriver before init image module */ + update_graphdriver_status(&(args->driver)); +#endif + + if (image_module_init(args->json_confs->graph)) { + ERROR("Failed to init image manager"); + goto unlock_out; + } + + if (containers_store_init()) { + ERROR("Failed to init containers store"); + goto unlock_out; + } + + if (name_index_init()) { + ERROR("Failed to init name index"); + goto unlock_out; + } + + ret = 0; +unlock_out: + if (lcrd_server_conf_unlock()) { + ret = -1; + goto out; + } + +out: + free(log_full_path); + free(fifo_full_path); + return ret; +} + +static char *parse_host(bool tls, const char *val) +{ + char *host = NULL; + char *tmp = util_strdup_s(val); + tmp = util_trim_space(tmp); + if (tmp == NULL) { + if (tls) { + host = util_strdup_s(DEFAULT_TLS_HOST); + } else { + host = util_strdup_s(DEFAULT_UNIX_SOCKET); + } + } else { + host = util_strdup_s(val); + } + free(tmp); + return host; +} + +static int listener_init(const char *proto, const char *addr, const char *socket_group) +{ + int ret = 0; + + if (proto == NULL || addr == NULL) { + FATAL("Invalid input arguments"); + return -1; + } + + if (strcmp(proto, "unix") == 0) { + ret = set_unix_socket_group(addr, socket_group); + if (ret) { + FATAL("Can't create unix socket %s", addr); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int load_listener(const struct service_arguments *args) +{ + int ret = 0; + const char *delim = "://"; + char *proto = NULL; + char *addr = NULL; + size_t i; + + for (i = 0; i < args->hosts_len; i++) { + char *proto_addr = NULL; + + proto_addr = parse_host(args->json_confs->tls, args->hosts[i]); + proto = strtok_s(proto_addr, delim, &addr); + if (proto == NULL) { + ERROR("Failed to get proto"); + ret = -1; + free(proto_addr); + goto out; + } + addr += strlen("://") - 1; + + if (strncmp(proto, "tcp", strlen("tcp")) == 0 && + (args->json_confs->tls_config == NULL || !args->json_confs->tls_verify)) { + WARN("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting" + " --tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]"); + } + + // note: If we're binding to a TCP port, make sure that a container doesn't try to use it. + ret = listener_init(proto, args->hosts[i], args->json_confs->group); + free(proto_addr); + if (ret != 0) { + ERROR("Failed to init listener"); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int lcrd_server_init_service() +{ + int ret = -1; + struct service_arguments *args = NULL; + + if (lcrd_server_conf_rdlock()) { + goto out; + } + + args = conf_get_server_conf(); + if (args == NULL) { + ERROR("Failed to get lcrd server config"); + goto unlock_out; + } +#ifdef GRPC_CONNECTOR + INFO("Creating grpc server..."); +#else + INFO("Creating rest server..."); +#endif + if (server_common_init(args)) { + ERROR("Failed to init service"); + goto unlock_out; + } + + ret = load_listener(args); + if (ret != 0) { + ERROR("Failed to load listener"); + goto unlock_out; + } + +unlock_out: + if (lcrd_server_conf_unlock()) { + ret = -1; + goto out; + } +out: + return ret; +} + +static int lcrd_server_init_engines() +{ + int ret = 0; + char *engine = NULL; + + engine = conf_get_lcrd_engine(); + if (engine == NULL) { + ret = -1; + goto out; + } + + if (engines_global_init()) { + ERROR("Init engines global failed"); + ret = -1; + goto out; + } + + /* Init default engine, now is lcr */ + if (engines_discovery(engine)) { + ERROR("Failed to discovery default engine:%s", engine); + ret = -1; + } + +out: + free(engine); + return ret; +} + +static void set_mallopt() +{ + if (mallopt(M_ARENA_TEST, 8) == 0) { + fprintf(stderr, "change M_ARENA_TEST to 8\n"); + } + if (mallopt(M_TOP_PAD, 32 * 1024) == 0) { + fprintf(stderr, "chagne M_TOP_PAD to 32KB"); + } + if (mallopt(M_TRIM_THRESHOLD, 64 * 1024) == 0) { + fprintf(stderr, "change M_TRIM_THRESHOLD to 64KB"); + } + if (mallopt(M_MMAP_THRESHOLD, 64 * 1024) == 0) { + fprintf(stderr, "change M_MMAP_THRESHOLD to 64KB"); + } +} + +static int start_monitord() +{ + int ret = 0; + int monitord_exitcode = 0; + sem_t monitord_sem; + struct monitord_sync_data msync = { 0 }; + + msync.monitord_sem = &monitord_sem; + msync.exit_code = &monitord_exitcode; + if (sem_init(msync.monitord_sem, 0, 0)) { + lcrd_set_error_message("Failed to init monitor sem"); + ret = -1; + goto out; + } + + if (new_monitord(&msync)) { + lcrd_set_error_message("Create monitord thread failed"); + ret = -1; + sem_destroy(msync.monitord_sem); + goto out; + } + + sem_wait(msync.monitord_sem); + sem_destroy(msync.monitord_sem); + if (monitord_exitcode) { + lcrd_set_error_message("Monitord start failed"); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* shutdown handler */ +static void *do_shutdown_handler(void *arg) +{ + int res = 0; + + res = pthread_detach(pthread_self()); + if (res != 0) { + CRIT("Set thread detach fail"); + } + + prctl(PR_SET_NAME, "Shutdown"); + + sem_wait(&g_daemon_shutdown_sem); + + daemon_shutdown(); + + exit(0); +} + +/* news_shutdown_handler */ +int new_shutdown_handler() +{ + int ret = -1; + pthread_t shutdown_thread; + + INFO("Starting new shutdown handler..."); + ret = pthread_create(&shutdown_thread, NULL, do_shutdown_handler, NULL); + if (ret != 0) { + CRIT("Thread creation failed"); + goto out; + } + + ret = 0; +out: + return ret; +} + + +static int start_daemon_threads(char **msg) +{ + int ret = -1; + + if (new_shutdown_handler()) { + *msg = "Create new shutdown handler thread failed"; + goto out; + } + + if (newcollector()) { + *msg = "Create collector thread failed"; + goto out; + } + + if (start_monitord()) { + *msg = g_lcrd_errmsg ? g_lcrd_errmsg : "Failed to init cgroups path"; + goto out; + } + + if (new_gchandler()) { + *msg = "Create garbage handler thread failed"; + goto out; + } + + if (new_supervisor()) { + *msg = "Create supervisor thread failed"; + goto out; + } + + containers_restore(); + + if (start_gchandler()) { + *msg = "Failed to start garbage collecotor handler"; + goto out; + } + + ret = 0; +out: + return ret; +} + +static int pre_init_daemon_log() +{ + struct log_config lconf = { 0 }; + + lconf.name = "lcrd"; + lconf.quiet = true; + lconf.file = NULL; + lconf.priority = "ERROR"; + lconf.driver = "stdout"; + if (log_init(&lconf)) { + fprintf(stderr, "log init failed\n"); + return -1; + } + + return 0; +} + +static int pre_init_daemon(int argc, char **argv, char **msg) +{ + int ret = -1; + /* + * must call lcrd by root + */ + if (geteuid() != 0) { + *msg = "LCRD must be called by root"; + goto out; + } + + if (server_conf_parse_save(argc, (const char **)argv)) { + *msg = g_lcrd_errmsg ? g_lcrd_errmsg : "Failed to parse and save server conf"; + goto out; + } + + /* note: daemonize will close all fds */ + if (daemonize()) { + *msg = "Failed to become a daemon"; + goto out; + } + + if (lcrd_server_init_engines()) { + *msg = "Failed to init engines"; + goto out; + } + + /* + * change the current working dir to root. + */ + if (chdir("/") < 0) { + *msg = "Failed to change dir to /"; + goto out; + } + + ret = 0; +out: + return ret; +} + +/* + * Takes socket path as argument + */ +int main(int argc, char **argv) +{ + struct timespec t_start, t_end; + double use_time = 0; + char *msg = NULL; + + prctl(PR_SET_NAME, "lcrd"); + + if (pre_init_daemon_log() != 0) { + exit(ECOMMON); + } + + set_mallopt(); + + update_isulad_rlimits(); + + clock_gettime(CLOCK_MONOTONIC, &t_start); + + if (pre_init_daemon(argc, argv, &msg) != 0) { + goto failure; + } + + if (lcrd_server_init_common() != 0) { + goto failure; + } + + if (init_cgroups_path("/lxc", 0)) { + msg = g_lcrd_errmsg ? g_lcrd_errmsg : "Failed to init cgroups path"; + goto failure; + } + + if (add_sighandler()) { + msg = "Failed to add sig handlers"; + goto failure; + } + + if (start_daemon_threads(&msg)) { + goto failure; + } + + if (lcrd_server_init_service()) { + msg = "Failed to init services"; + goto failure; + } + + if (start_plugin_manager()) { + msg = "Failed to init plugin_manager"; + goto failure; + } + + clock_gettime(CLOCK_MONOTONIC, &t_end); + use_time = (double)(t_end.tv_sec - t_start.tv_sec) * (double)1000000000 + (double)(t_end.tv_nsec - t_start.tv_nsec); + use_time /= 1000000000; + INFO("Lcrd successfully booted in %.3f s", use_time); +#ifdef GRPC_CONNECTOR + INFO("Starting grpc server..."); +#else + INFO("Starting rest server..."); +#endif + +#ifdef SYSTEMD_NOTIFY + if (sd_notify(0, "READY=1") < 0) { + msg = "Failed to send notify the service manager about state changes"; + goto failure; + } +#endif + + server_common_start(); + + DAEMON_CLEAR_ERRMSG(); + return 0; + +failure: + if (msg != NULL) { + fprintf(stderr, "Start failed: %s\n", msg); + } + DAEMON_CLEAR_ERRMSG(); + exit(1); +} diff --git a/src/config/CMakeLists.txt b/src/config/CMakeLists.txt new file mode 100644 index 0000000..5b3e1c9 --- /dev/null +++ b/src/config/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} config_srcs) + +set(CONFIG_SRCS + ${config_srcs} + PARENT_SCOPE + ) diff --git a/src/config/lcrd_config.c b/src/config/lcrd_config.c new file mode 100644 index 0000000..9100f57 --- /dev/null +++ b/src/config/lcrd_config.c @@ -0,0 +1,1754 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container configure definition + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "securec.h" +#include "log.h" +#include "utils.h" +#include "lcrd_config.h" +#include "sysinfo.h" +#include "liblcrd.h" + +#define ENGINE_ROOTPATH_NAME "engines" +#define GRAPH_ROOTPATH_NAME "storage" +#define GRAPH_ROOTPATH_CHECKED_FLAG "NEED_CHECK" + +#define INCREMENT_INTREVAL 2 +#define BUFFER_ITEM_NUMS 10 + +static struct lcrd_conf g_lcrd_conf; +static double g_jiffy = 0.0; + +/* tick to ns */ +static inline unsigned long long tick_to_ns(uint64_t tick) +{ +#define EPSINON 0.0001 + + if (g_jiffy < EPSINON && g_jiffy > -EPSINON) { + g_jiffy = (double)sysconf(_SC_CLK_TCK); + } + + if ((uint64_t)(tick / g_jiffy) > (UINT64_MAX / Time_Second)) { + return UINT64_MAX; + } + return (uint64_t)((tick / g_jiffy) * Time_Second); +} + +/* + * returns the host system's cpu usage in nanoseconds. + * Uses /proc/stat defined by POSIX. Looks for the cpu statistics line + * and then sums up the first seven fields provided. + * See `man 5 proc` for details on specific field information. + */ +int get_system_cpu_usage(uint64_t *val) +{ + int ret = 0; + int nret; + unsigned long long total, usertime, nicetime, systemtime, idletime; + unsigned long long ioWait, irq, softIrq, steal, guest, guestnice; + char buffer[BUFSIZ + 1] = { 0 }; + char *tmp = NULL; + FILE *file = NULL; + + if (val == NULL) { + return -1; + } + + file = util_fopen("/proc/stat", "r"); + if (file == NULL) { + ERROR("Failed to open '/proc/stat'"); + return -1; + } + + ioWait = irq = softIrq = steal = guest = guestnice = 0; + + /* + * Depending on your kernel version, + * 5, 7, 8 or 9 of these fields will be set. + * The rest will remain at zero. + */ + tmp = fgets(buffer, BUFSIZ, file); + if (tmp == NULL) { + ret = -1; + goto out; + } + nret = sscanf_s(buffer, "cpu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu", &usertime, + &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice); + if (nret != BUFFER_ITEM_NUMS) { + ERROR("sscanf buffer failed"); + ret = -1; + goto out; + } + + total = usertime + nicetime + systemtime + idletime + ioWait + irq + softIrq; + + *val = tick_to_ns(total); +out: + fclose(file); + return ret; +} + +/* lcrd server conf wrlock */ +int lcrd_server_conf_wrlock() +{ + int ret = 0; + + if (pthread_rwlock_wrlock(&g_lcrd_conf.lcrd_conf_rwlock)) { + ERROR("Failed to acquire lcrd conf write lock"); + ret = -1; + } + + return ret; +} + +/* lcrd server conf rdlock */ +int lcrd_server_conf_rdlock() +{ + int ret = 0; + + if (pthread_rwlock_rdlock(&g_lcrd_conf.lcrd_conf_rwlock)) { + ERROR("Failed to acquire lcrd conf read lock"); + ret = -1; + } + + return ret; +} + +/* lcrd server conf unlock */ +int lcrd_server_conf_unlock() +{ + int ret = 0; + + if (pthread_rwlock_unlock(&g_lcrd_conf.lcrd_conf_rwlock)) { + ERROR("Failed to release lcrd conf lock"); + ret = -1; + } + + return ret; +} + +struct service_arguments *conf_get_server_conf() +{ + return g_lcrd_conf.server_conf; +} + +/* conf get lcrd pidfile */ +char *conf_get_lcrd_pidfile() +{ + char *filename = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs == NULL || conf->json_confs->pidfile == NULL) { + goto out; + } + + filename = util_strdup_s(conf->json_confs->pidfile); + +out: + (void)lcrd_server_conf_unlock(); + return filename; +} + +/* conf get engine rootpath */ +char *conf_get_engine_rootpath() +{ + char *epath = NULL; + char *rootpath = NULL; + size_t len; + + rootpath = conf_get_lcrd_rootdir(); + if (rootpath == NULL) { + ERROR("Get rootpath failed"); + return epath; + } + if (strlen(rootpath) > (SIZE_MAX - strlen(ENGINE_ROOTPATH_NAME)) - 2) { + ERROR("Root path is too long"); + goto free_out; + } + len = strlen(rootpath) + 1 + strlen(ENGINE_ROOTPATH_NAME) + 1; + if (len > PATH_MAX) { + ERROR("The size of path exceeds the limit"); + goto free_out; + } + epath = util_common_calloc_s(len); + if (epath == NULL) { + ERROR("Out of memory"); + goto free_out; + } + + if (sprintf_s(epath, len, "%s/%s", rootpath, ENGINE_ROOTPATH_NAME) < 0) { + ERROR("Sprintf engine path failed"); + free(epath); + epath = NULL; + } + +free_out: + free(rootpath); + return epath; +} + +/* conf get graph rootpath */ +char *conf_get_graph_rootpath() +{ + char *epath = NULL; + char *rootpath = NULL; + size_t len; + + rootpath = conf_get_lcrd_rootdir(); + if (rootpath == NULL) { + ERROR("Get rootpath failed"); + return epath; + } + if (strlen(rootpath) > (SIZE_MAX - strlen(GRAPH_ROOTPATH_NAME)) - 2) { + ERROR("Root path is too long"); + goto free_out; + } + len = strlen(rootpath) + 1 + strlen(GRAPH_ROOTPATH_NAME) + 1; + if (len > PATH_MAX) { + ERROR("The size of path exceeds the limit"); + goto free_out; + } + epath = util_common_calloc_s(len); + if (epath == NULL) { + ERROR("Out of memory"); + goto free_out; + } + + if (sprintf_s(epath, len, "%s/%s", rootpath, GRAPH_ROOTPATH_NAME) < 0) { + ERROR("Sprintf graph path failed"); + free(epath); + epath = NULL; + } + +free_out: + free(rootpath); + return epath; +} + +/* conf get graph checked flag file path */ +char *conf_get_graph_check_flag_file() +{ + char *epath = NULL; + char *rootpath = NULL; + size_t len; + + rootpath = conf_get_lcrd_rootdir(); + if (rootpath == NULL) { + ERROR("Get rootpath failed"); + return epath; + } + if (strlen(rootpath) > ((SIZE_MAX - strlen(GRAPH_ROOTPATH_NAME)) - strlen(GRAPH_ROOTPATH_CHECKED_FLAG)) - 3) { + ERROR("Root path is too long"); + goto free_out; + } + len = strlen(rootpath) + 1 + strlen(GRAPH_ROOTPATH_NAME) + 1 + strlen(GRAPH_ROOTPATH_CHECKED_FLAG) + 1; + if (len > PATH_MAX) { + ERROR("The size of path exceeds the limit"); + goto free_out; + } + epath = util_common_calloc_s(len); + if (epath == NULL) { + ERROR("Out of memory"); + goto free_out; + } + + if (sprintf_s(epath, len, "%s/%s/%s", rootpath, GRAPH_ROOTPATH_NAME, GRAPH_ROOTPATH_CHECKED_FLAG) < 0) { + ERROR("Sprintf graph checked flag failed"); + free(epath); + epath = NULL; + } + +free_out: + free(rootpath); + return epath; +} + +/* conf get graph run path */ +char *conf_get_graph_run_path() +{ + char *epath = NULL; + char *rootpath = NULL; + size_t len; + + rootpath = conf_get_lcrd_statedir(); + if (rootpath == NULL) { + ERROR("Get rootpath failed"); + return epath; + } + if (strlen(rootpath) > (SIZE_MAX - strlen(GRAPH_ROOTPATH_NAME)) - 2) { + ERROR("Root path is too long"); + goto free_out; + } + len = strlen(rootpath) + 1 + strlen(GRAPH_ROOTPATH_NAME) + 1; + if (len > PATH_MAX) { + ERROR("The size of path exceeds the limit"); + goto free_out; + } + epath = util_common_calloc_s(len); + if (epath == NULL) { + ERROR("Out of memory"); + goto free_out; + } + + if (sprintf_s(epath, len, "%s/%s", rootpath, GRAPH_ROOTPATH_NAME) < 0) { + ERROR("Sprintf graph run path failed"); + free(epath); + epath = NULL; + } + +free_out: + free(rootpath); + return epath; +} + +/* conf get routine rootdir */ +char *conf_get_routine_rootdir(const char *runtime) +{ + char *path = NULL; + struct service_arguments *conf = NULL; + size_t len = 0; + + if (runtime == NULL) { + ERROR("Runtime is NULL"); + return NULL; + } + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->graph == NULL) { + ERROR("Server conf is NULL or rootpath is NULL"); + goto out; + } + + /* path = conf->rootpath + / + engines + / + runtime + /0 */ + if (strlen(conf->json_confs->graph) > (SIZE_MAX - strlen(ENGINE_ROOTPATH_NAME)) - 3) { + ERROR("Graph path is too long"); + goto out; + } + len = strlen(conf->json_confs->graph) + 1 + strlen(ENGINE_ROOTPATH_NAME) + 1 + strlen(runtime) + 1; + if (len > PATH_MAX / sizeof(char)) { + ERROR("The size of path exceeds the limit"); + goto out; + } + path = util_common_calloc_s(sizeof(char) * len); + if (path == NULL) { + ERROR("Out of memory"); + goto out; + } + + if (sprintf_s(path, len, "%s/%s/%s", conf->json_confs->graph, ENGINE_ROOTPATH_NAME, runtime) < 0) { + ERROR("Failed to sprintf path"); + free(path); + path = NULL; + } + +out: + (void)lcrd_server_conf_unlock(); + return path; +} + +/* conf get routine statedir */ +char *conf_get_routine_statedir(const char *runtime) +{ + char *path = NULL; + struct service_arguments *conf = NULL; + size_t len = 0; + + if (runtime == NULL) { + return NULL; + } + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->state == NULL) { + goto out; + } + + /* path = conf->statepath + / + runtime + /0 */ + if (strlen(conf->json_confs->state) > (SIZE_MAX - strlen(runtime)) - 2) { + ERROR("State path is too long"); + goto out; + } + len = strlen(conf->json_confs->state) + 1 + strlen(runtime) + 1; + if (len > PATH_MAX) { + goto out; + } + path = util_common_calloc_s(sizeof(char) * len); + if (path == NULL) { + goto out; + } + + if (sprintf_s(path, len, "%s/%s", conf->json_confs->state, runtime) < 0) { + ERROR("sprintf path failed"); + free(path); + path = NULL; + } + +out: + (void)lcrd_server_conf_unlock(); + return path; +} + +/* conf get lcrd rootdir */ +char *conf_get_lcrd_rootdir() +{ + char *path = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->graph == NULL) { + goto out; + } + + path = util_strdup_s(conf->json_confs->graph); + +out: + (void)lcrd_server_conf_unlock(); + return path; +} + +#ifdef ENABLE_OCI_IMAGE +/* conf get graph driver */ +char *conf_get_lcrd_storage_driver() +{ + char *driver = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->driver->name == NULL) { + goto out; + } + + driver = util_strdup_s(conf->driver->name); + +out: + (void)lcrd_server_conf_unlock(); + return driver; +} + +/* conf get graph driver */ +char *conf_get_lcrd_storage_driver_backing_fs() +{ + char *fs = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->driver->backing_fs == NULL) { + goto out; + } + + fs = util_strdup_s(conf->driver->backing_fs); + +out: + (void)lcrd_server_conf_unlock(); + return fs; +} + +/* conf get graph driver opts */ +char **conf_get_storage_opts() +{ + int nret = 0; + size_t i; + char **opts = NULL; + char *p = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->storage_opts == NULL) { + goto out; + } + + for (i = 0; i < conf->json_confs->storage_opts_len; i++) { + p = conf->json_confs->storage_opts[i]; + if (p == NULL) { + goto out; + } + nret = util_array_append(&opts, p); + if (nret != 0) { + ERROR("Out of memory"); + goto out_free; + } + } + +out_free: + if (nret != 0) { + util_free_array(opts); + opts = NULL; + } +out: + (void)lcrd_server_conf_unlock(); + return opts; +} +#endif + +/* conf get registry */ +char **conf_get_registry_list() +{ + int nret = 0; + size_t i; + char **opts = NULL; + char *p = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->registry_mirrors_len == 0) { + goto out; + } + + for (i = 0; i < conf->json_confs->registry_mirrors_len; i++) { + p = conf->json_confs->registry_mirrors[i]; + if (p == NULL) { + break; + } + nret = util_array_append(&opts, p); + if (nret != 0) { + ERROR("Out of memory"); + util_free_array(opts); + opts = NULL; + goto out; + } + } +out: + (void)lcrd_server_conf_unlock(); + return opts; +} + +/* conf get insecure registry */ +char **conf_get_insecure_registry_list() +{ + int nret = 0; + size_t i; + char **opts = NULL; + char *p = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->insecure_registries_len == 0) { + goto out; + } + + for (i = 0; i < conf->json_confs->insecure_registries_len; i++) { + p = conf->json_confs->insecure_registries[i]; + if (p == NULL) { + break; + } + nret = util_array_append(&opts, p); + if (nret != 0) { + util_free_array(opts); + opts = NULL; + ERROR("Out of memory"); + break; + } + } +out: + (void)lcrd_server_conf_unlock(); + return opts; +} + +/* conf get lcrd statedir */ +char *conf_get_lcrd_statedir() +{ + char *path = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->state == NULL) { + goto out; + } + + path = util_strdup_s(conf->json_confs->state); + +out: + (void)lcrd_server_conf_unlock(); + return path; +} + +/* conf get lcrd mount rootfs */ +char *conf_get_lcrd_mount_rootfs() +{ + char *path = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->rootfsmntdir == NULL) { + goto out; + } + + path = util_strdup_s(conf->json_confs->rootfsmntdir); + +out: + (void)lcrd_server_conf_unlock(); + return path; +} + +/* conf get lcrd umask for containers */ +char *conf_get_lcrd_native_umask() +{ + char *umask = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->native_umask == NULL) { + goto out; + } + + umask = util_strdup_s(conf->json_confs->native_umask); + +out: + (void)lcrd_server_conf_unlock(); + return umask; +} + +/* conf get lcrd cgroup parent for containers */ +char *conf_get_lcrd_cgroup_parent() +{ + char *cgroup_parent = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->cgroup_parent == NULL) { + goto out; + } + + cgroup_parent = util_strdup_s(conf->json_confs->cgroup_parent); + +out: + (void)lcrd_server_conf_unlock(); + return cgroup_parent; +} + +/* conf get lcrd engine */ +char *conf_get_lcrd_engine() +{ + char *engine = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->engine == NULL) { + goto out; + } + + engine = util_strdup_s(conf->json_confs->engine); + +out: + (void)lcrd_server_conf_unlock(); + return engine; +} + +/* conf get lcrd loglevel */ +char *conf_get_lcrd_loglevel() +{ + char *loglevel = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->log_level == NULL) { + goto out; + } + + loglevel = util_strdup_s(conf->json_confs->log_level); + +out: + (void)lcrd_server_conf_unlock(); + return loglevel; +} + +/* get log file helper */ +char *get_log_file_helper(const struct service_arguments *conf, const char *suffix) +{ + char *logfile = NULL; + size_t len = 0; + int nret = 0; + + if (suffix == NULL) { + return NULL; + } + + // log_file path = parent path + "/" + suffix + if (strlen(conf->logpath) > (SIZE_MAX - strlen(suffix)) - 2) { + ERROR("Log path is too long"); + return NULL; + } + len = strlen(conf->logpath) + 1 + strlen(suffix) + 1; + if (len > PATH_MAX) { + ERROR("The size of path exceeds the limit"); + return NULL; + } + logfile = util_common_calloc_s(len * sizeof(char)); + if (logfile == NULL) { + ERROR("Out of memory"); + goto out; + } + + nret = sprintf_s(logfile, len, "%s/%s", conf->logpath, suffix); + if (nret < 0) { + free(logfile); + logfile = NULL; + ERROR("Failed to sprintf log path"); + } + +out: + return logfile; +} + +/* conf get lcrd log gather fifo path */ +char *conf_get_lcrd_log_gather_fifo_path() +{ + char *logfile = NULL; + char *statedir = NULL; + size_t len = 0; + int nret; + + statedir = conf_get_lcrd_statedir(); + if (statedir == NULL) { + ERROR("Get lcrd statedir failed"); + goto err_out; + } + if (strlen(statedir) > (SIZE_MAX - strlen("/lcrd_log_gather_fifo")) - 1) { + ERROR("State path is too long"); + goto err_out; + } + len = strlen(statedir) + strlen("/lcrd_log_gather_fifo") + 1; + if (len > PATH_MAX) { + ERROR("Too long path: %s", statedir); + goto err_out; + } + logfile = util_common_calloc_s(len); + if (logfile == NULL) { + ERROR("Out of memory"); + goto err_out; + } + nret = sprintf_s(logfile, len, "%s%s", statedir, "/lcrd_log_gather_fifo"); + if (nret < 0) { + ERROR("Sprintf log file failed"); + goto err_out; + } + goto out; + +err_out: + free(logfile); + logfile = NULL; +out: + free(statedir); + return logfile; +} + +/* conf get lcrd log file */ +char *conf_get_lcrd_log_file() +{ + char *logfile = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->logpath == NULL) { + goto out; + } + + logfile = get_log_file_helper(conf, "lcrd.log"); + +out: + (void)lcrd_server_conf_unlock(); + return logfile; +} + +/* conf get engine log file */ +char *conf_get_engine_log_file() +{ + char *logfile = NULL; + char *full_path = NULL; + char *prefix = "fifo:"; + size_t len = 0; + + logfile = conf_get_lcrd_log_gather_fifo_path(); + if (logfile == NULL) { + ERROR("conf_get_lcrd_log_gather_fifo_path failed"); + goto out; + } + len = strlen(prefix) + strlen(logfile) + 1; + if (len > PATH_MAX) { + ERROR("The size of path exceeds the limit"); + goto out; + } + full_path = util_common_calloc_s(len * sizeof(char)); + if (full_path == NULL) { + FATAL("Out of Memory"); + goto out; + } + if (sprintf_s(full_path, len, "%s%s", prefix, logfile) < 0) { + ERROR("Failed to sprintf engine log path"); + free(full_path); + full_path = NULL; + goto out; + } + +out: + free(logfile); + return full_path; +} + +int conf_get_daemon_log_config(char **loglevel, char **logdriver, char **engine_log_path) +{ + *loglevel = conf_get_lcrd_loglevel(); + if (*loglevel == NULL) { + ERROR("DoStart: Failed to get log level"); + return -1; + } + *logdriver = conf_get_lcrd_logdriver(); + if (*logdriver == NULL) { + ERROR("DoStart: Failed to get log driver"); + return -1; + } + *engine_log_path = conf_get_engine_log_file(); + if (strcmp(*logdriver, "file") == 0 && *engine_log_path == NULL) { + ERROR("DoStart: Log driver is file, but engine log path is NULL"); + return -1; + } + return 0; +} + +/* conf get lcrd logdriver */ +char *conf_get_lcrd_logdriver() +{ + char *logdriver = NULL; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->log_driver == NULL) { + goto out; + } + + logdriver = util_strdup_s(conf->json_confs->log_driver); + +out: + (void)lcrd_server_conf_unlock(); + return logdriver; +} + +/* conf get image layer check flag */ +bool conf_get_image_layer_check_flag() +{ + bool check_flag = false; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return false; + } + + conf = conf_get_server_conf(); + if (conf == NULL) { + goto out; + } + + check_flag = conf->json_confs->image_layer_check; + +out: + (void)lcrd_server_conf_unlock(); + return check_flag; +} + +/* conf get flag of use decrypted key to pull image */ +bool conf_get_use_decrypted_key_flag() +{ + bool check_flag = true; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return false; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs->use_decrypted_key == NULL) { + goto out; + } + + check_flag = *(conf->json_confs->use_decrypted_key); + +out: + (void)lcrd_server_conf_unlock(); + return check_flag; +} + +bool conf_get_skip_insecure_verify_flag() +{ + bool check_flag = false; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return false; + } + + conf = conf_get_server_conf(); + if (conf == NULL) { + goto out; + } + + check_flag = conf->json_confs->insecure_skip_verify_enforce; + +out: + (void)lcrd_server_conf_unlock(); + return check_flag; +} + +#define OCI_STR_ARRAY_DUP(src, dest, srclen, destlen, ret) \ + do { \ + if ((src) != NULL) { \ + (dest) = str_array_dup((const char **)(src), (srclen)); \ + if ((dest) == NULL) { \ + (ret) = -1; \ + goto out; \ + } \ + (destlen) = (srclen); \ + } \ + } while (0) + +#define HOOKS_ELEM_DUP_DEF(item) \ + defs_hook *hooks_##item##_elem_dup(const defs_hook *src) \ + { \ + int ret = 0; \ + defs_hook *dest = NULL; \ + if (src == NULL) \ + return NULL; \ + dest = util_common_calloc_s(sizeof(defs_hook)); \ + if (dest == NULL) \ + return NULL; \ + dest->path = util_strdup_s(src->path); \ + OCI_STR_ARRAY_DUP(src->args, dest->args, src->args_len, dest->args_len, ret); \ + OCI_STR_ARRAY_DUP(src->env, dest->env, src->env_len, dest->env_len, ret); \ + dest->timeout = src->timeout; \ + out: \ + if (ret != 0 && dest != NULL) { \ + free_defs_hook(dest); \ + dest = NULL; \ + } \ + return dest; \ + } + +/* HOOKS ELEM DUP DEF */ +HOOKS_ELEM_DUP_DEF(prestart) +/* HOOKS ELEM DUP DEF */ +HOOKS_ELEM_DUP_DEF(poststart) +/* HOOKS ELEM DUP DEF */ +HOOKS_ELEM_DUP_DEF(poststop) + +#define HOOKS_ITEM_DUP_DEF(item) \ + int hooks_##item##_dup(oci_runtime_spec_hooks *dest, const oci_runtime_spec_hooks *src) \ + { \ + int i = 0; \ + if (src->item##_len > SIZE_MAX / sizeof(defs_hook *) - 1) { \ + return -1; \ + } \ + dest->item = util_common_calloc_s(sizeof(defs_hook *) * (src->item##_len + 1)); \ + if (dest->item == NULL) \ + return -1; \ + dest->item##_len = src->item##_len; \ + for (; (size_t)i < src->item##_len; ++i) { \ + dest->item[i] = hooks_##item##_elem_dup(src->item[i]); \ + if (dest->item[i] == NULL) \ + return -1; \ + } \ + return 0; \ + } + +/* HOOKS ITEM DUP DEF */ +HOOKS_ITEM_DUP_DEF(prestart) +/* HOOKS ITEM DUP DEF */ +HOOKS_ITEM_DUP_DEF(poststart) +/* HOOKS ITEM DUP DEF */ +HOOKS_ITEM_DUP_DEF(poststop) + +/* hooks_dup */ +oci_runtime_spec_hooks *hooks_dup(const oci_runtime_spec_hooks *src) +{ + int ret = 0; + oci_runtime_spec_hooks *dest = NULL; + + if (src == NULL) { + return NULL; + } + dest = util_common_calloc_s(sizeof(oci_runtime_spec_hooks)); + if (dest == NULL) { + return NULL; + } + + ret = hooks_prestart_dup(dest, src); + if (ret != 0) { + goto out; + } + + ret = hooks_poststart_dup(dest, src); + if (ret != 0) { + goto out; + } + + ret = hooks_poststop_dup(dest, src); + +out: + if (ret != 0) { + free_oci_runtime_spec_hooks(dest); + dest = NULL; + } + return dest; +} + +/* conf get lcrd hooks */ +int conf_get_lcrd_hooks(oci_runtime_spec_hooks **phooks) +{ + int ret = 0; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return -1; + } + + conf = conf_get_server_conf(); + if (conf != NULL && conf->hooks != NULL) { + *phooks = hooks_dup(conf->hooks); + if ((*phooks) == NULL) { + ret = -1; + goto out; + } + } else { + *phooks = NULL; + } +out: + (void)lcrd_server_conf_unlock(); + return ret; +} + +/* conf get lcrd default ulimit */ +int conf_get_lcrd_default_ulimit(host_config_ulimits_element ***ulimit) +{ + int ret = 0; + size_t i, ulimit_len; + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return -1; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->default_ulimit_len == 0) { + *ulimit = NULL; + goto out; + } + + for (i = 0; i < conf->default_ulimit_len; i++) { + ulimit_len = ulimit_array_len(*ulimit); + if (ulimit_array_append(ulimit, conf->default_ulimit[i], ulimit_len) != 0) { + ERROR("ulimit append failed"); + ret = -1; + goto out; + } + } +out: + (void)lcrd_server_conf_unlock(); + return ret; +} + +/* conf get start timeout */ +unsigned int conf_get_start_timeout() +{ + struct service_arguments *conf = NULL; + unsigned int ret = 0; + if (lcrd_server_conf_rdlock() != 0) { + return 0; + } + + conf = conf_get_server_conf(); + if (conf == NULL) { + goto out; + } + + ret = conf->start_timeout; + +out: + (void)lcrd_server_conf_unlock(); + return ret; +} + +/* conf get image opt timeout */ +unsigned int conf_get_im_opt_timeout() +{ + struct service_arguments *conf = NULL; + unsigned int ret = 0; + if (lcrd_server_conf_rdlock() != 0) { + return 0; + } + + conf = conf_get_server_conf(); + if (conf == NULL) { + goto out; + } + + ret = conf->im_opt_timeout; + +out: + (void)lcrd_server_conf_unlock(); + return ret; +} + +char *conf_get_enable_plugins() +{ + struct service_arguments *conf = NULL; + char *plugins = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + ERROR("BUG conf_rdlock failed"); + return NULL; + } + + conf = conf_get_server_conf(); + if (conf == NULL || conf->json_confs == NULL || conf->json_confs->enable_plugins == NULL) { + goto out; + } + + plugins = util_strdup_s(conf->json_confs->enable_plugins); + +out: + (void)lcrd_server_conf_unlock(); + return plugins; +} + +/* save args to conf */ +int save_args_to_conf(struct service_arguments *args) +{ + int ret = 0; + + ret = pthread_rwlock_init(&g_lcrd_conf.lcrd_conf_rwlock, NULL); + if (ret != 0) { + ERROR("Failed to init lcrd conf rwlock"); + ret = -1; + goto out; + } + + if (pthread_rwlock_wrlock(&g_lcrd_conf.lcrd_conf_rwlock) != 0) { + ERROR("Failed to acquire lcrd conf write lock"); + ret = -1; + goto out; + } + + if (g_lcrd_conf.server_conf != NULL) { + service_arguments_free(g_lcrd_conf.server_conf); + free(g_lcrd_conf.server_conf); + } + g_lcrd_conf.server_conf = args; + + if (pthread_rwlock_unlock(&g_lcrd_conf.lcrd_conf_rwlock) != 0) { + ERROR("Failed to release lcrd conf write lock"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* set path group */ +static int set_path_group(const char *rpath, const char *group) +{ + struct group *grp = NULL; + gid_t gid; + + grp = getgrnam(group); + + if (grp != NULL) { + gid = grp->gr_gid; + DEBUG("Group %s found, gid: %d", group, gid); + if (chown(rpath, -1, gid) != 0) { + DEBUG("Failed to chown %s to gid: %d", rpath, gid); + return -1; + } + } else { + if (strcmp(group, "docker") == 0 || strcmp(group, "lcrd") == 0) { + DEBUG("Warning: could not change group %s to %s", rpath, group); + } else { + lcrd_set_error_message("Group %s not found", group); + return -1; + } + } + + return 0; +} + +/* set socket group */ +int set_unix_socket_group(const char *socket, const char *group) +{ + const char *path = NULL; + char rpath[PATH_MAX + 1] = { 0x00 }; + int ret = 0; + int nret = 0; + + if (socket == NULL || group == NULL) { + return -1; + } + + path = socket + strlen(UNIX_SOCKET_PREFIX); + + if (strlen(path) > PATH_MAX || realpath(path, rpath) == NULL) { + ERROR("ensure socket path %s failed", path); + ret = -1; + goto out; + } + nret = set_path_group(rpath, group); + if (nret < 0) { + ERROR("set group of the path: %s failed", rpath); + ret = -1; + goto out; + } + + if (chmod(rpath, SOCKET_GROUP_DIRECTORY_MODE) != 0) { + DEBUG("Failed to chmod for socket: %s", rpath); + ret = -1; + goto out; + } + +out: + if (ret == 0) { + DEBUG("Listener created for HTTP on unix (%s)", rpath); + } + + return ret; +} + +/* maybe create cpu realtime file */ +static int maybe_create_cpu_realtime_file(bool present, int64_t value, const char *file, const char *path) +{ + int ret; + int fd = 0; + ssize_t nwrite; + char fpath[PATH_MAX] = { 0 }; + char buf[LCRD_NUMSTRLEN64] = { 0 }; + + if (!present || value == 0) { + return 0; + } + + ret = util_mkdir_p(path, CONFIG_DIRECTORY_MODE); + if (ret != 0) { + ERROR("Failed to mkdir: %s", path); + return -1; + } + + if (sprintf_s(fpath, sizeof(fpath), "%s/%s", path, file) < 0) { + ERROR("Failed to print string"); + return -1; + } + if (sprintf_s(buf, sizeof(buf), "%lld", (long long int)value) < 0) { + ERROR("Failed to print string"); + return -1; + } + + fd = util_open(fpath, O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0700); + if (fd < 0) { + ERROR("Failed to open file: %s: %s", fpath, strerror(errno)); + lcrd_set_error_message("Failed to open file: %s: %s", fpath, strerror(errno)); + return -1; + } + nwrite = util_write_nointr(fd, buf, strlen(buf)); + if (nwrite < 0) { + ERROR("Failed to write %s to %s: %s", buf, fpath, strerror(errno)); + lcrd_set_error_message("Failed to write '%s' to '%s': %s", buf, fpath, strerror(errno)); + close(fd); + return -1; + } + close(fd); + + return 0; +} + +static int get_cgroup_cpu_rt(int64_t *cpu_rt_period, int64_t *cpu_rt_runtime) +{ + struct service_arguments *conf = NULL; + + if (lcrd_server_conf_rdlock() != 0) { + return -1; + } + + conf = conf_get_server_conf(); + if (conf == NULL) { + (void)lcrd_server_conf_unlock(); + return -1; + } + + *cpu_rt_period = conf->json_confs->cpu_rt_period; + *cpu_rt_runtime = conf->json_confs->cpu_rt_runtime; + + if (lcrd_server_conf_unlock() != 0) { + return -1; + } + + return 0; +} + +static int recursively_create_cgroup(const char *path, int recursive_depth, int64_t cpu_rt_period, + int64_t cpu_rt_runtime) +{ + int ret = 0; + sysinfo_t *sysinfo = NULL; + char *dup = NULL; + char *dirpath = NULL; + char *mnt = NULL; + char *root = NULL; + char fpath[PATH_MAX] = { 0 }; + + dup = util_strdup_s(path); + dirpath = dirname(dup); + ret = init_cgroups_path(dirpath, (recursive_depth + 1)); + free(dup); + if (ret != 0) { + return ret; + } + + ret = find_cgroup_mountpoint_and_root("cpu", &mnt, &root); + if (ret != 0 || mnt == NULL || root == NULL) { + ERROR("Can not find cgroup mnt and root path for subsystem 'cpu'"); + lcrd_set_error_message("Can not find cgroup mnt and root path for subsystem 'cpu'"); + ret = -1; + goto out; + } + + // When iSulad is run inside iSulad/docker, the root is based of the host cgroup. + // Replace root to "/" + if (strncmp(root, "/lxc/", strlen("/lxc/")) != 0) { + root[1] = '\0'; + } + + if (sprintf_s(fpath, sizeof(fpath), "%s/%s/%s", mnt, root, path) < 0) { + ERROR("Failed to print string"); + ret = -1; + goto out; + } + + sysinfo = get_sys_info(true); + if (sysinfo == NULL) { + ERROR("Can not get system info"); + ret = -1; + goto out; + } + + ret = maybe_create_cpu_realtime_file(sysinfo->cgcpuinfo.cpu_rt_period, cpu_rt_period, "cpu.rt_period_us", fpath); + if (ret != 0) { + goto out; + } + ret = maybe_create_cpu_realtime_file(sysinfo->cgcpuinfo.cpu_rt_runtime, cpu_rt_runtime, "cpu.rt_runtime_us", fpath); + if (ret != 0) { + goto out; + } +out: + free(mnt); + free(root); + free_sysinfo(sysinfo); + return ret; +} + +/* init cgroups path */ +int init_cgroups_path(const char *path, int recursive_depth) +{ + int64_t cpu_rt_period = 0; + int64_t cpu_rt_runtime = 0; + + if ((recursive_depth + 1) > MAX_PATH_DEPTH) { + ERROR("Reach the max cgroup depth:%s", path); + return -1; + } + + if (path == NULL || strcmp(path, "/") == 0 || strcmp(path, ".") == 0) { + return 0; + } + + if (get_cgroup_cpu_rt(&cpu_rt_period, &cpu_rt_runtime)) { + return -1; + } + + if (cpu_rt_period == 0 && cpu_rt_runtime == 0) { + return 0; + } + + // Recursively create cgroup to ensure that the system and all parent cgroups have values set + // for the period and runtime as this limits what the children can be set to. + if (recursively_create_cgroup(path, recursive_depth, cpu_rt_period, cpu_rt_runtime)) { + return -1; + } + + return 0; +} + +#define OVERRIDE_STRING_VALUE(dst, src) \ + do { \ + if ((src) != NULL && strlen((src)) != 0) { \ + free((dst)); \ + (dst) = (src); \ + (src) = NULL; \ + } \ + } while (0) + +static int string_array_append(char **suffix, size_t suffix_len, size_t *curr_len, char ***result) +{ + if (suffix_len > 0) { + size_t new_len = *curr_len + suffix_len; + size_t work_len = *curr_len; + size_t i, j; + + if (util_grow_array(result, &work_len, new_len, INCREMENT_INTREVAL) != 0) { + return -1; + } + for (i = *curr_len, j = 0; i < new_len; i++) { + (*result)[i] = suffix[j]; + suffix[j++] = NULL; + } + *curr_len = new_len; + } + + return 0; +} + +int parse_log_opts(struct service_arguments *args, const char *key, const char *value) +{ + int ret = -1; + + if (key == NULL || value == NULL) { + return 0; + } + // support new driver options, add here + if (strcmp(key, "log-path") == 0) { + free(args->logpath); + args->logpath = util_strdup_s(value); + ret = 0; + } else if (strcmp(key, "log-file-mode") == 0) { + unsigned int file_mode = 0; + if (util_safe_uint(value, &file_mode) == 0) { + args->log_file_mode = file_mode; + ret = 0; + } + } else if (strcmp(key, "max-file") == 0) { + int tmaxfile = 0; + if (util_safe_int(value, &tmaxfile) == 0 && tmaxfile > 0) { + args->max_file = tmaxfile; + ret = 0; + } + } else if (strcmp(key, "max-size") == 0) { + int64_t tmaxsize = 0; + if (util_parse_byte_size_string(value, &tmaxsize) == 0 && tmaxsize > 0) { + args->max_size = tmaxsize; + ret = 0; + } + } else { + ERROR("Invalid config: %s = %s", key, value); + } + + return ret; +} + +static inline void override_string_value(char **dst, char **src) +{ + if (*src == NULL || (*src)[0] == '\0') { + return; + } + free(*dst); + *dst = *src; + *src = NULL; +} + +static inline void override_bool_pointer_value(bool **dst, bool **src) +{ + if (*src == NULL) { + return; + } + free(*dst); + *dst = *src; + *src = NULL; +} + +static int merge_hosts_conf_into_global(struct service_arguments *args, + const isulad_daemon_configs *tmp_json_confs) +{ + size_t i, j; + + for (i = 0; i < tmp_json_confs->hosts_len; i++) { + for (j = 0; j < args->json_confs->hosts_len; j++) { + if (strcmp(args->json_confs->hosts[j], tmp_json_confs->hosts[i]) == 0) { + break; + } + } + if (j != args->json_confs->hosts_len) { + continue; + } + + if (util_array_append(&(args->json_confs->hosts), tmp_json_confs->hosts[i]) != 0) { + ERROR("merge hosts config failed"); + return -1; + } + args->json_confs->hosts_len++; + if (args->json_confs->hosts_len > MAX_HOSTS) { + lcrd_set_error_message("Too many hosts, the max number is %d", MAX_HOSTS); + return -1; + } + } + + return 0; +} + +static int merge_logs_conf_into_global(struct service_arguments *args, isulad_daemon_configs *tmp_json_confs) +{ + size_t i; + + override_string_value(&args->json_confs->log_level, &tmp_json_confs->log_level); + override_string_value(&args->json_confs->log_driver, &tmp_json_confs->log_driver); + + for (i = 0; tmp_json_confs->log_opts != NULL && i < tmp_json_confs->log_opts->len; i++) { + if (parse_log_opts(args, tmp_json_confs->log_opts->keys[i], tmp_json_confs->log_opts->values[i]) != 0) { + COMMAND_ERROR("Failed to parse log options %s:%s", tmp_json_confs->log_opts->keys[i], + tmp_json_confs->log_opts->values[i]); + return -1; + } + if (append_json_map_string_string(args->json_confs->log_opts, tmp_json_confs->log_opts->keys[i], + tmp_json_confs->log_opts->values[i]) != 0) { + ERROR("Out of memory"); + return -1; + } + } + + return 0; +} + +static int merge_authorization_conf_into_global(struct service_arguments *args, isulad_daemon_configs *tmp_json_confs) +{ + args->json_confs->tls = tmp_json_confs->tls; + args->json_confs->tls_verify = tmp_json_confs->tls_verify; + if (tmp_json_confs->tls_config != NULL) { + override_string_value(&args->json_confs->tls_config->ca_file, &tmp_json_confs->tls_config->ca_file); + override_string_value(&args->json_confs->tls_config->cert_file, &tmp_json_confs->tls_config->cert_file); + override_string_value(&args->json_confs->tls_config->key_file, &tmp_json_confs->tls_config->key_file); + } + if (tmp_json_confs->authorization_plugin != NULL) { + override_string_value(&args->json_confs->authorization_plugin, &tmp_json_confs->authorization_plugin); + } + + return 0; +} + +static int merge_storage_conf_into_global(struct service_arguments *args, isulad_daemon_configs *tmp_json_confs) +{ + override_string_value(&args->json_confs->storage_driver, &tmp_json_confs->storage_driver); + + if (string_array_append(tmp_json_confs->storage_opts, tmp_json_confs->storage_opts_len, + &(args->json_confs->storage_opts_len), &(args->json_confs->storage_opts)) != 0) { + ERROR("merge graph config failed"); + return -1; + } + + return 0; +} + +static int merge_registry_conf_into_global(struct service_arguments *args, isulad_daemon_configs *tmp_json_confs) +{ + if (string_array_append(tmp_json_confs->registry_mirrors, tmp_json_confs->registry_mirrors_len, + &(args->json_confs->registry_mirrors_len), &(args->json_confs->registry_mirrors)) != 0) { + ERROR("merge registry mirrors config failed"); + return -1; + } + + if (string_array_append(tmp_json_confs->insecure_registries, tmp_json_confs->insecure_registries_len, + &(args->json_confs->insecure_registries_len), + &(args->json_confs->insecure_registries)) != 0) { + ERROR("merge insecure registries config failed"); + return -1; + } + + return 0; +} + +static int merge_default_ulimits_conf_into_global(struct service_arguments *args, + isulad_daemon_configs *tmp_json_confs) +{ + if (tmp_json_confs == NULL) { + return -1; + } + + if (tmp_json_confs->default_ulimits == NULL) { + return 0; + } + + args->json_confs->default_ulimits = tmp_json_confs->default_ulimits; + tmp_json_confs->default_ulimits = NULL; + return 0; +} + +int merge_json_confs_into_global(struct service_arguments *args) +{ + isulad_daemon_configs *tmp_json_confs; + parser_error err = NULL; + int ret = 0; + + tmp_json_confs = isulad_daemon_configs_parse_file(ISULAD_DAEMON_JSON_CONF_FILE, NULL, &err); + if (tmp_json_confs == NULL) { + COMMAND_ERROR("Load isulad json config failed: %s", err != NULL ? err : ""); + ret = -1; + goto out; + } + // Daemon socket option + if (merge_hosts_conf_into_global(args, tmp_json_confs)) { + ret = -1; + goto out; + } + + override_string_value(&args->json_confs->group, &tmp_json_confs->group); + override_string_value(&args->json_confs->graph, &tmp_json_confs->graph); + override_string_value(&args->json_confs->state, &tmp_json_confs->state); + + if (merge_logs_conf_into_global(args, tmp_json_confs)) { + ret = -1; + goto out; + } + + override_string_value(&args->json_confs->pidfile, &tmp_json_confs->pidfile); + // iSulad runtime execution options + override_string_value(&args->json_confs->engine, &tmp_json_confs->engine); + override_string_value(&args->json_confs->hook_spec, &tmp_json_confs->hook_spec); + override_string_value(&args->json_confs->enable_plugins, &tmp_json_confs->enable_plugins); + override_string_value(&args->json_confs->native_umask, &tmp_json_confs->native_umask); + override_string_value(&args->json_confs->cgroup_parent, &tmp_json_confs->cgroup_parent); + override_string_value(&args->json_confs->rootfsmntdir, &tmp_json_confs->rootfsmntdir); + override_string_value(&args->json_confs->start_timeout, &tmp_json_confs->start_timeout); + override_string_value(&args->json_confs->im_opt_timeout, &tmp_json_confs->im_opt_timeout); + override_string_value(&args->json_confs->pod_sandbox_image, &tmp_json_confs->pod_sandbox_image); + override_string_value(&args->json_confs->network_plugin, &tmp_json_confs->network_plugin); + override_string_value(&args->json_confs->cni_bin_dir, &tmp_json_confs->cni_bin_dir); + override_string_value(&args->json_confs->cni_conf_dir, &tmp_json_confs->cni_conf_dir); + + // Daemon storage-driver + if (merge_storage_conf_into_global(args, tmp_json_confs)) { + ret = -1; + goto out; + } + + if (merge_registry_conf_into_global(args, tmp_json_confs)) { + ret = -1; + goto out; + } + + if (tmp_json_confs->cpu_rt_period > 0) { + args->json_confs->cpu_rt_period = tmp_json_confs->cpu_rt_period; + } + + if (tmp_json_confs->cpu_rt_runtime > 0) { + args->json_confs->cpu_rt_runtime = tmp_json_confs->cpu_rt_runtime; + } + + if (tmp_json_confs->image_service) { + args->json_confs->image_service = tmp_json_confs->image_service; + } + if (tmp_json_confs->image_layer_check) { + args->json_confs->image_layer_check = tmp_json_confs->image_layer_check; + } + + override_bool_pointer_value(&args->json_confs->use_decrypted_key, &tmp_json_confs->use_decrypted_key); + + if (tmp_json_confs->insecure_skip_verify_enforce) { + args->json_confs->insecure_skip_verify_enforce = tmp_json_confs->insecure_skip_verify_enforce; + } + + if (merge_authorization_conf_into_global(args, tmp_json_confs)) { + ret = -1; + goto out; + } + + if (merge_default_ulimits_conf_into_global(args, tmp_json_confs)) { + ret = -1; + goto out; + } + +out: + free(err); + free_isulad_daemon_configs(tmp_json_confs); + return ret; +} diff --git a/src/config/lcrd_config.h b/src/config/lcrd_config.h new file mode 100644 index 0000000..a00710e --- /dev/null +++ b/src/config/lcrd_config.h @@ -0,0 +1,104 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container configure definition + ******************************************************************************/ +#ifndef __LCRD_CONF_H +#define __LCRD_CONF_H + +#include +#include "lcrd/arguments.h" +#include "oci_runtime_spec.h" +#include "isulad_daemon_configs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct lcrd_conf { + pthread_rwlock_t lcrd_conf_rwlock; + struct service_arguments *server_conf; +}; + +char *conf_get_lcrd_pidfile(); +char *conf_get_engine_rootpath(); +char *conf_get_routine_rootdir(const char *runtime); +char *conf_get_routine_statedir(const char *runtime); +char *conf_get_lcrd_rootdir(); +char *conf_get_lcrd_statedir(); +char *conf_get_lcrd_mount_rootfs(); +char *conf_get_lcrd_engine(); +char *conf_get_lcrd_loglevel(); +char *conf_get_lcrd_logdriver(); +int conf_get_daemon_log_config(char **loglevel, char **logdriver, char **engine_log_path); +char *conf_get_lcrd_log_gather_fifo_path(); + +char *conf_get_lcrd_log_file(); +char *conf_get_engine_log_file(); +char *conf_get_enable_plugins(); + +int save_args_to_conf(struct service_arguments *args); + +int set_unix_socket_group(const char *socket, const char *group); + +int lcrd_server_conf_wrlock(); + +int lcrd_server_conf_rdlock(); + +int lcrd_server_conf_unlock(); + +struct service_arguments *conf_get_server_conf(); + +int get_system_cpu_usage(uint64_t *val); + +int conf_get_lcrd_hooks(oci_runtime_spec_hooks **phooks); + +int conf_get_lcrd_default_ulimit(host_config_ulimits_element ***ulimit); + +unsigned int conf_get_start_timeout(); + +int init_cgroups_path(const char *path, int recursive_depth); + +char *conf_get_graph_rootpath(); + +char *conf_get_graph_run_path(); + +char *conf_get_lcrd_storage_driver(); + +char *conf_get_lcrd_storage_driver_backing_fs(); + +char **conf_get_storage_opts(); + +char **conf_get_insecure_registry_list(); + +char **conf_get_registry_list(); +char *conf_get_lcrd_native_umask(); + +char *conf_get_lcrd_cgroup_parent(); + +unsigned int conf_get_im_opt_timeout(); + +char *conf_get_graph_check_flag_file(); + +bool conf_get_image_layer_check_flag(); + +int merge_json_confs_into_global(struct service_arguments *args); + +bool conf_get_use_decrypted_key_flag(); +bool conf_get_skip_insecure_verify_flag(); +int parse_log_opts(struct service_arguments *args, const char *key, const char *value); + +#ifdef __cplusplus +} +#endif + +#endif /* __LCRD_CONF_H */ diff --git a/src/connect/CMakeLists.txt b/src/connect/CMakeLists.txt new file mode 100644 index 0000000..5e4d5a2 --- /dev/null +++ b/src/connect/CMakeLists.txt @@ -0,0 +1,8 @@ +# get current directory sources files +add_subdirectory(client) +set(CONNECTOR ${CONNECT_CLIENT_SRCS} PARENT_SCOPE) +set(CONNECTOR_INCS ${CONNECT_CLIENT_INCS} PARENT_SCOPE) + +add_subdirectory(service) +set(CONNECT_SOCKET ${CONNECT_SERVICE_SRCS} PARENT_SCOPE) +set(CONNECT_SOCKET_INCS ${CONNECT_SERVICE_INCS} PARENT_SCOPE) diff --git a/src/connect/client/CMakeLists.txt b/src/connect/client/CMakeLists.txt new file mode 100644 index 0000000..96ec225 --- /dev/null +++ b/src/connect/client/CMakeLists.txt @@ -0,0 +1,18 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} client_srcs) + +set(incs ${CMAKE_CURRENT_SOURCE_DIR}) + +if (GRPC_CONNECTOR) + add_subdirectory(grpc) + list(APPEND client_srcs ${CLIENT_GRPC_SRCS}) + list(APPEND incs ${CMAKE_CURRENT_SOURCE_DIR}/grpc) +else() + add_subdirectory(rest) + list(APPEND client_srcs ${CLIENT_REST_SRCS}) + list(APPEND incs ${CMAKE_CURRENT_SOURCE_DIR}/rest) +endif() + + +set(CONNECT_CLIENT_SRCS ${client_srcs} PARENT_SCOPE) +set(CONNECT_CLIENT_INCS ${incs} PARENT_SCOPE) diff --git a/src/connect/client/connect.h b/src/connect/client/connect.h new file mode 100644 index 0000000..38e8681 --- /dev/null +++ b/src/connect/client/connect.h @@ -0,0 +1,35 @@ +/****************************************************************************** +* Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. +* Author: liuhao +* Create: 2019-07-12 +* Description: provide lcrc connect command definition +*******************************************************************************/ +#ifndef __ISULAD_CLIENT_CONNECT_H +#define __ISULAD_CLIENT_CONNECT_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *socket; + // gRPC tls config + bool tls; + bool tls_verify; + char *ca_file; + char *cert_file; + char *key_file; +} client_connect_config_t; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/connect/client/grpc/CMakeLists.txt b/src/connect/client/grpc/CMakeLists.txt new file mode 100644 index 0000000..4fadef1 --- /dev/null +++ b/src/connect/client/grpc/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_client_grpc_srcs) + +set(CLIENT_GRPC_SRCS + ${local_client_grpc_srcs} + PARENT_SCOPE + ) diff --git a/src/connect/client/grpc/client_base.h b/src/connect/client/grpc/client_base.h new file mode 100644 index 0000000..b92a072 --- /dev/null +++ b/src/connect/client/grpc/client_base.h @@ -0,0 +1,207 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container client base functions + ******************************************************************************/ +#ifndef __CLIENT_BASH_H +#define __CLIENT_BASH_H +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "log.h" +#include "connect.h" +#include "utils.h" +#include "certificate.h" + +using grpc::Channel; +using grpc::ClientContext; +using grpc::ClientReader; +using grpc::ClientReaderWriter; +using grpc::ClientWriter; +using grpc::Status; + +namespace ClientBaseConstants { +const size_t COMMON_NAME_LEN { 50 }; +const std::string TLS_OFF { "0" }; +const std::string TLS_ON { "1" }; +} // namespace ClientBaseConstants + +template +class ClientBase { +public: + explicit ClientBase(void *args) + { + client_connect_config_t *arguments = reinterpret_cast(args); + + std::string socket_address = arguments->socket; + const std::string tcp_prefix = "tcp://"; + if (socket_address.compare(0, tcp_prefix.length(), tcp_prefix) == 0) { + socket_address.erase(0, tcp_prefix.length()); + } + + if (arguments->tls) { + m_tlsMode = ClientBaseConstants::TLS_ON; + m_certFile = arguments->cert_file != nullptr ? + std::string(arguments->cert_file, std::string(arguments->cert_file).length()) : ""; + std::string pem_root_certs = ReadTextFile(arguments->ca_file); + std::string pem_private_key = ReadTextFile(arguments->key_file); + std::string pem_cert_chain = ReadTextFile(arguments->cert_file); + // Client modes + // mode1/tls: Authenticate server based on public/default CA pool(not support) + // mode2/tlsverify, tlscacert: Authenticate server based on given CA + // mode3/tls, tlscert, tlskey: Authenticate with client certificate, + // do not authenticate server based on given CA + // mode4/tlsverify, tlscacert, tlscert, tlskey: Authenticate with client certificate + // and authenticate server based on given CA + grpc::SslCredentialsOptions ssl_opts = { + arguments->tls_verify ? pem_root_certs : "", pem_private_key, pem_cert_chain + }; + // Create a default SSL ChannelCredentials object. + auto channel_creds = grpc::SslCredentials(ssl_opts); + // Create a channel using the credentials created in the previous step. + auto channel = grpc::CreateChannel(socket_address, channel_creds); + // Connect to gRPC server with ssl/tls authentication mechanism. + stub_ = SV::NewStub(channel); + } else { + // Connect to gRPC server without ssl/tls authentication mechanism. + stub_ = SV::NewStub(grpc::CreateChannel(socket_address, grpc::InsecureChannelCredentials())); + } + } + virtual ~ClientBase() = default; + + virtual void unpackStatus(Status &status, RP *response) + { + if (!status.error_message().empty() && \ + (status.error_code() == grpc::StatusCode::UNKNOWN || + status.error_code() == grpc::StatusCode::PERMISSION_DENIED || + status.error_code() == grpc::StatusCode::INTERNAL)) { + response->errmsg = util_strdup_s(status.error_message().c_str()); + } else { + response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + } + + response->cc = LCRD_ERR_EXEC; + } + + virtual int run(const RQ *request, RP *response) + { + int ret; + gRQ req; + gRP reply; + ClientContext context; + Status status; + + // Set common name from cert.perm + char common_name_value[ClientBaseConstants::COMMON_NAME_LEN] = { 0 }; + ret = get_common_name_from_tls_cert(m_certFile.c_str(), common_name_value, + ClientBaseConstants::COMMON_NAME_LEN); + if (ret != 0) { + ERROR("Failed to get common name in: %s", m_certFile.c_str()); + return -1; + } + context.AddMetadata("username", std::string(common_name_value, strlen(common_name_value))); + context.AddMetadata("tls_mode", m_tlsMode); + + ret = request_to_grpc(request, &req); + if (ret != 0) { + ERROR("Failed to translate request to grpc"); + response->cc = LCRD_ERR_INPUT; + return -1; + } + + if (check_parameter(req)) { + response->cc = LCRD_ERR_INPUT; + return -1; + } + + status = grpc_call(&context, req, &reply); + if (!status.ok()) { + ERROR("error_code: %d: %s", status.error_code(), status.error_message().c_str()); + unpackStatus(status, response); + return -1; + } + + ret = response_from_grpc(&reply, response); + if (ret != 0) { + ERROR("Failed to transform grpc response"); + response->cc = LCRD_ERR_EXEC; + return -1; + } + + if (response->server_errono != LCRD_SUCCESS) { + response->cc = LCRD_ERR_EXEC; + } + + return (response->cc == LCRD_SUCCESS) ? 0 : -1; + } + +protected: + virtual int request_to_grpc(const RQ *rq, gRQ *grq) + { + return 0; + }; + virtual int response_from_grpc(gRP *reply, RP *response) + { + return 0; + }; + virtual int check_parameter(const gRQ &grq) + { + return 0; + }; + virtual Status grpc_call(ClientContext *context, const gRQ &req, gRP *reply) + { + return Status::OK; + }; + + static std::string ReadTextFile(const char *file) + { + char *real_file = verify_file_and_get_real_path(file); + if (real_file == nullptr) { + return ""; + } + std::ifstream context(real_file, std::ios::in); + if (!context) { + free(real_file); + return ""; + } + std::stringstream ss; + if (context.is_open()) { + ss << context.rdbuf(); + context.close(); + } + free(real_file); + return ss.str(); + } + + std::unique_ptr stub_; + std::string m_tlsMode { ClientBaseConstants::TLS_OFF }; + std::string m_certFile { "" }; +}; + +template +int container_func(const REQUEST *request, RESPONSE *response, void *arg) noexcept +{ + if (request == nullptr || response == nullptr || arg == nullptr) { + ERROR("Receive NULL args"); + return -1; + } + + std::unique_ptr client(new FUNC(arg)); + return client->run(request, response); +} + +#endif /* __CLIENT_BASH_H */ diff --git a/src/connect/client/grpc/grpc_client.cc b/src/connect/client/grpc/grpc_client.cc new file mode 100644 index 0000000..700cac1 --- /dev/null +++ b/src/connect/client/grpc/grpc_client.cc @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc ops functions + ******************************************************************************/ + +#include "grpc_client.h" +#include "grpc_containers_client.h" +#include "grpc_images_client.h" + +int grpc_ops_init(lcrc_connect_ops *ops) +{ + if (ops == nullptr) { + return -1; + } + + if (grpc_containers_client_ops_init(ops)) { + return -1; + } + if (grpc_images_client_ops_init(ops)) { + return -1; + } + + return 0; +} diff --git a/src/connect/client/grpc/grpc_client.h b/src/connect/client/grpc/grpc_client.h new file mode 100644 index 0000000..874a82b --- /dev/null +++ b/src/connect/client/grpc/grpc_client.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide grpc client definition + ******************************************************************************/ +#ifndef __GPRC_CLIENT_H +#define __GRPC_CLIENT_H + +#include "lcrc_connect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int grpc_ops_init(lcrc_connect_ops *ops); + +#ifdef __cplusplus +} +#endif + +#endif /* __GRPC_CLIENT_H */ diff --git a/src/connect/client/grpc/grpc_containers_client.cc b/src/connect/client/grpc/grpc_containers_client.cc new file mode 100644 index 0000000..808e907 --- /dev/null +++ b/src/connect/client/grpc/grpc_containers_client.cc @@ -0,0 +1,2247 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc containers client functions + ******************************************************************************/ +#include "grpc_containers_client.h" +#include +#include +#include +#include +#include +#include "securec.h" +#include "container_copy_to_request.h" +#include "container_exec_request.h" +#include "utils.h" +#include "lcrdtar.h" +#include "stoppable_thread.h" +#include "container.grpc.pb.h" +#include "client_base.h" +#include "pack_config.h" + +using namespace containers; + +using grpc::Channel; +using grpc::ClientContext; +using grpc::ClientReader; +using grpc::ClientReaderWriter; +using grpc::ClientWriter; +using grpc::Status; +using grpc::StatusCode; +using google::protobuf::Timestamp; + +class ContainerVersion : public ClientBase { +public: + explicit ContainerVersion(void *args) + : ClientBase(args) + { + } + ~ContainerVersion() = default; + + int response_from_grpc(VersionResponse *gresponse, lcrc_version_response *response) override + { + if (!gresponse->version().empty()) { + response->version = util_strdup_s(gresponse->version().c_str()); + } + if (!gresponse->git_commit().empty()) { + response->git_commit = util_strdup_s(gresponse->git_commit().c_str()); + } + if (!gresponse->build_time().empty()) { + response->build_time = util_strdup_s(gresponse->build_time().c_str()); + } + if (!gresponse->root_path().empty()) { + response->root_path = util_strdup_s(gresponse->root_path().c_str()); + } + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + response->server_errono = gresponse->cc(); + return 0; + } + + Status grpc_call(ClientContext *context, const VersionRequest &req, VersionResponse *reply) override + { + return stub_->Version(context, req, reply); + } +}; + +class ContainerInfo : public ClientBase { +public: + explicit ContainerInfo(void *args) + : ClientBase(args) + { + } + ~ContainerInfo() = default; + + int response_from_grpc(InfoResponse *gresponse, lcrc_info_response *response) override + { + if (!gresponse->version().empty()) { + response->version = util_strdup_s(gresponse->version().c_str()); + } + response->containers_num = gresponse->containers_num(); + response->c_running = gresponse->c_running(); + response->c_paused = gresponse->c_paused(); + response->c_stopped = gresponse->c_stopped(); + response->images_num = gresponse->images_num(); + get_os_info_from_grpc(response, gresponse); + if (!gresponse->logging_driver().empty()) { + response->logging_driver = util_strdup_s(gresponse->logging_driver().c_str()); + } + if (!gresponse->huge_page_size().empty()) { + response->huge_page_size = util_strdup_s(gresponse->huge_page_size().c_str()); + } + if (!gresponse->isulad_root_dir().empty()) { + response->isulad_root_dir = util_strdup_s(gresponse->isulad_root_dir().c_str()); + } + response->total_mem = gresponse->total_mem(); + get_proxy_info_from_grpc(response, gresponse); + + return 0; + } + + Status grpc_call(ClientContext *context, const InfoRequest &req, InfoResponse *reply) override + { + return stub_->Info(context, req, reply); + } + +private: + void get_os_info_from_grpc(lcrc_info_response *response, InfoResponse *gresponse) + { + if (!gresponse->kversion().empty()) { + response->kversion = util_strdup_s(gresponse->kversion().c_str()); + } + if (!gresponse->os_type().empty()) { + response->os_type = util_strdup_s(gresponse->os_type().c_str()); + } + if (!gresponse->architecture().empty()) { + response->architecture = util_strdup_s(gresponse->architecture().c_str()); + } + if (!gresponse->nodename().empty()) { + response->nodename = util_strdup_s(gresponse->nodename().c_str()); + } + response->cpus = gresponse->cpus(); + if (!gresponse->operating_system().empty()) { + response->operating_system = util_strdup_s(gresponse->operating_system().c_str()); + } + if (!gresponse->cgroup_driver().empty()) { + response->cgroup_driver = util_strdup_s(gresponse->cgroup_driver().c_str()); + } + } + + void get_proxy_info_from_grpc(lcrc_info_response *response, InfoResponse *gresponse) + { + if (!gresponse->http_proxy().empty()) { + response->http_proxy = util_strdup_s(gresponse->http_proxy().c_str()); + } + if (!gresponse->https_proxy().empty()) { + response->https_proxy = util_strdup_s(gresponse->https_proxy().c_str()); + } + if (!gresponse->no_proxy().empty()) { + response->no_proxy = util_strdup_s(gresponse->no_proxy().c_str()); + } + } +}; + +class ContainerCreate : public ClientBase { +public: + explicit ContainerCreate(void *args) + : ClientBase(args) + { + } + ~ContainerCreate() = default; + + int request_to_grpc(const lcrc_create_request *request, CreateRequest *grequest) override + { + int ret = 0; + char *host_json = nullptr, *config_json = nullptr; + + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + if (request->rootfs != nullptr) { + grequest->set_rootfs(request->rootfs); + } + if (request->image != nullptr) { + grequest->set_image(request->image); + } + if (request->runtime != nullptr) { + grequest->set_runtime(request->runtime); + } + ret = generate_hostconfig(request->hostconfig, &host_json); + if (ret) { + ERROR("Failed to pack host config"); + return EINVALIDARGS; + } + grequest->set_hostconfig(host_json); + + free(host_json); + + ret = generate_container_config(request->config, &config_json); + if (ret) { + ERROR("Failed to pack custom config"); + return EINVALIDARGS; + } + grequest->set_customconfig(config_json); + + free(config_json); + + return 0; + } + + int response_from_grpc(CreateResponse *gresponse, lcrc_create_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + if (!gresponse->id().empty()) { + response->id = util_strdup_s(gresponse->id().c_str()); + } + return 0; + } + + int check_parameter(const CreateRequest &req) override + { + int nret = -1; + + if (req.runtime().empty()) { + ERROR("Missing runtime in the request"); + return nret; + } + if (req.rootfs().empty() && req.image().empty()) { + ERROR("Missing container rootfs or image arguments in the request"); + return nret; + } + if (req.hostconfig().empty()) { + ERROR("Missing hostconfig in the request"); + return nret; + } + if (req.customconfig().empty()) { + ERROR("Missing customconfig in the request"); + return nret; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const CreateRequest &req, CreateResponse *reply) override + { + return stub_->Create(context, req, reply); + } +}; + +class ContainerStart : public ClientBase { +public: + explicit ContainerStart(void *args) + : ClientBase(args) + { + } + ~ContainerStart() = default; + + int request_to_grpc(const lcrc_start_request *request, StartRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + if (request->stdin != nullptr) { + grequest->set_stdin(request->stdin); + } + if (request->stdout != nullptr) { + grequest->set_stdout(request->stdout); + } + if (request->stderr != nullptr) { + grequest->set_stderr(request->stderr); + } + grequest->set_attach_stdin(request->attach_stdin); + grequest->set_attach_stdout(request->attach_stdout); + grequest->set_attach_stderr(request->attach_stderr); + + return 0; + } + + int response_from_grpc(StartResponse *gresponse, struct lcrc_start_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const StartRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const StartRequest &req, StartResponse *reply) override + { + return stub_->Start(context, req, reply); + } +}; + +class RemoteStartWriteToServerTask : public StoppableThread { +public: + explicit RemoteStartWriteToServerTask( + std::shared_ptr> stream) + : m_stream(stream) + { + } + ~RemoteStartWriteToServerTask() = default; + + void run() + { + while (stopRequested() == false) { + int cmd; + cmd = getchar(); + RemoteStartRequest request; + if (cmd == EOF) { + request.set_finish(true); + } else { + char in = (char)cmd; + request.set_stdin(&in, 1); + } + if (!m_stream->Write(request)) { + ERROR("Failed to write request to grpc server"); + break; + } + if (cmd == EOF) { + break; + } + } + } + +private: + std::shared_ptr> m_stream; +}; + +class ContainerRemoteStart : public ClientBase { +public: + explicit ContainerRemoteStart(void *args) + : ClientBase(args) + { + } + ~ContainerRemoteStart() = default; + + int set_custom_header_metadata(ClientContext &context, const struct lcrc_start_request *request) + { + if (request == nullptr || request->name == nullptr) { + ERROR("Missing container id in the request"); + return -1; + } + // Set common name from cert.perm + char common_name_value[ClientBaseConstants::COMMON_NAME_LEN] = { 0 }; + int ret = get_common_name_from_tls_cert(m_certFile.c_str(), common_name_value, + ClientBaseConstants::COMMON_NAME_LEN); + if (ret != 0) { + ERROR("Failed to get common name in: %s", m_certFile.c_str()); + return -1; + } + context.AddMetadata("username", std::string(common_name_value, strlen(common_name_value))); + context.AddMetadata("tls_mode", m_tlsMode); + context.AddMetadata("container-id", std::string(request->name)); + context.AddMetadata("attach-stdin", request->attach_stdin ? "true" : "false"); + context.AddMetadata("attach-stdout", request->attach_stdout ? "true" : "false"); + context.AddMetadata("attach-stderr", request->attach_stderr ? "true" : "false"); + return 0; + } + + void get_server_trailing_metadata(ClientContext &context, lcrc_start_response *response) + { + auto metadata = context.GetServerTrailingMetadata(); + auto cc = metadata.find("cc"); + if (cc != metadata.end()) { + auto tmpstr = std::string(cc->second.data(), cc->second.length()); + response->server_errono = (uint32_t)std::stoul(tmpstr, nullptr, 0); + } + auto errmsg = metadata.find("errmsg"); + if (errmsg != metadata.end()) { + auto tmpstr = std::string(errmsg->second.data(), errmsg->second.length()); + response->errmsg = util_strdup_s(tmpstr.c_str()); + } + } + + int run(const struct lcrc_start_request *request, struct lcrc_start_response *response) override + { + ClientContext context; + + if (set_custom_header_metadata(context, request) != 0) { + ERROR("Failed to translate request to grpc"); + response->cc = LCRD_ERR_INPUT; + return -1; + } + + using StreamStartRWSharedPtr = std::shared_ptr>; + StreamStartRWSharedPtr stream(stub_->RemoteStart(&context)); + + RemoteStartWriteToServerTask write_task(stream); + std::thread writer; + if (request->attach_stdin) { + writer = std::thread([&]() { + write_task.run(); + }); + } + + RemoteStartResponse stream_response; + if (request->attach_stdout || request->attach_stderr) { + while (stream->Read(&stream_response)) { + if (stream_response.finish()) { + break; + } + if (!stream_response.stdout().empty()) { + std::cout << stream_response.stdout() << std::flush; + } + if (!stream_response.stderr().empty()) { + std::cerr << stream_response.stderr() << std::flush; + } + } + } + write_task.stop(); + stream->WritesDone(); + Status status = stream->Finish(); + if (!status.ok()) { + ERROR("error_code: %d: %s", status.error_code(), status.error_message().c_str()); + unpackStatus(status, response); + goto out; + } + + get_server_trailing_metadata(context, response); + + if (response->server_errono != LCRD_SUCCESS) { + response->cc = LCRD_ERR_EXEC; + goto out; + } +out: + if (request->attach_stdin) { + pthread_cancel(writer.native_handle()); + if (writer.joinable()) { + writer.join(); + } + } + return (response->cc == LCRD_SUCCESS) ? 0 : -1; + } +}; +class ContainerTop : public ClientBase { +public: + explicit ContainerTop(void *args) + : ClientBase(args) + { + } + ~ContainerTop() = default; + + int request_to_grpc(const lcrc_top_request *request, TopRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + if (request->ps_argc > 0) { + for (int i = 0; i < request->ps_argc; i++) { + grequest->add_args(request->ps_args[i]); + } + } + + return 0; + } + + int response_from_grpc(TopResponse *gresponse, struct lcrc_top_response *response) override + { + int i = 0; + int num = gresponse->processes_size(); + + if (num <= 0) { + response->titles = nullptr; + response->processes_len = 0; + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + return 0; + } + + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + if (!gresponse->titles().empty()) { + response->titles = util_strdup_s(gresponse->titles().c_str()); + } + if ((size_t)num > SIZE_MAX / sizeof(char *)) { + ERROR("Too many summary info!"); + return -1; + } + response->processes = (char **)util_common_calloc_s(num * sizeof(char *)); + if (response->processes == nullptr) { + ERROR("out of memory"); + response->cc = LCRD_ERR_MEMOUT; + return -1; + } + for (i = 0; i < num; i++) { + response->processes[i] = util_strdup_s(gresponse->processes(i).c_str()); + } + response->processes_len = (size_t)gresponse->processes_size(); + + return 0; + } + + int check_parameter(const TopRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + Status grpc_call(ClientContext *context, const TopRequest &req, TopResponse *reply) override + { + return stub_->Top(context, req, reply); + } +}; + +class ContainerStop : public ClientBase { +public: + explicit ContainerStop(void *args) + : ClientBase(args) + { + } + ~ContainerStop() = default; + + int request_to_grpc(const lcrc_stop_request *request, StopRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + grequest->set_force(request->force); + grequest->set_timeout(request->timeout); + + return 0; + } + + int response_from_grpc(StopResponse *gresponse, lcrc_stop_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const StopRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const StopRequest &req, StopResponse *reply) override + { + return stub_->Stop(context, req, reply); + } +}; + +class ContainerRename : public ClientBase { +public: + explicit ContainerRename(void *args) + : ClientBase(args) + { + } + ~ContainerRename() = default; + + int request_to_grpc(const lcrc_rename_request *request, RenameRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->old_name != nullptr) { + grequest->set_oldname(request->old_name); + } + + if (request->new_name != nullptr) { + grequest->set_newname(request->new_name); + } + + return 0; + } + + int response_from_grpc(RenameResponse *gresponse, lcrc_rename_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const RenameRequest &req) override + { + if (req.oldname().empty()) { + ERROR("Missing container old name in the request"); + return -1; + } + + if (req.newname().empty()) { + ERROR("Missing container new name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const RenameRequest &req, RenameResponse *reply) override + { + return stub_->Rename(context, req, reply); + } +}; + +class ContainerRestart : public ClientBase { +public: + explicit ContainerRestart(void *args) + : ClientBase(args) + { + } + ~ContainerRestart() = default; + + int request_to_grpc(const lcrc_restart_request *request, RestartRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + grequest->set_timeout((int32_t)(request->timeout)); + + return 0; + } + + int response_from_grpc(RestartResponse *gresponse, lcrc_restart_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const RestartRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const RestartRequest &req, RestartResponse *reply) override + { + return stub_->Restart(context, req, reply); + } +}; + +class ContainerKill : public ClientBase { +public: + explicit ContainerKill(void *args) + : ClientBase(args) + { + } + ~ContainerKill() = default; + + int request_to_grpc(const lcrc_kill_request *request, KillRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + grequest->set_signal(request->signal); + + return 0; + } + + int response_from_grpc(KillResponse *gresponse, lcrc_kill_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const KillRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const KillRequest &req, KillResponse *reply) override + { + return stub_->Kill(context, req, reply); + } +}; + +class ContainerExec : public ClientBase { +public: + explicit ContainerExec(void *args) + : ClientBase(args) + { + } + ~ContainerExec() = default; + + int request_to_grpc(const lcrc_exec_request *request, ExecRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_container_id(request->name); + } + grequest->set_tty(request->tty); + grequest->set_open_stdin(request->open_stdin); + grequest->set_attach_stdin(request->attach_stdin); + grequest->set_attach_stdout(request->attach_stdout); + grequest->set_attach_stderr(request->attach_stderr); + if (request->stdin != nullptr) { + grequest->set_stdin(request->stdin); + } + if (request->stdout != nullptr) { + grequest->set_stdout(request->stdout); + } + if (request->stderr != nullptr) { + grequest->set_stderr(request->stderr); + } + for (int i = 0; i < request->argc; i++) { + grequest->add_argv(request->argv[i]); + } + for (size_t i = 0; i < request->env_len; i++) { + grequest->add_env(request->env[i]); + } + + return 0; + } + + int response_from_grpc(ExecResponse *gresponse, lcrc_exec_response *response) override + { + response->server_errono = gresponse->cc(); + response->pid = (uint32_t)gresponse->pid(); + response->exit_code = gresponse->exit_code(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const ExecRequest &req) override + { + if (req.container_id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const ExecRequest &req, ExecResponse *reply) override + { + return stub_->Exec(context, req, reply); + } +}; + +class RemoteExecWriteToServerTask : public StoppableThread { +public: + explicit RemoteExecWriteToServerTask( + std::shared_ptr> stream) + : m_stream(stream) + { + } + ~RemoteExecWriteToServerTask() = default; + + void run() + { + while (stopRequested() == false) { + int cmd; + cmd = getchar(); + RemoteExecRequest request; + if (cmd == EOF) { + request.set_finish(true); + } else { + char in = (char)cmd; + request.add_cmd(&in, 1); + } + if (!m_stream->Write(request)) { + ERROR("Failed to write request to grpc server"); + break; + } + if (cmd == EOF) { + break; + } + } + } + +private: + std::shared_ptr> m_stream; +}; + +class ContainerRemoteExec : public ClientBase { +public: + explicit ContainerRemoteExec(void *args) + : ClientBase(args) + { + } + ~ContainerRemoteExec() = default; + + int set_custom_header_metadata(ClientContext &context, const struct lcrc_exec_request *request, + struct lcrc_exec_response *response) + { + int ret = 0; + char *json = nullptr; + parser_error err = nullptr; + container_exec_request exec = { 0 }; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + // Set common name from cert.perm + char common_name_value[ClientBaseConstants::COMMON_NAME_LEN] = { 0 }; + + if (request == nullptr || request->name == nullptr) { + ERROR("Missing container id in the request"); + return -1; + } + + exec.container_id = request->name; + exec.tty = request->tty; + exec.attach_stdin = request->attach_stdin; + exec.attach_stdout = request->attach_stdout; + exec.attach_stderr = request->attach_stderr; + exec.timeout = request->timeout; + exec.argv = request->argv; + exec.argv_len = (size_t)request->argc; + exec.env = request->env; + exec.env_len = request->env_len; + json = container_exec_request_generate_json(&exec, &ctx, &err); + if (json == nullptr) { + format_errorf(&response->errmsg, "Can not generate json: %s", err); + ret = -1; + goto out; + } + ret = get_common_name_from_tls_cert(m_certFile.c_str(), common_name_value, + ClientBaseConstants::COMMON_NAME_LEN); + if (ret != 0) { + ERROR("Failed to get common name in: %s", m_certFile.c_str()); + ret = -1; + goto out; + } + context.AddMetadata("username", std::string(common_name_value, strlen(common_name_value))); + context.AddMetadata("tls_mode", m_tlsMode); + context.AddMetadata("isulad-remote-exec", json); +out: + free(err); + free(json); + return ret; + } + void get_server_trailing_metadata(ClientContext &context, lcrc_exec_response *response) + { + auto metadata = context.GetServerTrailingMetadata(); + auto cc = metadata.find("cc"); + if (cc != metadata.end()) { + auto tmpstr = std::string(cc->second.data(), cc->second.length()); + response->server_errono = (uint32_t)std::stoul(tmpstr, nullptr, 0); + } + auto pid = metadata.find("pid"); + if (pid != metadata.end()) { + auto tmpstr = std::string(pid->second.data(), pid->second.length()); + response->pid = (uint32_t)std::stoul(tmpstr, nullptr, 0); + } + auto exit_code = metadata.find("exit_code"); + if (exit_code != metadata.end()) { + auto tmpstr = std::string(exit_code->second.data(), exit_code->second.length()); + response->exit_code = (uint32_t)std::stoul(tmpstr, nullptr, 0); + } + auto errmsg = metadata.find("errmsg"); + if (errmsg != metadata.end()) { + auto tmpstr = std::string(errmsg->second.data(), errmsg->second.length()); + response->errmsg = util_strdup_s(tmpstr.c_str()); + } + } + int run(const struct lcrc_exec_request *request, struct lcrc_exec_response *response) override + { + ClientContext context; + + if (set_custom_header_metadata(context, request, response) != 0) { + ERROR("Failed to translate request to grpc"); + response->cc = LCRD_ERR_INPUT; + return -1; + } + + std::shared_ptr> stream(stub_->RemoteExec(&context)); + + RemoteExecWriteToServerTask write_task(stream); + std::thread writer([&]() { + write_task.run(); + }); + + RemoteExecResponse stream_response; + while (stream->Read(&stream_response)) { + if (stream_response.finish()) { + break; + } + std::cout << stream_response.stdout() << std::flush; + } + write_task.stop(); + stream->WritesDone(); + Status status = stream->Finish(); + if (!status.ok()) { + ERROR("error_code: %d: %s", status.error_code(), status.error_message().c_str()); + unpackStatus(status, response); + goto out; + } + + get_server_trailing_metadata(context, response); + + if (response->server_errono != LCRD_SUCCESS) { + response->cc = LCRD_ERR_EXEC; + goto out; + } +out: + pthread_cancel(writer.native_handle()); + if (writer.joinable()) { + writer.join(); + } + return (response->cc == LCRD_SUCCESS) ? 0 : -1; + } +}; + +class ContainerInspect : public ClientBase { +public: + explicit ContainerInspect(void *args) + : ClientBase(args) + { + } + ~ContainerInspect() = default; + + int request_to_grpc(const lcrc_inspect_request *request, InspectContainerRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + grequest->set_bformat(request->bformat); + grequest->set_timeout(request->timeout); + + return 0; + } + + int response_from_grpc(InspectContainerResponse *gresponse, lcrc_inspect_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->containerjson().empty()) { + response->json = util_strdup_s(gresponse->containerjson().c_str()); + } + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const InspectContainerRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const InspectContainerRequest &req, + InspectContainerResponse *reply) override + { + return stub_->Inspect(context, req, reply); + } +}; + +class ContainerDelete : public ClientBase { +public: + explicit ContainerDelete(void *args) + : ClientBase(args) + { + } + ~ContainerDelete() = default; + + int request_to_grpc(const lcrc_delete_request *request, DeleteRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + grequest->set_force(request->force); + + return 0; + } + + int response_from_grpc(DeleteResponse *gresponse, lcrc_delete_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->id().empty()) { + response->name = util_strdup_s(gresponse->id().c_str()); + } + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const DeleteRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const DeleteRequest &req, DeleteResponse *reply) override + { + return stub_->Delete(context, req, reply); + } +}; + +class ContainerList : public ClientBase { +public: + explicit ContainerList(void *args) + : ClientBase(args) + { + } + ~ContainerList() = default; + + int request_to_grpc(const lcrc_list_request *request, ListRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->filters != nullptr) { + google::protobuf::Map *map; + map = grequest->mutable_filters(); + for (size_t i = 0; i < request->filters->len; i++) { + (*map)[request->filters->keys[i]] = request->filters->values[i]; + } + } + grequest->set_all(request->all); + + return 0; + } + + int response_from_grpc(ListResponse *gresponse, lcrc_list_response *response) override + { + int i = 0; + int num = gresponse->containers_size(); + + if (num <= 0) { + response->container_summary = nullptr; + response->container_num = 0; + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + return 0; + } + if ((size_t)num > SIZE_MAX / sizeof(lcrc_container_summary_info *)) { + ERROR("Too many summary info!"); + return -1; + } + response->container_summary = (struct lcrc_container_summary_info **)util_common_calloc_s( + sizeof(struct lcrc_container_summary_info *) * (size_t)num); + if (response->container_summary == nullptr) { + ERROR("out of memory"); + response->cc = LCRD_ERR_MEMOUT; + return -1; + } + + for (i = 0; i < num; i++) { + if (get_container_summary_from_grpc(response, gresponse, i)) { + return -1; + } + } + + return 0; + } + + Status grpc_call(ClientContext *context, const ListRequest &req, ListResponse *reply) override + { + return stub_->List(context, req, reply); + } + +private: + int get_container_summary_from_grpc(lcrc_list_response *response, ListResponse *gresponse, int index) + { + response->container_summary[index] = + (struct lcrc_container_summary_info *)util_common_calloc_s(sizeof(struct lcrc_container_summary_info)); + if (response->container_summary[index] == nullptr) { + ERROR("out of memory"); + response->cc = LCRD_ERR_MEMOUT; + return -1; + } + const Container &in = gresponse->containers(index); + + const char *id = !in.id().empty() ? in.id().c_str() : "-"; + response->container_summary[index]->id = util_strdup_s(id); + const char *name = !in.name().empty() ? in.name().c_str() : "-"; + response->container_summary[index]->name = util_strdup_s(name); + response->container_summary[index]->runtime = !in.runtime().empty() ? util_strdup_s(in.runtime().c_str()) + : nullptr; + response->container_summary[index]->has_pid = (int)in.pid() != 0; + response->container_summary[index]->pid = (uint32_t)in.pid(); + response->container_summary[index]->status = (Container_Status)in.status(); + response->container_summary[index]->image = !in.image().empty() ? util_strdup_s(in.image().c_str()) + : util_strdup_s("none"); + response->container_summary[index]->command = !in.command().empty() ? util_strdup_s(in.command().c_str()) + : util_strdup_s("-"); + const char *starttime = !in.startat().empty() ? in.startat().c_str() : "-"; + response->container_summary[index]->startat = util_strdup_s(starttime); + + const char *finishtime = !in.finishat().empty() ? in.finishat().c_str() : "-"; + response->container_summary[index]->finishat = util_strdup_s(finishtime); + + response->container_summary[index]->exit_code = in.exit_code(); + response->container_summary[index]->restart_count = (uint32_t)(in.restartcount()); + + std::string healthState { "" }; + if (!in.health_state().empty()) { + healthState = "(" + in.health_state() + ")"; + } + response->container_summary[index]->health_state = !healthState.empty() ? util_strdup_s(healthState.c_str()) + : nullptr; + response->container_num++; + + return 0; + } +}; + +class ContainerWait : public ClientBase { +public: + explicit ContainerWait(void *args) + : ClientBase(args) + { + } + ~ContainerWait() = default; + + int request_to_grpc(const lcrc_wait_request *request, WaitRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + if (request->id != nullptr) { + grequest->set_id(request->id); + } + grequest->set_condition(request->condition); + + return 0; + } + + int response_from_grpc(WaitResponse *gresponse, lcrc_wait_response *response) override + { + response->exit_code = (int)gresponse->exit_code(); + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const WaitRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const WaitRequest &req, WaitResponse *reply) override + { + return stub_->Wait(context, req, reply); + } +}; + +class AttachWriteToServerTask : public StoppableThread { +public: + explicit AttachWriteToServerTask(std::shared_ptr> stream) + : m_stream(stream) + { + } + ~AttachWriteToServerTask() = default; + + void run() + { + while (stopRequested() == false) { + int cmd; + cmd = getchar(); + AttachRequest request; + if (cmd == EOF) { + request.set_finish(true); + } else { + char in = (char)cmd; + request.set_stdin(&in, 1); + } + if (!m_stream->Write(request)) { + ERROR("Failed to write request to grpc server"); + break; + } + if (cmd == EOF) { + break; + } + } + } + +private: + std::shared_ptr> m_stream; +}; + +class ContainerAttach : public ClientBase { +public: + explicit ContainerAttach(void *args) + : ClientBase(args) + { + } + ~ContainerAttach() = default; + + int set_custom_header_metadata(ClientContext &context, const struct lcrc_attach_request *request) + { + if (request == nullptr || request->name == nullptr) { + ERROR("Missing container id in the request"); + return -1; + } + // Set common name from cert.perm + char common_name_value[ClientBaseConstants::COMMON_NAME_LEN] = { 0 }; + int ret = get_common_name_from_tls_cert(m_certFile.c_str(), common_name_value, + ClientBaseConstants::COMMON_NAME_LEN); + if (ret != 0) { + ERROR("Failed to get common name in: %s", m_certFile.c_str()); + return -1; + } + context.AddMetadata("username", std::string(common_name_value, strlen(common_name_value))); + context.AddMetadata("tls_mode", m_tlsMode); + context.AddMetadata("container-id", std::string(request->name)); + context.AddMetadata("attach-stdin", request->attach_stdin ? "true" : "false"); + context.AddMetadata("attach-stdout", request->attach_stdout ? "true" : "false"); + context.AddMetadata("attach-stderr", request->attach_stderr ? "true" : "false"); + + return 0; + } + void get_server_trailing_metadata(ClientContext &context, lcrc_attach_response *response) + { + auto metadata = context.GetServerTrailingMetadata(); + auto cc = metadata.find("cc"); + if (cc != metadata.end()) { + auto tmpstr = std::string(cc->second.data(), cc->second.length()); + response->server_errono = (uint32_t)std::stoul(tmpstr, nullptr, 0); + } + auto errmsg = metadata.find("errmsg"); + if (errmsg != metadata.end()) { + auto tmpstr = std::string(errmsg->second.data(), errmsg->second.length()); + response->errmsg = util_strdup_s(tmpstr.c_str()); + } + } + + int run(const struct lcrc_attach_request *request, struct lcrc_attach_response *response) override + { + ClientContext context; + + if (set_custom_header_metadata(context, request) != 0) { + ERROR("Failed to translate request to grpc"); + response->cc = LCRD_ERR_INPUT; + return -1; + } + + std::shared_ptr> stream(stub_->Attach(&context)); + + AttachWriteToServerTask write_task(stream); + std::thread writer([&]() { + write_task.run(); + }); + + if (request->attach_stdin) { + AttachResponse stream_response; + while (stream->Read(&stream_response)) { + if (stream_response.finish()) { + break; + } + if (!stream_response.stdout().empty()) { + std::cout << stream_response.stdout() << std::flush; + } + if (!stream_response.stderr().empty()) { + std::cerr << stream_response.stderr() << std::flush; + } + } + } + write_task.stop(); + stream->WritesDone(); + Status status = stream->Finish(); + if (!status.ok()) { + ERROR("error_code: %d: %s", status.error_code(), status.error_message().c_str()); + unpackStatus(status, response); + goto out; + } + + get_server_trailing_metadata(context, response); + + if (response->server_errono != LCRD_SUCCESS) { + response->cc = LCRD_ERR_EXEC; + } + +out: + if (request->attach_stdin) { + pthread_cancel(writer.native_handle()); + if (writer.joinable()) { + writer.join(); + } + } + return (response->cc == LCRD_SUCCESS) ? 0 : -1; + } +}; + +class ContainerPause : public ClientBase { +public: + explicit ContainerPause(void *args) + : ClientBase(args) + { + } + ~ContainerPause() = default; + + int request_to_grpc(const lcrc_pause_request *request, PauseRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + + return 0; + } + + int response_from_grpc(PauseResponse *gresponse, lcrc_pause_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const PauseRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const PauseRequest &req, PauseResponse *reply) override + { + return stub_->Pause(context, req, reply); + } +}; + +class ContainerResume : public ClientBase { +public: + explicit ContainerResume(void *args) + : ClientBase(args) + { + } + ~ContainerResume() = default; + + int request_to_grpc(const lcrc_resume_request *request, ResumeRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + + return 0; + } + + int response_from_grpc(ResumeResponse *gresponse, lcrc_resume_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const ResumeRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const ResumeRequest &req, ResumeResponse *reply) override + { + return stub_->Resume(context, req, reply); + } +}; + +class ContainerExport : public ClientBase { +public: + explicit ContainerExport(void *args) + : ClientBase(args) + { + } + ~ContainerExport() = default; + + int request_to_grpc(const lcrc_export_request *request, ExportRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + if (request->file != nullptr) { + grequest->set_file(request->file); + } + + return 0; + } + + int response_from_grpc(ExportResponse *gresponse, lcrc_export_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const ExportRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + if (req.file().empty()) { + ERROR("Missing output file path in the request"); + return -1; + } + return 0; + } + + Status grpc_call(ClientContext *context, const ExportRequest &req, ExportResponse *reply) override + { + return stub_->Export(context, req, reply); + } +}; + +class ContainerUpdate : public ClientBase { +public: + explicit ContainerUpdate(void *args) + : ClientBase(args) + { + } + ~ContainerUpdate() = default; + + int request_to_grpc(const lcrc_update_request *request, UpdateRequest *grequest) override + { + int ret = 0; + char *json = nullptr; + + if (request == nullptr) { + return -1; + } + + lcrc_host_config_t hostconfig; + if (memset_s(&hostconfig, sizeof(hostconfig), 0, sizeof(hostconfig)) != EOK) { + ERROR("Failed to set memory"); + return -1; + } + + if (request->updateconfig) { + hostconfig.restart_policy = request->updateconfig->restart_policy; + hostconfig.cr = request->updateconfig->cr; + } + ret = generate_hostconfig(&hostconfig, &json); + if (ret != 0) { + ERROR("Failed to generate hostconfig json"); + ret = -1; + goto cleanup; + } + + grequest->set_hostconfig(json); + if (request->name != nullptr) { + grequest->set_id(request->name); + } + +cleanup: + free(json); + return ret; + } + + int response_from_grpc(UpdateResponse *gresponse, lcrc_update_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const UpdateRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const UpdateRequest &req, UpdateResponse *reply) override + { + return stub_->Update(context, req, reply); + } +}; + +class ContainerConf : public ClientBase { +public: + explicit ContainerConf(void *args) + : ClientBase(args) + { + } + ~ContainerConf() = default; + + int request_to_grpc(const lcrc_container_conf_request *request, Container_conf_Request *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_container_id(request->name); + } + + return 0; + } + + int response_from_grpc(Container_conf_Response *gresponse, lcrc_container_conf_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + if (!gresponse->container_logpath().empty()) { + response->container_logpath = util_strdup_s(gresponse->container_logpath().c_str()); + } + response->container_logrotate = gresponse->container_logrotate(); + if (!gresponse->container_logsize().empty()) { + response->container_logsize = util_strdup_s(gresponse->container_logsize().c_str()); + } + + return 0; + } + + int check_parameter(const Container_conf_Request &req) override + { + if (req.container_id().empty()) { + ERROR("Missing container name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const Container_conf_Request &req, Container_conf_Response *reply) override + { + return stub_->Container_conf(context, req, reply); + } +}; + +class ContainerStats : public ClientBase { +public: + explicit ContainerStats(void *args) + : ClientBase(args) + { + } + ~ContainerStats() = default; + + int request_to_grpc(const lcrc_stats_request *request, StatsRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->runtime != nullptr) { + grequest->set_runtime(request->runtime); + } + + for (size_t i = 0; request->containers != nullptr && i < request->containers_len; i++) { + grequest->add_containers(request->containers[i]); + } + + grequest->set_all(request->all); + + return 0; + } + + int response_from_grpc(StatsResponse *gresponse, lcrc_stats_response *response) override + { + int size = gresponse->containers_size(); + if (size > 0) { + response->container_stats = + static_cast(util_common_calloc_s(size * sizeof(struct lcrc_container_info))); + if (response->container_stats == nullptr) { + ERROR("Out of memory"); + return -1; + } + for (int i = 0; i < size; i++) { + if (!gresponse->containers(i).id().empty()) { + response->container_stats[i].id = util_strdup_s(gresponse->containers(i).id().c_str()); + } + response->container_stats[i].has_pid = (int)gresponse->containers(i).pid() != -1; + response->container_stats[i].pid = (uint32_t)gresponse->containers(i).pid(); + response->container_stats[i].status = (Container_Status)((int)gresponse->containers(i).status()); + response->container_stats[i].pids_current = gresponse->containers(i).pids_current(); + response->container_stats[i].cpu_use_nanos = gresponse->containers(i).cpu_use_nanos(); + response->container_stats[i].cpu_system_use = gresponse->containers(i).cpu_system_use(); + response->container_stats[i].online_cpus = gresponse->containers(i).online_cpus(); + response->container_stats[i].blkio_read = gresponse->containers(i).blkio_read(); + response->container_stats[i].blkio_write = gresponse->containers(i).blkio_write(); + response->container_stats[i].mem_used = gresponse->containers(i).mem_used(); + response->container_stats[i].mem_limit = gresponse->containers(i).mem_limit(); + response->container_stats[i].kmem_used = gresponse->containers(i).kmem_used(); + response->container_stats[i].kmem_limit = gresponse->containers(i).kmem_limit(); + } + response->container_num = (size_t)size; + } + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const StatsRequest &req) override + { + if (req.runtime().empty()) { + ERROR("Missing runtime in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const StatsRequest &req, StatsResponse *reply) override + { + return stub_->Stats(context, req, reply); + } +}; + +class ContainerEvents : public ClientBase { +public: + explicit ContainerEvents(void *args) + : ClientBase(args) + { + } + ~ContainerEvents() = default; + + int run(const struct lcrc_events_request *request, struct lcrc_events_response *response) override + { + int ret; + EventsRequest req; + Event event; + ClientContext context; + Status status; + container_events_format_t lcrc_event; + + ret = events_request_to_grpc(request, &req); + if (ret != 0) { + ERROR("Failed to translate request to grpc"); + response->server_errono = LCRD_ERR_INPUT; + return -1; + } + + std::unique_ptr> reader(stub_->Events(&context, req)); + while (reader->Read(&event)) { + event_from_grpc(&lcrc_event, &event); + if (request->cb != nullptr) { + request->cb(&lcrc_event); + } + } + status = reader->Finish(); + if (!status.ok()) { + ERROR("error_code: %d: %s", status.error_code(), status.error_message().c_str()); + unpackStatus(status, response); + return -1; + } + + if (response->server_errono != LCRD_SUCCESS) { + response->cc = LCRD_ERR_EXEC; + } + + return (response->cc == LCRD_SUCCESS) ? 0 : -1; + } + +private: + void protobuf_timestamp_to_grpc(const types_timestamp_t *timestamp, Timestamp *gtimestamp) + { + gtimestamp->set_seconds(timestamp->seconds); + gtimestamp->set_nanos(timestamp->nanos); + } + + void protobuf_timestamp_from_grpc(types_timestamp_t *timestamp, const Timestamp >imestamp) + { + timestamp->has_seconds = gtimestamp.seconds() != 0; + timestamp->seconds = gtimestamp.seconds(); + timestamp->has_nanos = gtimestamp.nanos() != 0; + timestamp->nanos = gtimestamp.nanos(); + } + + void event_from_grpc(container_events_format_t *event, Event *gevent) + { + errno_t mret = EOK; + + mret = memset_s(event, sizeof(*event), 0, sizeof(*event)); + if (mret != EOK) { + ERROR("Failed to set memory"); + return; + } + if (!gevent->id().empty()) { + event->id = (char *)gevent->id().c_str(); + } + + event->has_type = true; + event->type = (container_events_type_t)((int)gevent->type()); + event->has_pid = (int)gevent->pid() != -1; + event->pid = (uint32_t)gevent->pid(); + event->has_exit_status = true; + event->exit_status = gevent->exit_status(); + + if (gevent->has_timestamp()) { + protobuf_timestamp_from_grpc(&event->timestamp, gevent->timestamp()); + } + } + + int events_request_to_grpc(const struct lcrc_events_request *request, EventsRequest *grequest) + { + if (request == nullptr) { + return -1; + } + + grequest->set_storeonly(request->storeonly); + + if (request->id != nullptr) { + grequest->set_id(request->id); + } + + if (request->since.has_seconds || request->since.has_nanos) { + protobuf_timestamp_to_grpc((const types_timestamp_t *)(&request->since), grequest->mutable_since()); + } + if (request->until.has_seconds || request->until.has_nanos) { + protobuf_timestamp_to_grpc((const types_timestamp_t *)(&request->until), grequest->mutable_until()); + } + + return 0; + } +}; + +struct CopyFromContainerContext { + CopyFromContainerRequest request; + ClientContext context; + ClientReader *reader; +}; + +// Note: len of buf can not smaller than ARCHIVE_BLOCK_SIZE +static ssize_t CopyFromContainerRead(void *context, void *buf, size_t len) +{ + CopyFromContainerResponse res; + struct CopyFromContainerContext *gcopy = (struct CopyFromContainerContext *)context; + if (!gcopy->reader->Read(&res)) { + return -1; + } + size_t data_len = res.data().length(); + if (data_len <= len) { + if (memcpy_s(buf, len, res.data().c_str(), data_len) != EOK) { + return -1; + } + return (ssize_t)data_len; + } + + return -1; +} + +static int CopyFromContainerFinish(void *context, char **err) +{ + struct CopyFromContainerContext *gcopy = (struct CopyFromContainerContext *)context; + CopyFromContainerResponse res; + + if (gcopy->reader->Read(&res)) { + // Connection still alive, cancel it + gcopy->context.TryCancel(); + gcopy->reader->Finish(); + } else { + Status status = gcopy->reader->Finish(); + if (!status.ok()) { + ERROR("error_code: %d: %s", status.error_code(), status.error_message().c_str()); + if (!status.error_message().empty() && + (status.error_code() == StatusCode::UNKNOWN || status.error_code() == StatusCode::PERMISSION_DENIED || + status.error_code() == grpc::StatusCode::INTERNAL)) { + *err = util_strdup_s(status.error_message().c_str()); + } else { + *err = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + } + return -1; + } + } + delete gcopy->reader; + delete gcopy; + return 0; +} + +class CopyFromContainer + : public ClientBase { +public: + explicit CopyFromContainer(void *args) + : ClientBase(args) + { + } + ~CopyFromContainer() = default; + + int run(const struct lcrc_copy_from_container_request *request, + struct lcrc_copy_from_container_response *response) override + { + int ret; + CopyFromContainerResponse res; + struct CopyFromContainerContext *ctx = new (std::nothrow)(struct CopyFromContainerContext); + if (ctx == nullptr) { + return -1; + } + + ret = copy_from_container_request_to_grpc(request, &ctx->request); + if (ret != 0) { + ERROR("Failed to translate request to grpc"); + response->server_errono = LCRD_ERR_INPUT; + return -1; + } + + // Set common name from cert.perm + char common_name_value[ClientBaseConstants::COMMON_NAME_LEN] = { 0 }; + ret = get_common_name_from_tls_cert(m_certFile.c_str(), common_name_value, + ClientBaseConstants::COMMON_NAME_LEN); + if (ret) { + ERROR("Failed to get common name in: %s", m_certFile.c_str()); + return -1; + } + ctx->context.AddMetadata("username", std::string(common_name_value, strlen(common_name_value))); + ctx->context.AddMetadata("tls_mode", m_tlsMode); + auto reader = stub_->CopyFromContainer(&ctx->context, ctx->request); + reader->WaitForInitialMetadata(); + + ctx->reader = reader.release(); + auto metadata = ctx->context.GetServerInitialMetadata(); + auto stat = metadata.find("isulad-container-path-stat"); + if (stat != metadata.end()) { + char *err = nullptr; + std::string json = std::string(stat->second.data(), stat->second.length()); + response->stat = container_path_stat_parse_data(json.c_str(), nullptr, &err); + if (response->stat == nullptr) { + ERROR("Invalid json: %s", err); + free(err); + CopyFromContainerFinish(ctx, &response->errmsg); + return -1; + } + free(err); + } else { + CopyFromContainerFinish(ctx, &response->errmsg); + return -1; + } + // Ignore the first reader which is used for transform metadata + ctx->reader->Read(&res); + response->reader.context = (void *)ctx; + response->reader.read = CopyFromContainerRead; + response->reader.close = CopyFromContainerFinish; + + return 0; + } + +private: + int copy_from_container_request_to_grpc(const struct lcrc_copy_from_container_request *request, + CopyFromContainerRequest *grequest) + { + if (request == nullptr) { + return -1; + } + + if (request->runtime != nullptr) { + grequest->set_runtime(request->runtime); + } + + if (request->id != nullptr) { + grequest->set_id(request->id); + } + + if (request->srcpath != nullptr) { + grequest->set_srcpath(request->srcpath); + } + + return 0; + } +}; + +class CopyToContainerWriteToServerTask : public StoppableThread { +public: + explicit CopyToContainerWriteToServerTask( + const struct io_read_wrapper *reader, + std::shared_ptr> stream) + : m_reader(reader), m_stream(stream) + { + } + ~CopyToContainerWriteToServerTask() = default; + + void run() + { + size_t len = ARCHIVE_BLOCK_SIZE; + char *buf = (char *)util_common_calloc_s(len); + if (buf == nullptr) { + ERROR("Out of memory"); + m_stream->WritesDone(); + return; + } + + while (stopRequested() == false) { + ssize_t have_read_len = m_reader->read(m_reader->context, buf, len); + CopyToContainerRequest request; + request.set_data((const void*)buf, (size_t)have_read_len); + if (!m_stream->Write(request)) { + DEBUG("Server may be exited, stop send data"); + break; + } + } + free(buf); + m_stream->WritesDone(); + } + +private: + const struct io_read_wrapper *m_reader; + std::shared_ptr> m_stream; +}; + +class CopyToContainer + : public ClientBase { +public: + explicit CopyToContainer(void *args) + : ClientBase(args) + { + } + ~CopyToContainer() = default; + + int set_custom_header_metadata(ClientContext &context, const struct lcrc_copy_to_container_request *request, + struct lcrc_copy_to_container_response *response) + { + int ret = 0; + char *json = nullptr; + char *err = nullptr; + container_copy_to_request copy = { 0 }; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + // Set common name from cert.perm + char common_name_value[ClientBaseConstants::COMMON_NAME_LEN] = { 0 }; + + if (request == nullptr || request->id == nullptr) { + ERROR("Missing container id in the request"); + return -1; + } + + copy.id = request->id; + copy.runtime = request->runtime; + copy.src_path = request->srcpath; + copy.src_isdir = request->srcisdir; + copy.src_rebase_name = request->srcrebase; + copy.dst_path = request->dstpath; + json = container_copy_to_request_generate_json(©, &ctx, &err); + if (json == nullptr) { + format_errorf(&response->errmsg, "Can not generate json: %s", err); + ret = -1; + goto out; + } + ret = get_common_name_from_tls_cert(m_certFile.c_str(), common_name_value, + ClientBaseConstants::COMMON_NAME_LEN); + if (ret) { + ERROR("Failed to get common name in: %s", m_certFile.c_str()); + ret = -1; + goto out; + } + context.AddMetadata("username", std::string(common_name_value, strlen(common_name_value))); + context.AddMetadata("tls_mode", m_tlsMode); + context.AddMetadata("isulad-copy-to-container", json); +out: + free(err); + free(json); + return ret; + } + + int run(const struct lcrc_copy_to_container_request *request, struct lcrc_copy_to_container_response *response) + override + { + ClientContext context; + if (set_custom_header_metadata(context, request, response) != 0) { + ERROR("Failed to translate request to grpc"); + response->cc = LCRD_ERR_INPUT; + return -1; + } + using StreamRSharedPtr = std::shared_ptr>; + StreamRSharedPtr stream(stub_->CopyToContainer(&context)); + + CopyToContainerWriteToServerTask write_task(&request->reader, stream); + std::thread writer([&]() { + write_task.run(); + }); + + CopyToContainerResponse stream_response; + while (stream->Read(&stream_response)) { + if (stream_response.finish()) { + break; + } + } + write_task.stop(); + writer.join(); + + Status status = stream->Finish(); + if (!status.ok()) { + ERROR("error_code: %d: %s", status.error_code(), status.error_message().c_str()); + unpackStatus(status, response); + return -1; + } + + return 0; + } +}; + +class ContainerLogs : public ClientBase { +public: + explicit ContainerLogs(void *args) + : ClientBase(args) + { + } + ~ContainerLogs() = default; + + int run(const struct lcrc_logs_request *request, struct lcrc_logs_response *response) override + { + ClientContext context; + LogsRequest grequest; + int ret = -1; + + // Set common name from cert.perm + char common_name_value[ClientBaseConstants::COMMON_NAME_LEN] = { 0 }; + ret = get_common_name_from_tls_cert(m_certFile.c_str(), common_name_value, + ClientBaseConstants::COMMON_NAME_LEN); + if (ret != 0) { + ERROR("Failed to get common name in: %s", m_certFile.c_str()); + return -1; + } + context.AddMetadata("username", std::string(common_name_value, strlen(common_name_value))); + context.AddMetadata("tls_mode", m_tlsMode); + + if (logs_request_to_grpc(request, &grequest) != 0) { + ERROR("Failed to transform container request to grpc"); + response->server_errono = LCRD_ERR_INPUT; + return -1; + } + + auto reader = stub_->Logs(&context, grequest); + + LogsResponse gresponse; + while (reader->Read(&gresponse)) { + show_container_log(request, gresponse); + } + Status status = reader->Finish(); + if (!status.ok()) { + ERROR("error code: %d: %s", status.error_code(), status.error_message().c_str()); + unpackStatus(status, response); + return -1; + } + return 0; + } + +private: + void show_container_log(const struct lcrc_logs_request *request, const LogsResponse &gresponse) + { + static std::ostream *os = nullptr; + + if (gresponse.stream() == "stdout") { + os = &std::cout; + } else if (gresponse.stream() == "stderr") { + os = &std::cerr; + } else { + ERROR("Invalid container log: %s", gresponse.stream().c_str()); + return; + } + if (request->timestamps) { + (*os) << gresponse.time() << " "; + } + (*os) << gresponse.data(); + } + + int logs_request_to_grpc(const struct lcrc_logs_request *request, LogsRequest *grequest) + { + if (request == nullptr) { + return -1; + } + if (request->id != nullptr) { + grequest->set_id(request->id); + } + if (request->runtime != nullptr) { + grequest->set_runtime(request->runtime); + } + if (request->since != nullptr) { + grequest->set_since(request->since); + } + if (request->until != nullptr) { + grequest->set_until(request->until); + } + grequest->set_timestamps(request->timestamps); + grequest->set_follow(request->follow); + grequest->set_tail(request->tail); + grequest->set_details(request->details); + return 0; + } +}; + +int grpc_containers_client_ops_init(lcrc_connect_ops *ops) +{ + if (ops == nullptr) { + return -1; + } + // implement following interface + ops->container.version = container_func; + ops->container.info = container_func; + ops->container.create = container_func; + ops->container.start = container_func; + ops->container.remote_start = container_func; + ops->container.stop = container_func; + ops->container.restart = container_func; + ops->container.remove = container_func; + ops->container.list = container_func; + ops->container.exec = container_func; + ops->container.remote_exec = container_func; + ops->container.attach = container_func; + ops->container.pause = container_func; + ops->container.resume = container_func; + ops->container.update = container_func; + ops->container.conf = container_func; + ops->container.kill = container_func; + ops->container.stats = container_func; + ops->container.wait = container_func; + ops->container.events = container_func; + ops->container.inspect = container_func; + ops->container.export_rootfs = container_func; + ops->container.copy_from_container = + container_func; + ops->container.copy_to_container = + container_func; + ops->container.top = container_func; + ops->container.rename = container_func; + ops->container.logs = container_func; + + return 0; +} + diff --git a/src/connect/client/grpc/grpc_containers_client.h b/src/connect/client/grpc/grpc_containers_client.h new file mode 100644 index 0000000..0110bc7 --- /dev/null +++ b/src/connect/client/grpc/grpc_containers_client.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container grpc client definition + ******************************************************************************/ +#ifndef __GPRC_CONTAINERS_CLIENT_H +#define __GRPC_CONTAINERS_CLIENT_H + +#include "lcrc_connect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int grpc_containers_client_ops_init(lcrc_connect_ops *ops); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/client/grpc/grpc_images_client.cc b/src/connect/client/grpc/grpc_images_client.cc new file mode 100644 index 0000000..1bba43d --- /dev/null +++ b/src/connect/client/grpc/grpc_images_client.cc @@ -0,0 +1,464 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc container service functions + ******************************************************************************/ +#include "grpc_images_client.h" +#include "securec.h" +#include "images.grpc.pb.h" +#include "api.grpc.pb.h" +#include "utils.h" +#include "client_base.h" + +using namespace images; + +using grpc::Channel; +using grpc::ClientContext; +using grpc::ClientReader; +using grpc::ClientReaderWriter; +using grpc::ClientWriter; +using grpc::Status; +using google::protobuf::Timestamp; + +class ImagesList : public ClientBase { +public: + explicit ImagesList(void *args) + : ClientBase(args) + { + } + ~ImagesList() = default; + + int request_to_grpc(const lcrc_list_images_request *request, ListImagesRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + return 0; + } + + int response_from_grpc(ListImagesResponse *gresponse, lcrc_list_images_response *response) override + { + struct lcrc_image_info *images_list = nullptr; + int i; + int num = gresponse->images_size(); + + if (num <= 0) { + response->images_list = nullptr; + response->images_num = 0; + response->server_errono = gresponse->cc(); + return 0; + } + + response->images_num = 0; + + if ((size_t)num > SIZE_MAX / sizeof(struct lcrc_image_info)) { + ERROR("Too many images"); + response->cc = LCRD_ERR_MEMOUT; + return -1; + } + images_list = (struct lcrc_image_info *)util_common_calloc_s(sizeof(struct lcrc_image_info) * (size_t)num); + if (images_list == nullptr) { + ERROR("out of memory"); + response->cc = LCRD_ERR_MEMOUT; + return -1; + } + + for (i = 0; i < num; i++) { + const Image &image = gresponse->images(i); + if (image.has_target()) { + const char *media_type = + !image.target().media_type().empty() ? image.target().media_type().c_str() : "-"; + images_list[i].type = util_strdup_s(media_type); + const char *digest = !image.target().digest().empty() ? image.target().digest().c_str() : "-"; + images_list[i].digest = util_strdup_s(digest); + images_list[i].size = image.target().size(); + } + if (image.has_created_at()) { + images_list[i].created = image.created_at().seconds(); + images_list[i].created_nanos = image.created_at().nanos(); + } + const char *name = !image.name().empty() ? image.name().c_str() : "-"; + images_list[i].imageref = util_strdup_s(name); + } + + response->images_list = images_list; + response->images_num = (size_t)num; + response->server_errono = gresponse->cc(); + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + Status grpc_call(ClientContext *context, const ListImagesRequest &req, ListImagesResponse *reply) override + { + return stub_->List(context, req, reply); + } +}; + +class ImagesDelete : public ClientBase { +public: + explicit ImagesDelete(void *args) + : ClientBase(args) + { + } + ~ImagesDelete() = default; + + int request_to_grpc(const lcrc_rmi_request *request, DeleteImageRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->image_name != nullptr) { + grequest->set_name(request->image_name); + } + grequest->set_force(request->force); + + return 0; + } + + int response_from_grpc(DeleteImageResponse *gresponse, lcrc_rmi_response *response) override + { + response->server_errono = (uint32_t)gresponse->cc(); + + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const DeleteImageRequest &req) override + { + if (req.name().empty()) { + ERROR("Missing image name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const DeleteImageRequest &req, DeleteImageResponse *reply) override + { + return stub_->Delete(context, req, reply); + } +}; + +class ImagesLoad : public ClientBase { +public: + explicit ImagesLoad(void *args) + : ClientBase(args) + { + } + ~ImagesLoad() = default; + + int request_to_grpc(const lcrc_load_request *request, LoadImageRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->file != nullptr) { + grequest->set_file(request->file); + } + if (request->type != nullptr) { + grequest->set_type(request->type); + } + if (request->tag != nullptr) { + grequest->set_tag(request->tag); + } + + return 0; + } + + int response_from_grpc(LoadImageResponse *gresponse, lcrc_load_response *response) override + { + response->server_errono = (uint32_t)gresponse->cc(); + + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const LoadImageRequest &req) override + { + if (req.file().empty()) { + ERROR("Missing manifest file name in the request"); + return -1; + } + if (req.type().empty()) { + ERROR("Missing image type in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const LoadImageRequest &req, LoadImageResponse *reply) override + { + return stub_->Load(context, req, reply); + } +}; + +class ImagesPull : public ClientBase { +public: + explicit ImagesPull(void *args) + : ClientBase(args) + { + } + ~ImagesPull() = default; + + int request_to_grpc(const lcrc_pull_request *request, runtime::PullImageRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->image_name != nullptr) { + runtime::ImageSpec *image_spec = new (std::nothrow) runtime::ImageSpec; + if (image_spec == nullptr) { + return -1; + } + image_spec->set_image(request->image_name); + grequest->set_allocated_image(image_spec); + } + + return 0; + } + + int response_from_grpc(runtime::PullImageResponse *gresponse, lcrc_pull_response *response) override + { + if (!gresponse->image_ref().empty()) { + response->image_ref = util_strdup_s(gresponse->image_ref().c_str()); + } + + return 0; + } + + int check_parameter(const runtime::PullImageRequest &req) override + { + if (req.image().image().empty()) { + ERROR("Missing image name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const runtime::PullImageRequest &req, + runtime::PullImageResponse *reply) override + { + return stub_->PullImage(context, req, reply); + } +}; + +class ImageInspect : public ClientBase { +public: + explicit ImageInspect(void *args) + : ClientBase(args) + { + } + ~ImageInspect() = default; + + int request_to_grpc(const lcrc_inspect_request *request, InspectImageRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->name != nullptr) { + grequest->set_id(request->name); + } + grequest->set_bformat(request->bformat); + grequest->set_timeout(request->timeout); + + return 0; + } + + int response_from_grpc(InspectImageResponse *gresponse, lcrc_inspect_response *response) override + { + response->server_errono = gresponse->cc(); + if (!gresponse->imagejson().empty()) { + response->json = util_strdup_s(gresponse->imagejson().c_str()); + } + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const InspectImageRequest &req) override + { + if (req.id().empty()) { + ERROR("Missing image name in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const InspectImageRequest &req, InspectImageResponse *reply) override + { + return stub_->Inspect(context, req, reply); + } +}; + +class Login : public + ClientBase { +public: + explicit Login(void *args) : ClientBase(args) {} + ~Login() = default; + + int request_to_grpc(const lcrc_login_request *request, LoginRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->server != nullptr) { + grequest->set_server(request->server); + } + if (request->username != nullptr) { + grequest->set_username(request->username); + } + if (request->password != nullptr) { + grequest->set_password(request->password); + } + if (request->type != nullptr) { + grequest->set_type(request->type); + } + + return 0; + } + + int response_from_grpc(LoginResponse *gresponse, lcrc_login_response *response) override + { + response->server_errono = (uint32_t)gresponse->cc(); + + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const LoginRequest &req) override + { + if (req.username().empty()) { + ERROR("Missing username in the request"); + return -1; + } + if (req.password().empty()) { + ERROR("Missing password in the request"); + return -1; + } + if (req.server().empty()) { + ERROR("Missing server in the request"); + return -1; + } + if (req.type().empty()) { + ERROR("Missing type in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const LoginRequest &req, LoginResponse *reply) override + { + return stub_->Login(context, req, reply); + } +}; + +class Logout : public + ClientBase { +public: + explicit Logout(void *args) : ClientBase(args) {} + ~Logout() = default; + + int request_to_grpc(const lcrc_logout_request *request, LogoutRequest *grequest) override + { + if (request == nullptr) { + return -1; + } + + if (request->server != nullptr) { + grequest->set_server(request->server); + } + if (request->type != nullptr) { + grequest->set_type(request->type); + } + + return 0; + } + + int response_from_grpc(LogoutResponse *gresponse, lcrc_logout_response *response) override + { + response->server_errono = (uint32_t)gresponse->cc(); + + if (!gresponse->errmsg().empty()) { + response->errmsg = util_strdup_s(gresponse->errmsg().c_str()); + } + + return 0; + } + + int check_parameter(const LogoutRequest &req) override + { + if (req.server().empty()) { + ERROR("Missing server in the request"); + return -1; + } + if (req.type().empty()) { + ERROR("Missing type in the request"); + return -1; + } + + return 0; + } + + Status grpc_call(ClientContext *context, const LogoutRequest &req, LogoutResponse *reply) override + { + return stub_->Logout(context, req, reply); + } +}; + +int grpc_images_client_ops_init(lcrc_connect_ops *ops) +{ + if (ops == nullptr) { + return -1; + } + + ops->image.list = container_func; + ops->image.remove = container_func; + ops->image.load = container_func; + ops->image.pull = container_func; + ops->image.inspect = container_func; + ops->image.login = container_func; + ops->image.logout = container_func; + + return 0; +} + diff --git a/src/connect/client/grpc/grpc_images_client.h b/src/connect/client/grpc/grpc_images_client.h new file mode 100644 index 0000000..594524c --- /dev/null +++ b/src/connect/client/grpc/grpc_images_client.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc image client definition + ******************************************************************************/ +#ifndef __GPRC_IMAGES_CLIENT_H +#define __GRPC_IMAGES_CLIENT_H + +#include "lcrc_connect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int grpc_images_client_ops_init(lcrc_connect_ops *ops); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/client/lcrc_connect.c b/src/connect/client/lcrc_connect.c new file mode 100644 index 0000000..bac7aa9 --- /dev/null +++ b/src/connect/client/lcrc_connect.c @@ -0,0 +1,51 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide lcrc connect command definition + ******************************************************************************/ +#include "lcrc_connect.h" + +#include "securec.h" + +#ifdef GRPC_CONNECTOR +#include "grpc_client.h" +#else +#include "rest_client.h" +#endif + +static lcrc_connect_ops g_connect_ops; + +/* connect client ops init */ +int connect_client_ops_init(void) +{ + errno_t ret; + ret = memset_s(&g_connect_ops, sizeof(g_connect_ops), 0, sizeof(g_connect_ops)); + if (ret != EOK) { + return -1; + } +#ifdef GRPC_CONNECTOR + if (grpc_ops_init(&g_connect_ops)) { + return -1; + } +#else + if (rest_ops_init(&g_connect_ops)) { + return -1; + } +#endif + return 0; +} + +/* get connect client ops */ +lcrc_connect_ops *get_connect_client_ops(void) +{ + return &g_connect_ops; +} diff --git a/src/connect/client/lcrc_connect.h b/src/connect/client/lcrc_connect.h new file mode 100644 index 0000000..6dc3ee6 --- /dev/null +++ b/src/connect/client/lcrc_connect.h @@ -0,0 +1,145 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide lcrc connect command definition + ******************************************************************************/ +#ifndef __LCRC_CONNECT_H +#define __LCRC_CONNECT_H + +#include "liblcrc.h" +#include "connect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int(*version)(const struct lcrc_version_request *request, + struct lcrc_version_response *response, void *arg); + + int(*info)(const struct lcrc_info_request *request, + struct lcrc_info_response *response, void *arg); + + int(*create)(const struct lcrc_create_request *request, + struct lcrc_create_response *response, void *arg); + + int(*start)(const struct lcrc_start_request *request, + struct lcrc_start_response *response, void *arg); + + int(*remote_start)(const struct lcrc_start_request *request, + struct lcrc_start_response *response, void *arg); + + int(*stop)(const struct lcrc_stop_request *request, + struct lcrc_stop_response *response, void *arg); + + int(*restart)(const struct lcrc_restart_request *request, + struct lcrc_restart_response *response, void *arg); + + int(*kill)(const struct lcrc_kill_request *request, + struct lcrc_kill_response *response, void *arg); + + int(*remove)(const struct lcrc_delete_request *request, + struct lcrc_delete_response *response, void *arg); + + int(*pause)(const struct lcrc_pause_request *request, + struct lcrc_pause_response *response, void *arg); + + int(*resume)(const struct lcrc_resume_request *request, + struct lcrc_resume_response *response, void *arg); + + int(*list)(const struct lcrc_list_request *request, + struct lcrc_list_response *response, void *arg); + + int(*inspect)(const struct lcrc_inspect_request *request, + struct lcrc_inspect_response *response, void *arg); + + int(*stats)(const struct lcrc_stats_request *request, + struct lcrc_stats_response *response, void *arg); + + int(*events)(const struct lcrc_events_request *request, + struct lcrc_events_response *response, void *arg); + + int(*copy_from_container)(const struct lcrc_copy_from_container_request *request, + struct lcrc_copy_from_container_response *response, void *arg); + + int(*copy_to_container)(const struct lcrc_copy_to_container_request *request, + struct lcrc_copy_to_container_response *response, void *arg); + + int(*exec)(const struct lcrc_exec_request *request, + struct lcrc_exec_response *response, void *arg); + + int(*remote_exec)(const struct lcrc_exec_request *request, + struct lcrc_exec_response *response, void *arg); + + int(*update)(const struct lcrc_update_request *request, + struct lcrc_update_response *response, void *arg); + + int(*conf)(const struct lcrc_container_conf_request *request, + struct lcrc_container_conf_response *response, void *arg); + + int(*attach)(const struct lcrc_attach_request *request, + struct lcrc_attach_response *response, void *arg); + + int(*wait)(const struct lcrc_wait_request *request, + struct lcrc_wait_response *response, void *arg); + + int(*export_rootfs)(const struct lcrc_export_request *request, + struct lcrc_export_response *response, void *arg); + int(*top)(const struct lcrc_top_request *request, + struct lcrc_top_response *response, void *arg); + int(*rename)(const struct lcrc_rename_request *request, + struct lcrc_rename_response *response, void *arg); + + int(*logs)(const struct lcrc_logs_request *request, struct lcrc_logs_response *response, void *arg); +} container_ops; + +typedef struct { + int(*list)(const struct lcrc_list_images_request *request, + struct lcrc_list_images_response *response, void *arg); + + int(*remove)(const struct lcrc_rmi_request *request, + struct lcrc_rmi_response *response, void *arg); + + int(*load)(const struct lcrc_load_request *request, + struct lcrc_load_response *response, void *arg); + + int(*pull)(const struct lcrc_pull_request *request, + struct lcrc_pull_response *response, void *arg); + + int(*inspect)(const struct lcrc_inspect_request *request, + struct lcrc_inspect_response *response, void *arg); + int(*login)(const struct lcrc_login_request *request, + struct lcrc_login_response *response, void *arg); + int(*logout)(const struct lcrc_logout_request *request, + struct lcrc_logout_response *response, void *arg); +} image_ops; + +typedef struct { + int(*check)(const struct lcrc_health_check_request *request, + struct lcrc_health_check_response *response, void *arg); +} health_ops; + +typedef struct { + container_ops container; + image_ops image; + health_ops health; +} lcrc_connect_ops; + +int connect_client_ops_init(void); + +lcrc_connect_ops *get_connect_client_ops(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __LCRC_CONNECT_H */ diff --git a/src/connect/client/rest/CMakeLists.txt b/src/connect/client/rest/CMakeLists.txt new file mode 100644 index 0000000..d2f228b --- /dev/null +++ b/src/connect/client/rest/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_client_rest_srcs) + +set(CLIENT_REST_SRCS + ${local_client_rest_srcs} + PARENT_SCOPE + ) diff --git a/src/connect/client/rest/rest_client.c b/src/connect/client/rest/rest_client.c new file mode 100644 index 0000000..1f72d45 --- /dev/null +++ b/src/connect/client/rest/rest_client.c @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide rest client + ******************************************************************************/ +#include "rest_client.h" +#include "rest_containers_client.h" +#include "rest_images_client.h" + +int rest_ops_init(lcrc_connect_ops *ops) +{ + if (ops == NULL) { + return -1; + } + + /* Add all operator api at here */ + if (rest_containers_client_ops_init(ops) != 0) { + return -1; + } + if (rest_images_client_ops_init(ops) != 0) { + return -1; + } + + return 0; +} diff --git a/src/connect/client/rest/rest_client.h b/src/connect/client/rest/rest_client.h new file mode 100644 index 0000000..9a8476d --- /dev/null +++ b/src/connect/client/rest/rest_client.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful client definition + ******************************************************************************/ +#ifndef __REST_CLIENT_H +#define __REST_CLIENT_H + +#include "lcrc_connect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int rest_ops_init(lcrc_connect_ops *ops); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/client/rest/rest_containers_client.c b/src/connect/client/rest/rest_containers_client.c new file mode 100644 index 0000000..1cc9ab1 --- /dev/null +++ b/src/connect/client/rest/rest_containers_client.c @@ -0,0 +1,1885 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful functions + ******************************************************************************/ +#include +#include "error.h" + +#include "log.h" +#include "securec.h" +#include "lcrc_connect.h" +#include "container.rest.h" +#include "pack_config.h" +#include "rest_common.h" +#include "rest_containers_client.h" + +/* create request to rest */ +static int create_request_to_rest(const struct lcrc_create_request *lc_request, char **body, size_t *body_len) +{ + container_create_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_create_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + ret = generate_hostconfig(lc_request->hostconfig, &crequest->hostconfig); + if (ret != 0) { + ERROR("Failed to pack host config"); + ret = EINVALIDARGS; + goto out; + } + + ret = generate_container_config(lc_request->config, &crequest->customconfig); + if (ret != 0) { + ERROR("Failed to pack custom config"); + ret = EINVALIDARGS; + goto out; + } + + if (lc_request->name != NULL) { + crequest->id = util_strdup_s(lc_request->name); + } + if (lc_request->image != NULL) { + crequest->image = util_strdup_s(lc_request->image); + } + if (lc_request->rootfs != NULL) { + crequest->rootfs = util_strdup_s(lc_request->rootfs); + } + if (lc_request->runtime != NULL) { + crequest->runtime = util_strdup_s(lc_request->runtime); + } + + *body = container_create_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate create request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_create_request(crequest); + return ret; +} + +/* start request to rest */ +static int start_request_to_rest(const struct lcrc_start_request *ls_request, char **body, size_t *body_len) +{ + container_start_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_start_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (ls_request->name != NULL) { + crequest->id = util_strdup_s(ls_request->name); + } + if (ls_request->stdout != NULL) { + crequest->stdout = util_strdup_s(ls_request->stdout); + } + if (ls_request->stdin != NULL) { + crequest->stdin = util_strdup_s(ls_request->stdin); + } + if (ls_request->stderr != NULL) { + crequest->stderr = util_strdup_s(ls_request->stderr); + } + crequest->attach_stdin = ls_request->attach_stdin; + crequest->attach_stdout = ls_request->attach_stdout; + crequest->attach_stderr = ls_request->attach_stderr; + + *body = container_start_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate start request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_start_request(crequest); + return ret; +} + +/* list request to rest */ +static int list_request_to_rest(const struct lcrc_list_request *ll_request, char **body, size_t *body_len) +{ + container_list_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + size_t i, len; + + crequest = util_common_calloc_s(sizeof(container_list_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + crequest->all = ll_request->all; + + if (ll_request->filters == NULL || ll_request->filters->len == 0) { + goto pack_json; + } + + crequest->filters = util_common_calloc_s(sizeof(defs_filters)); + if (crequest->filters == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + len = ll_request->filters->len; + if (len > SIZE_MAX / sizeof(char *)) { + ERROR("Too many filters"); + ret = -1; + goto out; + } + crequest->filters->keys = (char **)util_common_calloc_s(len * sizeof(char *)); + if (crequest->filters->keys == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + crequest->filters->values = (json_map_string_bool **)util_common_calloc_s(len * sizeof(json_map_string_bool *)); + if (crequest->filters->values == NULL) { + ERROR("Out of memory"); + free(crequest->filters->keys); + crequest->filters->keys = NULL; + ret = -1; + goto out; + } + + for (i = 0; i < ll_request->filters->len; i++) { + crequest->filters->values[crequest->filters->len] = util_common_calloc_s(sizeof(json_map_string_bool)); + if (crequest->filters->values[crequest->filters->len] == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + if (append_json_map_string_bool(crequest->filters->values[crequest->filters->len], + ll_request->filters->values[i], true)) { + free(crequest->filters->values[crequest->filters->len]); + crequest->filters->values[crequest->filters->len] = NULL; + ERROR("Append failed"); + ret = -1; + goto out; + } + crequest->filters->keys[crequest->filters->len] = util_strdup_s(ll_request->filters->keys[i]); + crequest->filters->len++; + } + +pack_json: + *body = container_list_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate list request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_list_request(crequest); + return ret; +} + +/* attach request to rest */ +static int attach_request_to_rest(const struct lcrc_attach_request *la_request, char **body, size_t *body_len) +{ + container_attach_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_attach_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (la_request->name != NULL) { + crequest->container_id = util_strdup_s(la_request->name); + } + if (la_request->stdout != NULL) { + crequest->stdout = util_strdup_s(la_request->stdout); + } + if (la_request->stdin != NULL) { + crequest->stdin = util_strdup_s(la_request->stdin); + } + if (la_request->stderr != NULL) { + crequest->stderr = util_strdup_s(la_request->stderr); + } + *body = container_attach_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate attach request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_attach_request(crequest); + return ret; +} + +/* resume request to rest */ +static int resume_request_to_rest(const struct lcrc_resume_request *lr_request, char **body, size_t *body_len) +{ + container_resume_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_resume_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (lr_request->name != NULL) { + crequest->id = util_strdup_s(lr_request->name); + } + *body = container_resume_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate resume request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_resume_request(crequest); + return ret; +} + +/* container conf request to rest */ +static int container_conf_request_to_rest(const struct lcrc_container_conf_request *lconf_request, char **body, + size_t *body_len) +{ + container_conf_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_conf_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (lconf_request->name != NULL) { + crequest->container_id = util_strdup_s(lconf_request->name); + } + *body = container_conf_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate conf request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_conf_request(crequest); + return ret; +} + +/* wait request to rest */ +static int wait_request_to_rest(const struct lcrc_wait_request *lw_request, char **body, size_t *body_len) +{ + container_wait_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_wait_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (lw_request->id != NULL) { + crequest->id = util_strdup_s(lw_request->id); + } + crequest->condition = lw_request->condition; + + *body = container_wait_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate wait request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_wait_request(crequest); + return ret; +} + +/* unpack create response */ +static int unpack_create_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_create_response *response = arg; + container_create_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_create_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid create response:%s", err); + ret = -1; + goto out; + } + response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + response->errmsg = util_strdup_s(cresponse->errmsg); + } + if (cresponse->id != NULL) { + response->id = util_strdup_s(cresponse->id); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_create_response(cresponse); + return ret; +} + +/* unpack start response */ +static int unpack_start_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_start_response *start_response = arg; + container_start_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_start_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid start response:%s", err); + ret = -1; + goto out; + } + start_response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + start_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_start_response(cresponse); + return ret; +} + +static int unpack_container_info_for_list_response(container_list_response *cresponse, + struct lcrc_list_response *response) +{ + size_t num = 0; + size_t i = 0; + struct lcrc_container_summary_info **summary_info = NULL; + + if (cresponse == NULL || response == NULL) { + return -1; + } + + num = cresponse->containers_len; + if (num == 0) { + return 0; + } + if (num > SIZE_MAX / sizeof(struct lcrc_container_summary_info *)) { + ERROR("Too many container summaries"); + return -1; + } + summary_info = (struct lcrc_container_summary_info **)util_common_calloc_s( + sizeof(struct lcrc_container_summary_info *) * num); + if (summary_info == NULL) { + ERROR("out of memory"); + return -1; + } + response->container_num = num; + response->container_summary = summary_info; + for (i = 0; i < num; i++) { + summary_info[i] = + (struct lcrc_container_summary_info *)util_common_calloc_s(sizeof(struct lcrc_container_summary_info)); + if (summary_info[i] == NULL) { + ERROR("Out of memory"); + return -1; + } + summary_info[i]->id = cresponse->containers[i]->id ? util_strdup_s(cresponse->containers[i]->id) + : util_strdup_s("-"); + summary_info[i]->name = cresponse->containers[i]->name ? util_strdup_s(cresponse->containers[i]->name) + : util_strdup_s("-"); + summary_info[i]->runtime = cresponse->containers[i]->runtime ? util_strdup_s(cresponse->containers[i]->runtime) + : util_strdup_s("-"); + summary_info[i]->has_pid = cresponse->containers[i]->pid != 0; + summary_info[i]->pid = cresponse->containers[i]->pid; + summary_info[i]->status = cresponse->containers[i]->status; + summary_info[i]->image = cresponse->containers[i]->image ? util_strdup_s(cresponse->containers[i]->image) + : util_strdup_s("-"); + summary_info[i]->command = cresponse->containers[i]->command ? util_strdup_s(cresponse->containers[i]->command) + : util_strdup_s("-"); + summary_info[i]->startat = cresponse->containers[i]->startat ? util_strdup_s(cresponse->containers[i]->startat) + : util_strdup_s("-"); + summary_info[i]->finishat = cresponse->containers[i]->finishat ? + util_strdup_s(cresponse->containers[i]->finishat) : util_strdup_s("-"); + summary_info[i]->exit_code = cresponse->containers[i]->exit_code; + summary_info[i]->restart_count = (unsigned int)cresponse->containers[i]->restartcount; + } + + return 0; +} +/* unpack list response */ +static int unpack_list_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_list_response *response = arg; + container_list_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_list_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid list response:%s", err); + ret = -1; + goto out; + } + response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + if (unpack_container_info_for_list_response(cresponse, response)) { + ret = -1; + goto out; + } + +out: + free(err); + free_container_list_response(cresponse); + return ret; +} + +/* unpack attach response */ +static int unpack_attach_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_attach_response *attach_response = arg; + container_attach_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_attach_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid attach response:%s", err); + ret = -1; + goto out; + } + attach_response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + attach_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_attach_response(cresponse); + return ret; +} + +/* unpack resume response */ +static int unpack_resume_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_resume_response *resume_response = arg; + container_resume_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_resume_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid resume response:%s", err); + ret = -1; + goto out; + } + resume_response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + resume_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_resume_response(cresponse); + return ret; +} + +/* unpack container conf response */ +static int unpack_container_conf_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_container_conf_response *response = arg; + container_conf_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_conf_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid create response:%s", err); + ret = -1; + goto out; + } + response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + response->errmsg = util_strdup_s(cresponse->errmsg); + } + if (cresponse->container_logpath != NULL) { + response->container_logpath = util_strdup_s(cresponse->container_logpath); + } + response->container_logrotate = cresponse->container_logrotate; + if (cresponse->container_logsize != NULL) { + response->container_logsize = util_strdup_s(cresponse->container_logsize); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_conf_response(cresponse); + return ret; +} + +/* unpack wait response */ +static int unpack_wait_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_wait_response *response = arg; + container_wait_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_wait_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid create response:%s", err); + ret = -1; + goto out; + } + response->server_errono = cresponse->cc; + response->exit_code = (int)cresponse->exit_code; + if (cresponse->errmsg != NULL) { + response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free_container_wait_response(cresponse); + free(err); + return ret; +} + +/* rest container create */ +static int rest_container_create(const struct lcrc_create_request *lc_request, + struct lcrc_create_response *lc_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *c_output = NULL; + + ret = create_request_to_rest(lc_request, &body, &len); + if (ret != 0) { + lc_response->cc = LCRD_ERR_INPUT; + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceCreate, body, len, &c_output); + if (ret != 0) { + lc_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lc_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(c_output, unpack_create_response, (void *)lc_response); + if (ret != 0) { + goto out; + } +out: + if (c_output != NULL) { + buffer_free(c_output); + } + put_body(body); + return ret; +} + +/* rest container start */ +static int rest_container_start(const struct lcrc_start_request *ls_request, struct lcrc_start_response *ls_response, + void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *s_output = NULL; + + ret = start_request_to_rest(ls_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceStart, body, len, &s_output); + if (ret != 0) { + ls_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + ls_response->cc = LCRD_ERR_EXEC; + ls_response->server_errono = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(s_output, unpack_start_response, (void *)ls_response); + if (ret != 0) { + goto out; + } +out: + if (s_output != NULL) { + buffer_free(s_output); + } + put_body(body); + return ret; +} + +/* rest container attach */ +static int rest_container_attach(const struct lcrc_attach_request *la_request, + struct lcrc_attach_response *la_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *a_output = NULL; + + ret = attach_request_to_rest(la_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceAttach, body, len, &a_output); + if (ret != 0) { + la_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + la_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(a_output, unpack_attach_response, (void *)la_response); + if (ret != 0) { + goto out; + } +out: + if (a_output != NULL) { + buffer_free(a_output); + } + put_body(body); + return ret; +} + +/* rest container list */ +static int rest_container_list(const struct lcrc_list_request *ll_request, + struct lcrc_list_response *ll_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *l_output = NULL; + + ret = list_request_to_rest(ll_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceList, body, len, &l_output); + if (ret != 0) { + ll_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + ll_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(l_output, unpack_list_response, (void *)ll_response); + if (ret != 0) { + goto out; + } +out: + if (l_output != NULL) { + buffer_free(l_output); + } + put_body(body); + return ret; +} + +/* rest container resume */ +static int rest_container_resume(const struct lcrc_resume_request *lr_request, + struct lcrc_resume_response *lr_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *r_output = NULL; + + ret = resume_request_to_rest(lr_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceResume, body, len, &r_output); + if (ret != 0) { + lr_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lr_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(r_output, unpack_resume_response, (void *)lr_response); + if (ret != 0) { + goto out; + } +out: + if (r_output != NULL) { + buffer_free(r_output); + } + put_body(body); + return ret; +} + +/* rest container conf */ +static int rest_container_conf(const struct lcrc_container_conf_request *lc_request, + struct lcrc_container_conf_response *lc_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *c_output = NULL; + + ret = container_conf_request_to_rest(lc_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceConf, body, len, &c_output); + if (ret != 0) { + lc_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lc_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(c_output, unpack_container_conf_response, (void *)lc_response); + if (ret != 0) { + goto out; + } +out: + if (c_output != NULL) { + buffer_free(c_output); + } + put_body(body); + return ret; +} + +/* rest container wait */ +static int rest_container_wait(const struct lcrc_wait_request *lw_request, + struct lcrc_wait_response *lw_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *w_output = NULL; + + ret = wait_request_to_rest(lw_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceWait, body, len, &w_output); + if (ret != 0) { + lw_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lw_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(w_output, unpack_wait_response, (void *)lw_response); + if (ret != 0) { + goto out; + } +out: + if (w_output != NULL) { + buffer_free(w_output); + } + put_body(body); + return ret; +} + +/* stop request to rest */ +static int stop_request_to_rest(const struct lcrc_stop_request *ls_request, char **body, size_t *body_len) +{ + container_stop_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_stop_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (ls_request->name != NULL) { + crequest->id = util_strdup_s(ls_request->name); + } + crequest->force = ls_request->force; + crequest->timeout = ls_request->timeout; + + *body = container_stop_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate stop request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_stop_request(crequest); + return ret; +} + +/* unpack stop response */ +static int unpack_stop_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_stop_response *stop_response = arg; + container_stop_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_stop_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid stop response:%s", err); + ret = -1; + goto out; + } + stop_response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + stop_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_stop_response(cresponse); + return ret; +} + +/* rest container stop */ +static int rest_container_stop(const struct lcrc_stop_request *ls_request, + struct lcrc_stop_response *ls_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = stop_request_to_rest(ls_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceStop, body, len, &output); + if (ret != 0) { + ls_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + ls_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_stop_response, (void *)ls_response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* restart request to rest */ +static int restart_request_to_rest(const struct lcrc_restart_request *lr_request, char **body, size_t *body_len) +{ + container_restart_request *creq = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + creq = util_common_calloc_s(sizeof(container_restart_request)); + if (creq == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (lr_request->name != NULL) { + creq->id = util_strdup_s(lr_request->name); + } + creq->timeout = (int32_t)lr_request->timeout; + + *body = container_restart_request_generate_json(creq, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate restart request json: %s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_restart_request(creq); + return ret; +} + +/* unpack restart response */ +static int unpack_restart_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_restart_response *response = arg; + container_restart_response *cres = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cres = container_restart_response_parse_data(message->body, NULL, &err); + if (cres == NULL) { + ERROR("Invalid restart response: %s", err); + ret = -1; + goto out; + } + response->server_errono = cres->cc; + if (cres->errmsg != NULL) { + response->errmsg = util_strdup_s(cres->errmsg); + } + ret = (cres->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_restart_response(cres); + return ret; +} + +/* rest container restart */ +static int rest_container_restart(const struct lcrc_restart_request *lr_request, + struct lcrc_restart_response *lr_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = restart_request_to_rest(lr_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceRestart, body, len, &output); + if (ret != 0) { + lr_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lr_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_restart_response, (void *)lr_response); + +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* update request to rest */ +static int update_request_to_rest(const struct lcrc_update_request *lu_request, char **body, size_t *body_len) +{ + container_update_request *crequest = NULL; + lcrc_host_config_t srcconfig; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + char *srcconfigjson = NULL; + int ret = 0; + + if (memset_s(&srcconfig, sizeof(srcconfig), 0, sizeof(srcconfig)) != EOK) { + ERROR("Failed to set memory"); + return -1; + } + + crequest = util_common_calloc_s(sizeof(container_update_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (lu_request->updateconfig != NULL) { + srcconfig.restart_policy = lu_request->updateconfig->restart_policy; + srcconfig.cr = lu_request->updateconfig->cr; + } + ret = generate_hostconfig(&srcconfig, &srcconfigjson); + if (ret != 0) { + ERROR("Failed to generate hostconfig json"); + ret = -1; + goto out; + } + crequest->host_config = srcconfigjson; + + if (lu_request->name != NULL) { + crequest->name = util_strdup_s(lu_request->name); + } + + *body = container_update_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate update request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_update_request(crequest); + return ret; +} + +/* unpack update response */ +static int unpack_update_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_update_response *update_response = arg; + container_update_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_update_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid update response:%s", err); + ret = -1; + goto out; + } + update_response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + update_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_update_response(cresponse); + return ret; +} + +/* rest container update */ +static int rest_container_update(const struct lcrc_update_request *lu_request, + struct lcrc_update_response *lu_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = update_request_to_rest(lu_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceUpdate, body, len, &output); + if (ret != 0) { + lu_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lu_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_update_response, (void *)lu_response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* version request to rest */ +static int version_request_to_rest(const struct lcrc_version_request *lv_request, char **body, size_t *body_len) +{ + container_version_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_version_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + *body = container_version_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate version request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_version_request(crequest); + return ret; +} + +/* unpack version response */ +static int unpack_version_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_version_response *version_response = arg; + container_version_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_version_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid version response:%s", err); + ret = -1; + goto out; + } + version_response->server_errono = cresponse->cc; + if (cresponse->version != NULL) { + version_response->version = util_strdup_s(cresponse->version); + } + if (cresponse->git_commit != NULL) { + version_response->git_commit = util_strdup_s(cresponse->git_commit); + } + if (cresponse->build_time != NULL) { + version_response->build_time = util_strdup_s(cresponse->build_time); + } + if (cresponse->root_path != NULL) { + version_response->root_path = util_strdup_s(cresponse->root_path); + } + if (cresponse->errmsg != NULL) { + version_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_version_response(cresponse); + return ret; +} + +/* rest container version */ +static int rest_container_version(const struct lcrc_version_request *lv_request, + struct lcrc_version_response *lv_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = version_request_to_rest(lv_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceVersion, body, len, &output); + if (ret != 0) { + lv_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lv_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_version_response, (void *)lv_response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* pause request to rest */ +static int pause_request_to_rest(const struct lcrc_pause_request *lp_request, char **body, size_t *body_len) +{ + container_pause_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_pause_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (lp_request->name != NULL) { + crequest->id = util_strdup_s(lp_request->name); + } + + *body = container_pause_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate pause request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_pause_request(crequest); + return ret; +} + +/* unpack pause response */ +static int unpack_pause_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_pause_response *pause_response = arg; + container_pause_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_pause_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid pause response:%s", err); + ret = -1; + goto out; + } + pause_response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + pause_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_pause_response(cresponse); + return ret; +} + +/* rest container pause */ +static int rest_container_pause(const struct lcrc_pause_request *lp_request, + struct lcrc_pause_response *lp_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = pause_request_to_rest(lp_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServicePause, body, len, &output); + if (ret != 0) { + lp_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lp_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_pause_response, (void *)lp_response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* kill request to rest */ +static int kill_request_to_rest(const struct lcrc_kill_request *lk_request, char **body, size_t *body_len) +{ + container_kill_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_kill_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (lk_request->name != NULL) { + crequest->id = util_strdup_s(lk_request->name); + } + crequest->signal = lk_request->signal; + + *body = container_kill_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate kill request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_kill_request(crequest); + return ret; +} + +/* unpack kill response */ +static int unpack_kill_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_kill_response *kill_response = arg; + container_kill_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_kill_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid kill response:%s", err); + ret = -1; + goto out; + } + kill_response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + kill_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_kill_response(cresponse); + return ret; +} + +/* rest container kill */ +static int rest_container_kill(const struct lcrc_kill_request *lk_request, + struct lcrc_kill_response *lk_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = kill_request_to_rest(lk_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceKill, body, len, &output); + if (ret != 0) { + lk_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + lk_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_kill_response, (void *)lk_response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* remove request to rest */ +static int remove_request_to_rest(const struct lcrc_delete_request *ld_request, char **body, size_t *body_len) +{ + container_delete_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_delete_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (ld_request->name != NULL) { + crequest->id = util_strdup_s(ld_request->name); + } + crequest->force = ld_request->force; + + *body = container_delete_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate remove request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_delete_request(crequest); + return ret; +} + +/* unpack remove response */ +static int unpack_remove_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_delete_response *delete_response = arg; + container_delete_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_delete_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid remove response:%s", err); + ret = -1; + goto out; + } + delete_response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + delete_response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_delete_response(cresponse); + return ret; +} + +/* rest container remove */ +static int rest_container_remove(const struct lcrc_delete_request *ld_request, + struct lcrc_delete_response *ld_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *r_output = NULL; + + ret = remove_request_to_rest(ld_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceRemove, body, len, &r_output); + if (ret != 0) { + ld_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + ld_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(r_output, unpack_remove_response, (void *)ld_response); + if (ret != 0) { + goto out; + } +out: + if (r_output != NULL) { + buffer_free(r_output); + } + put_body(body); + return ret; +} + +/* inspect request to rest */ +static int inspect_request_to_rest(const struct lcrc_inspect_request *li_request, char **body, size_t *body_len) +{ + container_inspect_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_inspect_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (li_request->name != NULL) { + crequest->id = util_strdup_s(li_request->name); + } + + crequest->bformat = li_request->bformat; + crequest->timeout = li_request->timeout; + + *body = container_inspect_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate inspect request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_inspect_request(crequest); + return ret; +} + +/* unpack inspect response */ +static int unpack_inspect_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_inspect_response *response = arg; + container_inspect_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_inspect_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid inspect response:%s", err); + ret = -1; + goto out; + } + response->server_errono = cresponse->cc; + if (cresponse->container_json != NULL) { + response->json = util_strdup_s(cresponse->container_json); + } + if (cresponse->errmsg != NULL) { + response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_inspect_response(cresponse); + return ret; +} + +/* rest container inspect */ +static int rest_container_inspect(const struct lcrc_inspect_request *li_request, + struct lcrc_inspect_response *li_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = inspect_request_to_rest(li_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceInspect, body, len, &output); + if (ret != 0) { + li_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + li_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_inspect_response, (void *)li_response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* exec request to rest */ +static int exec_request_to_rest(const struct lcrc_exec_request *le_request, char **body, size_t *body_len) +{ + container_exec_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(container_exec_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + crequest->tty = le_request->tty; + crequest->attach_stdin = le_request->attach_stdin; + crequest->attach_stdout = le_request->attach_stdout; + crequest->attach_stderr = le_request->attach_stderr; + + if (le_request->name != NULL) { + crequest->container_id = util_strdup_s(le_request->name); + } + if (le_request->stdout != NULL) { + crequest->stdout = util_strdup_s(le_request->stdout); + } + if (le_request->stdin != NULL) { + crequest->stdin = util_strdup_s(le_request->stdin); + } + if (le_request->stderr != NULL) { + crequest->stderr = util_strdup_s(le_request->stderr); + } + + int i = 0; + if (le_request->argc > 0) { + if ((size_t)le_request->argc > SIZE_MAX / sizeof(char *)) { + ERROR("Too many arguments!"); + ret = -1; + goto out; + } + crequest->argv = (char **)util_common_calloc_s(sizeof(char *) * (size_t)le_request->argc); + if (crequest->argv == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + for (i = 0; i < le_request->argc; i++) { + crequest->argv[i] = util_strdup_s(le_request->argv[i]); + } + crequest->argv_len = (size_t)le_request->argc; + } + if (le_request->env_len > 0) { + if ((size_t)le_request->env_len > SIZE_MAX / sizeof(char *)) { + ERROR("Too many environmental variables!"); + ret = -1; + goto out; + } + crequest->env = (char **)util_common_calloc_s(sizeof(char *) * (size_t)le_request->env_len); + if (crequest->env == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + for (i = 0; i < le_request->env_len; i++) { + crequest->env[i] = util_strdup_s(le_request->env[i]); + } + crequest->env_len = (size_t)le_request->env_len; + } + + *body = container_exec_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate exec request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_container_exec_request(crequest); + return ret; +} + +/* unpack exec response */ +static int unpack_exec_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_exec_response *response = arg; + container_exec_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = container_exec_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid exec response:%s", err); + ret = -1; + goto out; + } + response->server_errono = cresponse->cc; + response->pid = cresponse->pid; + response->exit_code = cresponse->exit_code; + if (cresponse->errmsg != NULL) { + response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_container_exec_response(cresponse); + return ret; +} + +/* rest container exec */ +static int rest_container_exec(const struct lcrc_exec_request *le_request, + struct lcrc_exec_response *le_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = exec_request_to_rest(le_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ContainerServiceExec, body, len, &output); + if (ret != 0) { + le_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + le_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_exec_response, (void *)le_response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* rest containers client ops init */ +int rest_containers_client_ops_init(lcrc_connect_ops *ops) +{ + if (ops == NULL) { + return -1; + } + + ops->container.create = &rest_container_create; + ops->container.start = &rest_container_start; + ops->container.stop = &rest_container_stop; + ops->container.restart = &rest_container_restart; + ops->container.remove = &rest_container_remove; + ops->container.inspect = &rest_container_inspect; + ops->container.list = &rest_container_list; + ops->container.exec = &rest_container_exec; + ops->container.pause = &rest_container_pause; + ops->container.attach = &rest_container_attach; + ops->container.resume = &rest_container_resume; + ops->container.update = &rest_container_update; + ops->container.conf = &rest_container_conf; + ops->container.kill = &rest_container_kill; + ops->container.version = &rest_container_version; + ops->container.wait = &rest_container_wait; + + return 0; +} diff --git a/src/connect/client/rest/rest_containers_client.h b/src/connect/client/rest/rest_containers_client.h new file mode 100644 index 0000000..3de2503 --- /dev/null +++ b/src/connect/client/rest/rest_containers_client.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide rest containers client definition + ******************************************************************************/ +#ifndef __REST_CONTAINERS_CLIENT_H +#define __REST_CONTAINERS_CLIENT_H + +#include "lcrc_connect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int rest_containers_client_ops_init(lcrc_connect_ops *ops); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/client/rest/rest_images_client.c b/src/connect/client/rest/rest_images_client.c new file mode 100644 index 0000000..c6725fb --- /dev/null +++ b/src/connect/client/rest/rest_images_client.c @@ -0,0 +1,476 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide image restful client functions + ******************************************************************************/ +#include +#include "error.h" +#include + +#include "log.h" +#include "securec.h" +#include "lcrc_connect.h" +#include "image.rest.h" +#include "rest_common.h" +#include "rest_images_client.h" + +/* image load request to rest */ +static int image_load_request_to_rest(const struct lcrc_load_request *request, char **body, size_t *body_len) +{ + image_load_image_request *crequest = NULL; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(image_load_image_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (request->file != NULL) { + crequest->file = util_strdup_s(request->file); + } + if (request->type != NULL) { + crequest->type = util_strdup_s(request->type); + } + *body = image_load_image_request_generate_json(crequest, NULL, &err); + if (*body == NULL) { + ERROR("Failed to generate image load request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_image_load_image_request(crequest); + return ret; +} + +/* image list request to rest */ +static int image_list_request_to_rest(const struct lcrc_list_images_request *request, char **body, size_t *body_len) +{ + image_list_images_request *crequest = NULL; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(image_list_images_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + *body = image_list_images_request_generate_json(crequest, NULL, &err); + if (*body == NULL) { + ERROR("Failed to generate image list request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_image_list_images_request(crequest); + return ret; +} + +/* image delete request to rest */ +static int image_delete_request_to_rest(const struct lcrc_rmi_request *request, char **body, size_t *body_len) +{ + image_delete_image_request *crequest = NULL; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(image_delete_image_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (request->image_name) { + crequest->image_name = util_strdup_s(request->image_name); + } + crequest->force = request->force; + + *body = image_delete_image_request_generate_json(crequest, NULL, &err); + if (*body == NULL) { + ERROR("Failed to generate image delete request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_image_delete_image_request(crequest); + return ret; +} + +static int unpack_image_info_to_list_response(image_list_images_response *cresponse, + struct lcrc_list_images_response *response) +{ + size_t num = 0; + struct lcrc_image_info *image_info = NULL; + + if (cresponse == NULL || response == NULL) { + return -1; + } + + num = cresponse->images_len; + if (num > 0 && (num < (SIZE_MAX / sizeof(struct lcrc_image_info)))) { + size_t i; + image_info = (struct lcrc_image_info *)util_common_calloc_s(sizeof(struct lcrc_image_info) * num); + if (image_info == NULL) { + ERROR("out of memory"); + return -1; + } + response->images_num = num; + response->images_list = image_info; + for (i = 0; i < num; i++) { + if (cresponse->images[i]->target != NULL) { + image_info[i].type = cresponse->images[i]->target->media_type ? + util_strdup_s(cresponse->images[i]->target->media_type) : util_strdup_s("-"); + image_info[i].digest = cresponse->images[i]->target->digest ? + util_strdup_s(cresponse->images[i]->target->digest) : util_strdup_s("-"); + image_info[i].size = cresponse->images[i]->target->size; + } + if (cresponse->images[i]->created_at != NULL) { + image_info[i].created = cresponse->images[i]->created_at->seconds; + image_info[i].created_nanos = cresponse->images[i]->created_at->nanos; + } + image_info[i].imageref = cresponse->images[i]->name ? + util_strdup_s(cresponse->images[i]->name) : util_strdup_s("-"); + } + } + + return 0; +} +/* unpack image list response */ +static int unpack_image_list_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_list_images_response *response = arg; + image_list_images_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = image_list_images_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid images list response:%s", err); + ret = -1; + goto out; + } + response->server_errono = cresponse->cc; + if (cresponse->errmsg != NULL) { + response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + + if (unpack_image_info_to_list_response(cresponse, response)) { + ret = -1; + goto out; + } + +out: + free(err); + free_image_list_images_response(cresponse); + return ret; +} + +/* unpack image load response */ +static int unpack_image_load_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_load_response *c_load_response = arg; + image_load_image_response *load_response = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + load_response = image_load_image_response_parse_data(message->body, NULL, &err); + if (load_response == NULL) { + ERROR("Invalid load image response:%s", err); + ret = -1; + goto out; + } + c_load_response->server_errono = load_response->cc; + if (load_response->errmsg != NULL) { + c_load_response->errmsg = util_strdup_s(load_response->errmsg); + } + ret = (load_response->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_image_load_image_response(load_response); + return ret; +} + +/* rest image load */ +static int rest_image_load(const struct lcrc_load_request *request, struct lcrc_load_response *response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = image_load_request_to_rest(request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ImagesServiceLoad, body, len, &output); + if (ret != 0) { + response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_image_load_response, (void *)response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* unpack image delete response */ +static int unpack_image_delete_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_rmi_response *c_rmi_response = arg; + image_delete_image_response *delete_response = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + delete_response = image_delete_image_response_parse_data(message->body, NULL, &err); + if (delete_response == NULL) { + ERROR("Invalid delete image response:%s", err); + ret = -1; + goto out; + } + c_rmi_response->server_errono = delete_response->cc; + if (delete_response->errmsg != NULL) { + c_rmi_response->errmsg = util_strdup_s(delete_response->errmsg); + } + ret = (delete_response->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_image_delete_image_response(delete_response); + return ret; +} + +/* rest image list */ +static int rest_image_list(const struct lcrc_list_images_request *request, struct lcrc_list_images_response *response, + void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = image_list_request_to_rest(request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ImagesServiceList, body, len, &output); + if (ret != 0) { + response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_image_list_response, (void *)response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* rest image remove */ +static int rest_image_remove(const struct lcrc_rmi_request *request, struct lcrc_rmi_response *response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len = 0; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = image_delete_request_to_rest(request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ImagesServiceDelete, body, len, &output); + if (ret != 0) { + response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_image_delete_response, (void *)response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* inspect request to rest */ +static int inspect_request_to_rest(const struct lcrc_inspect_request *li_request, char **body, size_t *body_len) +{ + image_inspect_request *crequest = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + int ret = 0; + + crequest = util_common_calloc_s(sizeof(image_inspect_request)); + if (crequest == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (li_request->name != NULL) { + crequest->id = util_strdup_s(li_request->name); + } + + crequest->bformat = li_request->bformat; + crequest->timeout = li_request->timeout; + + *body = image_inspect_request_generate_json(crequest, &ctx, &err); + if (*body == NULL) { + ERROR("Failed to generate inspect request json:%s", err); + ret = -1; + goto out; + } + *body_len = strlen(*body) + 1; +out: + free(err); + free_image_inspect_request(crequest); + return ret; +} + +/* unpack inspect response */ +static int unpack_inspect_response(const struct parsed_http_message *message, void *arg) +{ + struct lcrc_inspect_response *response = arg; + image_inspect_response *cresponse = NULL; + parser_error err = NULL; + int ret = 0; + + ret = check_status_code(message->status_code); + if (ret != 0) { + goto out; + } + + cresponse = image_inspect_response_parse_data(message->body, NULL, &err); + if (cresponse == NULL) { + ERROR("Invalid inspect response:%s", err); + ret = -1; + goto out; + } + response->server_errono = cresponse->cc; + if (cresponse->image_json != NULL) { + response->json = util_strdup_s(cresponse->image_json); + } + if (cresponse->errmsg != NULL) { + response->errmsg = util_strdup_s(cresponse->errmsg); + } + ret = (cresponse->cc == LCRD_SUCCESS) ? 0 : -1; + if (message->status_code == EVHTP_RES_SERVERR) { + ret = -1; + } + +out: + free(err); + free_image_inspect_response(cresponse); + return ret; +} + +/* rest image inspect */ +static int rest_image_inspect(const struct lcrc_inspect_request *li_request, + struct lcrc_inspect_response *li_response, void *arg) +{ + char *body = NULL; + int ret = 0; + size_t len; + client_connect_config_t *connect_config = (client_connect_config_t *)arg; + const char *socketname = (const char *)(connect_config->socket); + Buffer *output = NULL; + + ret = inspect_request_to_rest(li_request, &body, &len); + if (ret != 0) { + goto out; + } + ret = rest_send_requst(socketname, RestHttpHead ImagesServiceInspect, body, len, &output); + if (ret != 0) { + li_response->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_CONNECT)); + li_response->cc = LCRD_ERR_EXEC; + goto out; + } + ret = get_response(output, unpack_inspect_response, (void *)li_response); + if (ret != 0) { + goto out; + } +out: + if (output != NULL) { + buffer_free(output); + } + put_body(body); + return ret; +} + +/* rest images client ops init */ +int rest_images_client_ops_init(lcrc_connect_ops *ops) +{ + if (ops == NULL) { + return -1; + } + + ops->image.list = &rest_image_list; + ops->image.remove = &rest_image_remove; + ops->image.load = &rest_image_load; + ops->image.inspect = &rest_image_inspect; + + return 0; +} diff --git a/src/connect/client/rest/rest_images_client.h b/src/connect/client/rest/rest_images_client.h new file mode 100644 index 0000000..e5e294f --- /dev/null +++ b/src/connect/client/rest/rest_images_client.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide image restful client definition + ******************************************************************************/ +#ifndef __REST_IMAGES_CLIENT_H +#define __REST_IMAGES_CLIENT_H + +#include "lcrc_connect.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int rest_images_client_ops_init(lcrc_connect_ops *ops); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/service/CMakeLists.txt b/src/connect/service/CMakeLists.txt new file mode 100644 index 0000000..dbe1a78 --- /dev/null +++ b/src/connect/service/CMakeLists.txt @@ -0,0 +1,17 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} service_srcs) + +set(incs ${CMAKE_CURRENT_SOURCE_DIR}) + +if (GRPC_CONNECTOR) + add_subdirectory(grpc) + list(APPEND service_srcs ${SERVICE_GRPC_SRCS}) + list(APPEND incs ${CMAKE_CURRENT_SOURCE_DIR}/grpc) +else() + add_subdirectory(rest) + list(APPEND service_srcs ${SERVICE_REST_SRCS}) + list(APPEND incs ${CMAKE_CURRENT_SOURCE_DIR}/rest) +endif() + +set(CONNECT_SERVICE_SRCS ${service_srcs} PARENT_SCOPE) +set(CONNECT_SERVICE_INCS ${incs} PARENT_SCOPE) diff --git a/src/connect/service/grpc/CMakeLists.txt b/src/connect/service/grpc/CMakeLists.txt new file mode 100644 index 0000000..4a8c1bc --- /dev/null +++ b/src/connect/service/grpc/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_service_grpc_srcs) + +set(SERVICE_GRPC_SRCS + ${local_service_grpc_srcs} + PARENT_SCOPE + ) diff --git a/src/connect/service/grpc/grpc_containers_service.cc b/src/connect/service/grpc/grpc_containers_service.cc new file mode 100644 index 0000000..ae02546 --- /dev/null +++ b/src/connect/service/grpc/grpc_containers_service.cc @@ -0,0 +1,1454 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc container functions + ******************************************************************************/ +#include "grpc_containers_service.h" +#include +#include +#include +#include +#include +#include "securec.h" +#include "log.h" +#include "utils.h" +#include "error.h" +#include "cxxutils.h" +#include "stoppable_thread.h" +#include "grpc_server_tls_auth.h" +#include "containers_store.h" +#include "logger_json_file.h" + +void protobuf_timestamp_to_grpc(const types_timestamp_t *timestamp, Timestamp *gtimestamp) +{ + gtimestamp->set_seconds(timestamp->seconds); + gtimestamp->set_nanos(timestamp->nanos); +} + +void protobuf_timestamp_from_grpc(types_timestamp_t *timestamp, const Timestamp >imestamp) +{ + timestamp->has_seconds = gtimestamp.seconds() != 0; + timestamp->seconds = gtimestamp.seconds(); + timestamp->has_nanos = gtimestamp.nanos() != 0; + timestamp->nanos = gtimestamp.nanos(); +} + +void event_to_grpc(const struct lcrd_events_format *event, Event *gevent) +{ + gevent->Clear(); + if (event->id != nullptr) { + gevent->set_id(event->id); + } + + if (event->has_type != 0) { + gevent->set_type((EventType)event->type); + } + if (event->has_pid != 0) { + gevent->set_pid((int32_t)(event->pid)); + } else { + gevent->set_pid(-1); + } + if (event->has_exit_status != 0) { + gevent->set_exit_status(event->exit_status); + } + + if (event->timestamp.has_seconds != 0 || event->timestamp.has_nanos != 0) { + protobuf_timestamp_to_grpc((const types_timestamp_t *)(&event->timestamp), gevent->mutable_timestamp()); + } +} + +void copy_from_container_response_to_grpc(const struct lcrd_copy_from_container_response *copy, + CopyFromContainerResponse *gcopy) +{ + gcopy->Clear(); + if (copy == nullptr) { + return; + } + if (copy->data != nullptr && copy->data_len > 0) { + gcopy->set_data(copy->data, copy->data_len); + } +} + +bool grpc_is_call_cancelled(void *context) +{ + return ((ServerContext *)context)->IsCancelled(); +} + +bool grpc_add_initial_metadata(void *context, const char *header, const char *val) +{ + ((ServerContext *)context)->AddInitialMetadata(header, val); + return true; +} + +bool grpc_event_write_function(void *writer, void *data) +{ + struct lcrd_events_format *event = (struct lcrd_events_format *)data; + ServerWriter *gwriter = (ServerWriter *)writer; + Event gevent; + event_to_grpc(event, &gevent); + return gwriter->Write(gevent); +} + +bool grpc_copy_from_container_write_function(void *writer, void *data) +{ + struct lcrd_copy_from_container_response *copy = (struct lcrd_copy_from_container_response *)data; + ServerWriter *gwriter = (ServerWriter *)writer; + CopyFromContainerResponse gcopy; + copy_from_container_response_to_grpc(copy, &gcopy); + return gwriter->Write(gcopy); +} + +static bool copy_to_container_data_from_grpc(struct lcrd_copy_to_container_data *copy, + CopyToContainerRequest *gcopy) +{ + size_t len = (size_t)gcopy->data().length(); + if (len > 0) { + char *data = nullptr; + data = (char *)util_common_calloc_s(len); + if (data == nullptr) { + ERROR("Out of memory"); + return false; + } + if (memcpy_s(data, len, gcopy->data().c_str(), len) != EOK) { + ERROR("Can not copy memory"); + free(data); + return false; + } + copy->data = data; + copy->data_len = len; + return true; + } + return false; +} + +bool grpc_copy_to_container_read_function(void *reader, void *data) +{ + struct lcrd_copy_to_container_data *copy = (struct lcrd_copy_to_container_data *)data; + ServerReaderWriter *stream = + (ServerReaderWriter *)reader; + CopyToContainerRequest gcopy; + if (!stream->Read(&gcopy)) { + return false; + } + return copy_to_container_data_from_grpc(copy, &gcopy); +} + +Status ContainerServiceImpl::Version(ServerContext *context, const VersionRequest *request, VersionResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_version_request *container_req = nullptr; + container_version_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "docker_version"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.version == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = version_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.version(container_req, &container_res); + tret = version_response_to_grpc(container_res, reply); + + free_container_version_request(container_req); + free_container_version_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Info(ServerContext *context, const InfoRequest *request, InfoResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + host_info_request *container_req = nullptr; + host_info_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "docker_info"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.info == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = info_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.info(container_req, &container_res); + tret = info_response_to_grpc(container_res, reply); + + free_host_info_request(container_req); + free_host_info_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Create(ServerContext *context, const CreateRequest *request, CreateResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_create_response *container_res = nullptr; + container_create_request *container_req = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_create"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.create == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = create_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.create(container_req, &container_res); + tret = create_response_to_grpc(container_res, reply); + + free_container_create_request(container_req); + free_container_create_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Start(ServerContext *context, const StartRequest *request, StartResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_start_request *req = nullptr; + container_start_response *res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_start"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.start == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = start_request_from_grpc(request, &req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::CANCELLED; + } + + ret = cb->container.start(req, &res, -1, nullptr, nullptr); + tret = response_to_grpc(res, reply); + + free_container_start_request(req); + free_container_start_response(res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +struct RemoteStartContext { + ServerReaderWriter *stream; + bool isStdout; + sem_t *sem; +}; + +ssize_t WriteStartResponseToRemoteClient(void *context, const void *data, size_t len) +{ + if (context == nullptr || data == nullptr || len == 0) { + return 0; + } + + struct RemoteStartContext *ctx = (struct RemoteStartContext *)context; + RemoteStartResponse response; + if (ctx->isStdout) { + response.set_stdout((char *)data, len); + } else { + response.set_stderr((char *)data, len); + } + if (!ctx->stream->Write(response)) { + ERROR("Failed to write request to grpc client"); + return 0; + } + + return (ssize_t)len; +} + +int grpc_start_stream_close(void *context, char **err) +{ + int ret = 0; + (void)err; + struct RemoteStartContext *ctx = (struct RemoteStartContext *)context; + RemoteStartResponse finish_response; + finish_response.set_finish(true); + if (!ctx->stream->Write(finish_response)) { + ERROR("Failed to write finish request to grpc client"); + ret = -1; + } + if (ctx->sem != nullptr) { + (void)sem_post(ctx->sem); + } + + return ret; +} + +Status ContainerServiceImpl::RemoteStart(ServerContext *context, + ServerReaderWriter *stream) +{ + service_callback_t *cb = nullptr; + container_start_request *container_req = nullptr; + container_start_response *container_res = nullptr; + sem_t sem; + + cb = get_service_callback(); + if (cb == nullptr || cb->container.start == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + if (remote_start_request_from_stream(context->client_metadata(), &container_req) != 0) { + ERROR("Failed to transform grpc request!"); + return Status(StatusCode::UNKNOWN, "Transform request failed"); + } + + if (sem_init(&sem, 0, 0) != 0) { + return grpc::Status(grpc::StatusCode::UNKNOWN, "Semaphore initialization failed");; + } + + int read_pipe_fd[2]; + if ((pipe2(read_pipe_fd, O_NONBLOCK | O_CLOEXEC)) < 0) { + ERROR("create read pipe failed"); + (void)sem_destroy(&sem); + return Status(StatusCode::UNKNOWN, "create read pipe failed"); + } + + struct RemoteStartContext stdoutCtx = { 0 }; + stdoutCtx.stream = stream; + stdoutCtx.isStdout = true; + struct io_write_wrapper stdoutWriter = { 0 }; + stdoutWriter.context = (void *)(&stdoutCtx); + stdoutWriter.write_func = WriteStartResponseToRemoteClient; + stdoutWriter.close_func = nullptr; + + struct RemoteStartContext stderrCtx = { 0 }; + stderrCtx.stream = stream; + stderrCtx.sem = &sem; + struct io_write_wrapper stderrWriter = { 0 }; + stderrWriter.context = (void *)(&stderrCtx); + stderrWriter.write_func = WriteStartResponseToRemoteClient; + stderrWriter.close_func = grpc_start_stream_close; + + int ret = cb->container.start(container_req, &container_res, container_req->attach_stdin ? read_pipe_fd[0] : -1, + container_req->attach_stdout ? &stdoutWriter : nullptr, + container_req->attach_stderr ? &stderrWriter : nullptr); + if (container_req->attach_stdin && ret == 0) { + RemoteStartRequest request; + while (stream->Read(&request)) { + if (request.finish()) { + break; + } + std::string command = request.stdin(); + if (write(read_pipe_fd[1], (void *)(command.c_str()), command.length()) < 0) { + ERROR("sub write over!"); + break; + } + } + } + + // close pipe 1 first, make sure io copy thread exit + close(read_pipe_fd[1]); + if (container_req->attach_stderr && ret == 0) { + (void)sem_wait(&sem); + } + (void)sem_destroy(&sem); + close(read_pipe_fd[0]); + + add_start_trailing_metadata(context, container_res); + free_container_start_request(container_req); + free_container_start_response(container_res); + return Status::OK; +} + +Status ContainerServiceImpl::Top(ServerContext *context, const TopRequest *request, TopResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_top_request *req = nullptr; + container_top_response *res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_top"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.top == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = top_request_from_grpc(request, &req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::CANCELLED; + } + + ret = cb->container.top(req, &res); + tret = top_response_to_grpc(res, reply); + + free_container_top_request(req); + free_container_top_response(res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Stop(ServerContext *context, const StopRequest *request, StopResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_stop_request *container_req = nullptr; + container_stop_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_stop"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.stop == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = stop_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.stop(container_req, &container_res); + tret = response_to_grpc(container_res, reply); + + free_container_stop_request(container_req); + free_container_stop_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Restart(ServerContext *context, const RestartRequest *request, RestartResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_restart_request *container_req = nullptr; + container_restart_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_restart"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.restart == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = restart_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.restart(container_req, &container_res); + tret = response_to_grpc(container_res, reply); + + free_container_restart_request(container_req); + free_container_restart_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Kill(ServerContext *context, const KillRequest *request, KillResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_kill_request *container_req = nullptr; + container_kill_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_kill"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.kill == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = kill_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.kill(container_req, &container_res); + tret = response_to_grpc(container_res, reply); + + free_container_kill_request(container_req); + free_container_kill_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Delete(ServerContext *context, const DeleteRequest *request, DeleteResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_delete_request *container_req = nullptr; + container_delete_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_delete"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.remove == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = delete_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.remove(container_req, &container_res); + tret = delete_response_to_grpc(container_res, reply); + + free_container_delete_request(container_req); + free_container_delete_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Exec(ServerContext *context, const ExecRequest *request, ExecResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_exec_request *container_req = nullptr; + container_exec_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_exec_create"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.exec == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = exec_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::CANCELLED; + } + + ret = cb->container.exec(container_req, &container_res, -1, nullptr); + tret = exec_response_to_grpc(container_res, reply); + + free_container_exec_request(container_req); + free_container_exec_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +ssize_t WriteExecResponseToRemoteClient(void *context, const void *data, size_t len) +{ + if (context == nullptr || data == nullptr || len == 0) { + return 0; + } + auto stream = static_cast *>(context); + RemoteExecResponse response; + response.set_stdout((char *)data, len); + if (!stream->Write(response)) { + ERROR("Failed to write request to grpc client"); + return -1; + } + return (ssize_t)len; +} + +class RemoteExecReceiveFromClientTask : public StoppableThread { +public: + RemoteExecReceiveFromClientTask(ServerReaderWriter *stream, + int read_pipe_fd) : m_stream(stream), m_read_pipe_fd(read_pipe_fd) {} + ~RemoteExecReceiveFromClientTask() = default; + + void run() + { + RemoteExecRequest request; + while (stopRequested() == false && m_stream->Read(&request)) { + if (request.finish()) { + return; + } + for (int i = 0; i < request.cmd_size(); i++) { + std::string command = request.cmd(i); + if (write(m_read_pipe_fd, (void *)(command.c_str()), command.length()) < 0) { + ERROR("sub write over!"); + return; + } + } + } + } + +private: + ServerReaderWriter *m_stream; + int m_read_pipe_fd; +}; + +Status ContainerServiceImpl::RemoteExec(ServerContext *context, + ServerReaderWriter *stream) +{ + service_callback_t *cb = nullptr; + container_exec_request *container_req = nullptr; + container_exec_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_exec_create"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.exec == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + int read_pipe_fd[2] = { -1, -1 }; + if ((pipe2(read_pipe_fd, O_NONBLOCK | O_CLOEXEC)) < 0) { + ERROR("create read pipe(grpc server to lxc pipe) fail!"); + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + std::string errmsg; + if (remote_exec_request_from_stream(context, &container_req, errmsg) != 0) { + ERROR("Failed to transform grpc request!"); + return Status(StatusCode::UNKNOWN, errmsg); + } + + RemoteExecReceiveFromClientTask receive_task(stream, read_pipe_fd[1]); + std::thread command_writer([&]() { + receive_task.run(); + }); + + struct io_write_wrapper stringWriter = { 0 }; + stringWriter.context = (void *)stream; + stringWriter.write_func = WriteExecResponseToRemoteClient; + stringWriter.close_func = nullptr; + (void)cb->container.exec(container_req, &container_res, read_pipe_fd[0], &stringWriter); + + RemoteExecResponse finish_response; + finish_response.set_finish(true); + + receive_task.stop(); + + if (!stream->Write(finish_response)) { + ERROR("Failed to write finish request to grpc client"); + return Status(StatusCode::INTERNAL, "Internal errors"); + } + + command_writer.join(); + add_exec_trailing_metadata(context, container_res); + free_container_exec_request(container_req); + free_container_exec_response(container_res); + close(read_pipe_fd[0]); + close(read_pipe_fd[1]); + return Status::OK; +} + +Status ContainerServiceImpl::Inspect(ServerContext *context, const InspectContainerRequest *request, + InspectContainerResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_inspect_request *container_req = nullptr; + container_inspect_response *container_res = nullptr; + + cb = get_service_callback(); + if (cb == nullptr || cb->container.inspect == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = inspect_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + Status status = GrpcServerTlsAuth::auth(context, "container_inspect"); + if (!status.ok()) { + return status; + } + + ret = cb->container.inspect(container_req, &container_res); + tret = inspect_response_to_grpc(container_res, reply); + + free_container_inspect_request(container_req); + free_container_inspect_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::List(ServerContext *context, const ListRequest *request, ListResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_list_request *container_req = nullptr; + container_list_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_list"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.list == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = list_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.list(container_req, &container_res); + tret = list_response_to_grpc(container_res, reply); + + free_container_list_request(container_req); + free_container_list_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +struct AttachContext { + ServerReaderWriter *stream; + bool isStdout; + sem_t *sem; +}; + +ssize_t WriteAttachResponseToRemoteClient(void *context, const void *data, size_t len) +{ + if (context == nullptr || data == nullptr || len == 0) { + return 0; + } + struct AttachContext *ctx = (struct AttachContext *)context; + AttachResponse response; + if (ctx->isStdout) { + response.set_stdout((char *)data, len); + } else { + response.set_stderr((char *)data, len); + } + + if (!ctx->stream->Write(response)) { + ERROR("Failed to write request to grpc client"); + return 0; + } + return (ssize_t)len; +} + +int grpc_attach_stream_close(void *context, char **err) +{ + int ret = 0; + (void)err; + struct AttachContext *ctx = (struct AttachContext *)context; + AttachResponse finish_response; + finish_response.set_finish(true); + if (!ctx->stream->Write(finish_response)) { + ERROR("Failed to write finish request to grpc client"); + ret = -1; + } + if (ctx->sem != nullptr) { + (void)sem_post(ctx->sem); + } + return ret; +} + +Status ContainerServiceImpl::AttachInit(ServerContext *context, service_callback_t **cb, + container_attach_request **req, container_attach_response **res, + sem_t *sem_stderr, int pipefd[]) +{ + auto status = GrpcServerTlsAuth::auth(context, "container_attach"); + if (!status.ok()) { + return status; + } + *cb = get_service_callback(); + if (*cb == nullptr || (*cb)->container.attach == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + if (attach_request_from_stream(context->client_metadata(), req) != 0) { + ERROR("Failed to transform grpc request!"); + return Status(StatusCode::UNKNOWN, "Transform request failed"); + } + + if ((*req)->attach_stdout != (*req)->attach_stderr) { + free_container_attach_request(*req); + return Status(StatusCode::UNKNOWN, "Attach stdout should always equal to attach stderr"); + } + + if (sem_init(sem_stderr, 0, 0) != 0) { + free_container_attach_request(*req); + return grpc::Status(grpc::StatusCode::UNKNOWN, "Semaphore initialization failed");; + } + + if ((pipe2(pipefd, O_NONBLOCK | O_CLOEXEC)) < 0) { + ERROR("create pipe failed"); + (void)sem_destroy(sem_stderr); + free_container_attach_request(*req); + return Status(StatusCode::UNKNOWN, "create pipe failed"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Attach(ServerContext *context, ServerReaderWriter *stream) +{ + service_callback_t *cb = nullptr; + container_attach_request *container_req = nullptr; + container_attach_response *container_res = nullptr; + sem_t sem_stderr; + int pipefd[2] = { -1, -1 }; + + auto status = AttachInit(context, &cb, &container_req, &container_res, &sem_stderr, pipefd); + if (!status.ok()) { + return status; + } + + struct AttachContext stdoutCtx = { 0 }; + stdoutCtx.stream = stream; + stdoutCtx.isStdout = true; + struct io_write_wrapper stdoutWriter = { 0 }; + stdoutWriter.context = (void *)(&stdoutCtx); + stdoutWriter.write_func = WriteAttachResponseToRemoteClient; + stdoutWriter.close_func = nullptr; + + struct AttachContext stderrCtx = { 0 }; + stderrCtx.stream = stream; + stderrCtx.sem = &sem_stderr; + struct io_write_wrapper stderrWriter = { 0 }; + stderrWriter.context = (void *)(&stderrCtx); + stderrWriter.write_func = WriteAttachResponseToRemoteClient; + stderrWriter.close_func = grpc_attach_stream_close; + + int ret = cb->container.attach(container_req, &container_res, container_req->attach_stdin ? pipefd[0] : -1, + container_req->attach_stdout ? &stdoutWriter : nullptr, + container_req->attach_stderr ? &stderrWriter : nullptr); + if (container_req->attach_stdin && ret == 0) { + AttachRequest request; + while (stream->Read(&request)) { + if (request.finish()) { + break; + } + std::string command = request.stdin(); + if (write(pipefd[1], (void *)(command.c_str()), command.length()) < 0) { + ERROR("sub write over!"); + break; + } + } + } + + // Close pipe 1 first, make sure io copy thread exit + close(pipefd[1]); + // Waiting sem, make sure the sem is posted always in attach callback. + if (container_req->attach_stderr) { + (void)sem_wait(&sem_stderr); + } + (void)sem_destroy(&sem_stderr); + close(pipefd[0]); + + add_attach_trailing_metadata(context, container_res); + free_container_attach_request(container_req); + free_container_attach_response(container_res); + return Status::OK; +} + +Status ContainerServiceImpl::Pause(ServerContext *context, const PauseRequest *request, PauseResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_pause_request *container_req = nullptr; + container_pause_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_pause"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.pause == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = pause_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.pause(container_req, &container_res); + tret = response_to_grpc(container_res, reply); + + free_container_pause_request(container_req); + free_container_pause_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Resume(ServerContext *context, const ResumeRequest *request, ResumeResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_resume_request *container_req = nullptr; + container_resume_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_unpause"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.resume == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = resume_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.resume(container_req, &container_res); + tret = response_to_grpc(container_res, reply); + + free_container_resume_request(container_req); + free_container_resume_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Export(ServerContext *context, const ExportRequest *request, ExportResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_export_request *container_req = nullptr; + container_export_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_export"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.export_rootfs == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = export_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.export_rootfs(container_req, &container_res); + tret = response_to_grpc(container_res, reply); + + free_container_export_request(container_req); + free_container_export_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Container_conf(ServerContext *context, const Container_conf_Request *request, + Container_conf_Response *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + struct lcrd_container_conf_request *lcrdreq = nullptr; + struct lcrd_container_conf_response *lcrdres = nullptr; + + cb = get_service_callback(); + if (cb == nullptr || cb->container.conf == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = container_conf_request_from_grpc(request, &lcrdreq); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.conf(lcrdreq, &lcrdres); + tret = container_conf_response_to_grpc(lcrdres, reply); + + lcrd_container_conf_request_free(lcrdreq); + lcrd_container_conf_response_free(lcrdres); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Rename(ServerContext *context, const RenameRequest *request, + RenameResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + struct lcrd_container_rename_request *lcrdreq = nullptr; + struct lcrd_container_rename_response *lcrdres = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_rename"); + if (!status.ok()) { + return status; + } + + cb = get_service_callback(); + if (cb == nullptr || cb->container.rename == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = container_rename_request_from_grpc(request, &lcrdreq); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.rename(lcrdreq, &lcrdres); + tret = container_rename_response_to_grpc(lcrdres, reply); + + lcrd_container_rename_request_free(lcrdreq); + lcrd_container_rename_response_free(lcrdres); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Update(ServerContext *context, const UpdateRequest *request, UpdateResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_update_request *container_req = nullptr; + container_update_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_update"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.update == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = update_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.update(container_req, &container_res); + tret = update_response_to_grpc(container_res, reply); + + free_container_update_request(container_req); + free_container_update_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Stats(ServerContext *context, const StatsRequest *request, StatsResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_stats_request *container_req = nullptr; + container_stats_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_stats"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.stats == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = stats_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.stats(container_req, &container_res); + tret = stats_response_to_grpc(container_res, reply); + + free_container_stats_request(container_req); + free_container_stats_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Wait(ServerContext *context, const WaitRequest *request, WaitResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + container_wait_request *container_req = nullptr; + container_wait_response *container_res = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_wait"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.wait == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + + tret = wait_request_from_grpc(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + ret = cb->container.wait(container_req, &container_res); + tret = wait_response_to_grpc(container_res, reply); + + free_container_wait_request(container_req); + free_container_wait_response(container_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ContainerServiceImpl::Events(ServerContext *context, const EventsRequest *request, ServerWriter *writer) +{ + int ret, tret; + service_callback_t *cb = nullptr; + lcrd_events_request *lcrdreq = nullptr; + stream_func_wrapper stream = { 0 }; + + auto status = GrpcServerTlsAuth::auth(context, "docker_events"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.events == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = events_request_from_grpc(request, &lcrdreq); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + return Status(StatusCode::INTERNAL, "Failed to transform grpc request"); + } + + stream.context = (void *)context; + stream.is_cancelled = &grpc_is_call_cancelled; + stream.write_func = &grpc_event_write_function; + stream.writer = (void *)writer; + + ret = cb->container.events(lcrdreq, &stream); + lcrd_events_request_free(lcrdreq); + if (ret != 0) { + return Status(StatusCode::INTERNAL, "Failed to execute events callback"); + } + return Status::OK; +} + +Status ContainerServiceImpl::CopyFromContainer(ServerContext *context, const CopyFromContainerRequest *request, + ServerWriter *writer) +{ + int ret, tret; + service_callback_t *cb = nullptr; + lcrd_copy_from_container_request *lcrdreq = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_archive"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.copy_from_container == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = copy_from_container_request_from_grpc(request, &lcrdreq); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + return Status(StatusCode::UNKNOWN, "Failed to transform grpc request"); + } + + stream_func_wrapper stream = { 0 }; + stream.context = (void *)context; + stream.is_cancelled = &grpc_is_call_cancelled; + stream.add_initial_metadata = &grpc_add_initial_metadata; + stream.write_func = &grpc_copy_from_container_write_function; + stream.writer = (void *)writer; + + char *err = nullptr; + ret = cb->container.copy_from_container(lcrdreq, &stream, &err); + lcrd_copy_from_container_request_free(lcrdreq); + std::string errmsg = (err != nullptr) ? err : "Failed to execute copy_from_container callback"; + free(err); + if (ret != 0) { + return Status(StatusCode::UNKNOWN, errmsg); + } + return Status::OK; +} + +Status ContainerServiceImpl::CopyToContainer( + ServerContext *context, + ServerReaderWriter *stream) + +{ + int ret; + service_callback_t *cb = nullptr; + container_copy_to_request *lcrdreq = nullptr; + + auto status = GrpcServerTlsAuth::auth(context, "container_archive"); + if (!status.ok()) { + return status; + } + cb = get_service_callback(); + if (cb == nullptr || cb->container.copy_to_container == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + auto metadata = context->client_metadata(); + auto iter = metadata.find("isulad-copy-to-container"); + if (iter != metadata.end()) { + char *err = nullptr; + std::string json = std::string(iter->second.data(), iter->second.length()); + lcrdreq = container_copy_to_request_parse_data(json.c_str(), nullptr, &err); + if (lcrdreq == nullptr) { + std::string errmsg = "Invalid copy to container json: "; + errmsg += (err != nullptr) ? err : "unknown"; + free(err); + return Status(StatusCode::UNKNOWN, errmsg); + } + } else { + return Status(StatusCode::UNKNOWN, "No metadata 'isulad-copy-to-container' received"); + } + stream_func_wrapper wrapper = { 0 }; + wrapper.context = (void *)context; + wrapper.is_cancelled = &grpc_is_call_cancelled; + wrapper.reader = (void *)stream; + wrapper.read_func = &grpc_copy_to_container_read_function; + + char *err = nullptr; + ret = cb->container.copy_to_container(lcrdreq, &wrapper, &err); + free_container_copy_to_request(lcrdreq); + std::string msg = (err != nullptr) ? err : "Failed to execute copy_to_container callback"; + free(err); + + CopyToContainerResponse res; + res.set_finish(true); + stream->Write(res); + if (ret != 0) { + return Status(StatusCode::UNKNOWN, msg); + } + return Status::OK; +} + +void log_to_grpc(const logger_json_file *log, LogsResponse *glog) +{ + glog->Clear(); + if (log->log != nullptr) { + glog->set_data(log->log, log->log_len); + } + if (log->stream != nullptr) { + glog->set_stream(log->stream); + } + if (log->time != nullptr) { + glog->set_time(log->time); + } + if (log->attrs != nullptr) { + glog->set_attrs(log->attrs, log->attrs_len); + } +} + +int ContainerServiceImpl::logs_request_from_grpc(const LogsRequest *grequest, struct lcrd_logs_request **request) +{ + *request = (struct lcrd_logs_request *)util_common_calloc_s(sizeof(struct lcrd_logs_request)); + if (*request == nullptr) { + ERROR("Out of memory"); + return -1; + } + if (!grequest->id().empty()) { + (*request)->id = util_strdup_s(grequest->id().c_str()); + } + if (!grequest->runtime().empty()) { + (*request)->runtime = util_strdup_s(grequest->runtime().c_str()); + } + if (!grequest->since().empty()) { + (*request)->since = util_strdup_s(grequest->since().c_str()); + } + if (!grequest->until().empty()) { + (*request)->until = util_strdup_s(grequest->until().c_str()); + } + (*request)->timestamps = grequest->timestamps(); + (*request)->follow = grequest->follow(); + (*request)->tail = grequest->tail(); + (*request)->details = grequest->details(); + + return 0; +} + +bool grpc_logs_write_function(void *writer, void *data) +{ + logger_json_file *log = static_cast(data); + ServerWriter *gwriter = static_cast *>(writer); + LogsResponse gresponse; + log_to_grpc(log, &gresponse); + return gwriter->Write(gresponse); +} + +Status ContainerServiceImpl::Logs(ServerContext *context, const LogsRequest* request, + ServerWriter* writer) +{ + int ret = 0; + service_callback_t *cb = nullptr; + struct lcrd_logs_request *lcrd_request = nullptr; + struct lcrd_logs_response *lcrd_response = nullptr; + stream_func_wrapper stream = { 0 }; + + auto status = GrpcServerTlsAuth::auth(context, "container_logs"); + if (!status.ok()) { + return status; + } + + cb = get_service_callback(); + if (cb == nullptr || cb->container.logs == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + ret = logs_request_from_grpc(request, &lcrd_request); + if (ret != 0) { + ERROR("Failed to transform grpc request"); + return Status(StatusCode::UNKNOWN, "Failed to transform grpc request"); + } + + stream.context = (void *)context; + stream.is_cancelled = &grpc_is_call_cancelled; + stream.write_func = &grpc_logs_write_function; + stream.writer = (void *)writer; + + ret = cb->container.logs(lcrd_request, &stream, &lcrd_response); + lcrd_logs_request_free(lcrd_request); + std::string errmsg = "Failed to execute logs"; + if (lcrd_response == nullptr) { + return Status(StatusCode::UNKNOWN, errmsg); + } + errmsg = (lcrd_response->errmsg != nullptr) ? lcrd_response->errmsg : "Failed to execute logs"; + lcrd_logs_response_free(lcrd_response); + if (ret != 0) { + return Status(StatusCode::UNKNOWN, errmsg); + } + return Status::OK; +} + diff --git a/src/connect/service/grpc/grpc_containers_service.h b/src/connect/service/grpc/grpc_containers_service.h new file mode 100644 index 0000000..3a06655 --- /dev/null +++ b/src/connect/service/grpc/grpc_containers_service.h @@ -0,0 +1,225 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc container functions + ******************************************************************************/ +#ifndef _GRPC_CONTAINER_SERVICE_H_ +#define _GRPC_CONTAINER_SERVICE_H_ +#include +#include +#include +#include "container.grpc.pb.h" +#include "callback.h" +#include "error.h" + +using namespace containers; + +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::ServerReader; +using grpc::ServerReaderWriter; +using grpc::ServerWriter; +using grpc::Status; +using grpc::StatusCode; +using google::protobuf::Timestamp; + +void protobuf_timestamp_to_grpc(types_timestamp_t *timestamp, Timestamp *gtimestamp); +void protobuf_timestamp_from_grpc(types_timestamp_t *timestamp, const Timestamp >imestamp); + +// Implement of containers service +class ContainerServiceImpl final : public ContainerService::Service { +public: + ContainerServiceImpl() = default; + ContainerServiceImpl(const ContainerServiceImpl &) = delete; + ContainerServiceImpl &operator=(const ContainerServiceImpl &) = delete; + virtual ~ContainerServiceImpl() = default; + + Status Version(ServerContext *context, const VersionRequest *request, VersionResponse *reply) override; + + Status Info(ServerContext *context, const InfoRequest *request, InfoResponse *reply) override; + + Status Create(ServerContext *context, const CreateRequest *request, CreateResponse *reply) override; + + Status Start(ServerContext *context, const StartRequest *request, StartResponse *reply) override; + + Status Top(ServerContext *context, const TopRequest *request, TopResponse *reply) override; + + Status Stop(ServerContext *context, const StopRequest *request, StopResponse *reply) override; + + Status Restart(ServerContext *context, const RestartRequest *request, RestartResponse *reply) override; + + Status Kill(ServerContext *context, const KillRequest *request, KillResponse *reply) override; + + Status Delete(ServerContext *context, const DeleteRequest *request, DeleteResponse *reply) override; + + Status Exec(ServerContext *context, const ExecRequest *request, ExecResponse *reply) override; + + Status Inspect(ServerContext *context, const InspectContainerRequest *request, + InspectContainerResponse *reply) override; + + Status List(ServerContext *context, const ListRequest *request, ListResponse *reply) override; + + Status Attach(ServerContext *context, ServerReaderWriter *stream) override; + + Status Pause(ServerContext *context, const PauseRequest *request, PauseResponse *reply) override; + + Status Resume(ServerContext *context, const ResumeRequest *request, ResumeResponse *reply) override; + + Status Container_conf(ServerContext *context, const Container_conf_Request *request, + Container_conf_Response *reply) override; + + Status Rename(ServerContext *context, const RenameRequest *request, RenameResponse *reply) override; + + Status Update(ServerContext *context, const UpdateRequest *request, UpdateResponse *reply) override; + + + Status Stats(ServerContext *context, const StatsRequest *request, StatsResponse *reply) override; + + Status Wait(ServerContext *context, const WaitRequest *request, WaitResponse *reply) override; + + Status Events(ServerContext *context, const EventsRequest *request, ServerWriter *writer) override; + + Status Export(ServerContext *context, const ExportRequest *request, ExportResponse *reply) override; + + Status RemoteStart(ServerContext *context, + ServerReaderWriter *stream) override; + + Status RemoteExec(ServerContext *context, + ServerReaderWriter *stream) override; + + Status CopyFromContainer(ServerContext *context, const CopyFromContainerRequest *request, + ServerWriter *writer) override; + + Status CopyToContainer(ServerContext *context, ServerReaderWriter *stream); + + Status Logs(ServerContext *context, const LogsRequest* request, + ServerWriter* writer) override; + +private: + template + int response_to_grpc(const T1 *response, T2 *gresponse) + { + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; + } + + int version_request_from_grpc(const VersionRequest *grequest, container_version_request **request); + + int version_response_to_grpc(const container_version_response *response, VersionResponse *gresponse); + + int info_request_from_grpc(const InfoRequest *grequest, host_info_request **request); + + int info_response_to_grpc(const host_info_response *response, InfoResponse *gresponse); + + int create_request_from_grpc(const CreateRequest *grequest, container_create_request **request); + + int create_response_to_grpc(const container_create_response *response, CreateResponse *gresponse); + + int start_request_from_grpc(const StartRequest *grequest, container_start_request **request); + + int top_request_from_grpc(const TopRequest *grequest, container_top_request **request); + + int top_response_to_grpc(const container_top_response *response, TopResponse *gresponse); + + int stop_request_from_grpc(const StopRequest *grequest, container_stop_request **request); + + int restart_request_from_grpc(const RestartRequest *grequest, container_restart_request **request); + + int kill_request_from_grpc(const KillRequest *grequest, container_kill_request **request); + + int delete_request_from_grpc(const DeleteRequest *grequest, container_delete_request **request); + + int delete_response_to_grpc(const container_delete_response *response, DeleteResponse *gresponse); + + int exec_request_from_grpc(const ExecRequest *grequest, container_exec_request **request); + + int exec_response_to_grpc(const container_exec_response *response, ExecResponse *gresponse); + + int inspect_request_from_grpc(const InspectContainerRequest *grequest, container_inspect_request **request); + + int inspect_response_to_grpc(const container_inspect_response *response, InspectContainerResponse *gresponse); + + int list_request_from_grpc(const ListRequest *grequest, container_list_request **request); + + int list_response_to_grpc(const container_list_response *response, ListResponse *gresponse); + + int pause_request_from_grpc(const PauseRequest *grequest, container_pause_request **request); + + int resume_request_from_grpc(const ResumeRequest *grequest, container_resume_request **request); + + int container_conf_request_from_grpc(const Container_conf_Request *grequest, + struct lcrd_container_conf_request **request); + + int container_conf_response_to_grpc(const struct lcrd_container_conf_response *response, + Container_conf_Response *gresponse); + + int container_rename_request_from_grpc(const RenameRequest *grequest, + struct lcrd_container_rename_request **request); + + int container_rename_response_to_grpc(const struct lcrd_container_rename_response *response, + RenameResponse *gresponse); + + int update_request_from_grpc(const UpdateRequest *grequest, container_update_request **request); + + int update_response_to_grpc(const container_update_response *response, UpdateResponse *gresponse); + + int stats_request_from_grpc(const StatsRequest *grequest, container_stats_request **request); + + int stats_response_to_grpc(const container_stats_response *response, StatsResponse *gresponse); + + int wait_request_from_grpc(const WaitRequest *grequest, container_wait_request **request); + + int wait_response_to_grpc(const container_wait_response *response, WaitResponse *gresponse); + + int events_request_from_grpc(const EventsRequest *grequest, struct lcrd_events_request **request); + + int copy_from_container_request_from_grpc(const CopyFromContainerRequest *grequest, + struct lcrd_copy_from_container_request **request); + + int remote_exec_request_from_stream(ServerContext *context, + container_exec_request **request, std::string &errmsg); + + void add_exec_trailing_metadata(ServerContext *context, container_exec_response *response); + + int attach_request_from_stream(const std::multimap &metadata, + container_attach_request **request); + + Status AttachInit(ServerContext *context, service_callback_t **cb, + container_attach_request **req, container_attach_response **res, + sem_t *sem_stderr, int pipefd[]); + + void add_attach_trailing_metadata(ServerContext *context, container_attach_response *response); + + int remote_start_request_from_stream(const std::multimap &metadata, + container_start_request **request); + + void add_start_trailing_metadata(ServerContext *context, container_start_response *response); + + int export_request_from_grpc(const ExportRequest *grequest, container_export_request **request); + + int pack_os_info_to_grpc(const host_info_response *response, InfoResponse *gresponse); + + int pack_proxy_info_to_grpc(const host_info_response *response, InfoResponse *gresponse); + + int logs_request_from_grpc(const LogsRequest *grequest, struct lcrd_logs_request **request); +}; + +#endif /*_GRPC_CONTAINER_SERVICE_H_*/ diff --git a/src/connect/service/grpc/grpc_containers_service_private.cc b/src/connect/service/grpc/grpc_containers_service_private.cc new file mode 100644 index 0000000..04104a4 --- /dev/null +++ b/src/connect/service/grpc/grpc_containers_service_private.cc @@ -0,0 +1,1111 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc container service private functions + ******************************************************************************/ +#include "grpc_containers_service.h" +#include "log.h" +#include "utils.h" +#include "error.h" + +int ContainerServiceImpl::version_request_from_grpc(const VersionRequest *grequest, container_version_request **request) +{ + container_version_request *tmpreq = nullptr; + + tmpreq = (container_version_request *)util_common_calloc_s(sizeof(container_version_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::version_response_to_grpc(const container_version_response *response, + VersionResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + if (response->version != nullptr) { + gresponse->set_version(response->version); + } + if (response->git_commit != nullptr) { + gresponse->set_git_commit(response->git_commit); + } + if (response->build_time != nullptr) { + gresponse->set_build_time(response->build_time); + } + if (response->root_path != nullptr) { + gresponse->set_root_path(response->root_path); + } + + return 0; +} + +int ContainerServiceImpl::info_request_from_grpc(const InfoRequest *grequest, host_info_request **request) +{ + host_info_request *tmpreq = (host_info_request *)util_common_calloc_s(sizeof(host_info_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::info_response_to_grpc(const host_info_response *response, InfoResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + if (response->version != nullptr) { + gresponse->set_version(response->version); + } + gresponse->set_containers_num((::google::protobuf::uint32)response->containers_num); + + gresponse->set_c_running((::google::protobuf::uint32)response->c_running); + + gresponse->set_c_paused((::google::protobuf::uint32)response->c_paused); + + gresponse->set_c_stopped((::google::protobuf::uint32)response->c_stopped); + + gresponse->set_images_num(response->images_num); + + if (pack_os_info_to_grpc(response, gresponse)) { + return -1; + } + + if (response->logging_driver != nullptr) { + gresponse->set_logging_driver(response->logging_driver); + } + + if (response->isulad_root_dir != nullptr) { + gresponse->set_isulad_root_dir(response->isulad_root_dir); + } + + gresponse->set_total_mem(response->total_mem); + + if (pack_proxy_info_to_grpc(response, gresponse)) { + return -1; + } + + return 0; +} + +int ContainerServiceImpl::create_request_from_grpc(const CreateRequest *grequest, container_create_request **request) +{ + container_create_request *tmpreq = nullptr; + + tmpreq = (container_create_request *)util_common_calloc_s(sizeof(container_create_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + if (!grequest->rootfs().empty()) { + tmpreq->rootfs = util_strdup_s(grequest->rootfs().c_str()); + } + if (!grequest->image().empty()) { + tmpreq->image = util_strdup_s(grequest->image().c_str()); + } + if (!grequest->runtime().empty()) { + tmpreq->runtime = util_strdup_s(grequest->runtime().c_str()); + } + if (!grequest->hostconfig().empty()) { + tmpreq->hostconfig = util_strdup_s(grequest->hostconfig().c_str()); + } + if (!grequest->customconfig().empty()) { + tmpreq->customconfig = util_strdup_s(grequest->customconfig().c_str()); + } + + *request = tmpreq; + return 0; +} + + +int ContainerServiceImpl::create_response_to_grpc(const container_create_response *response, CreateResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + if (response->id != nullptr) { + gresponse->set_id(response->id); + } + return 0; +} + +int ContainerServiceImpl::start_request_from_grpc(const StartRequest *grequest, container_start_request **request) +{ + container_start_request *tmpreq = nullptr; + + tmpreq = (container_start_request *)util_common_calloc_s(sizeof(container_start_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + if (!grequest->stdin().empty()) { + tmpreq->stdin = util_strdup_s(grequest->stdin().c_str()); + } + if (!grequest->stdout().empty()) { + tmpreq->stdout = util_strdup_s(grequest->stdout().c_str()); + } + if (!grequest->stderr().empty()) { + tmpreq->stderr = util_strdup_s(grequest->stderr().c_str()); + } + tmpreq->attach_stdin = grequest->attach_stdin(); + tmpreq->attach_stdout = grequest->attach_stdout(); + tmpreq->attach_stderr = grequest->attach_stderr(); + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::top_request_from_grpc(const TopRequest *grequest, container_top_request **request) +{ + container_top_request *tmpreq = nullptr; + + tmpreq = (container_top_request *)util_common_calloc_s(sizeof(container_top_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + if (grequest->args_size() > 0) { + if ((size_t)grequest->args_size() > SIZE_MAX / sizeof(char *)) { + ERROR("Too many arguments!"); + free_container_top_request(tmpreq); + return -1; + } + tmpreq->args = (char **)util_common_calloc_s(sizeof(char *) * grequest->args_size()); + if (tmpreq->args == nullptr) { + ERROR("Out of memory"); + free_container_top_request(tmpreq); + return -1; + } + for (int i = 0; i < grequest->args_size(); i++) { + tmpreq->args[i] = util_strdup_s(grequest->args(i).c_str()); + } + tmpreq->args_len = (size_t)grequest->args_size(); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::top_response_to_grpc(const container_top_response *response, TopResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + + if (response->titles != nullptr) { + gresponse->set_titles(response->titles); + } + + for (size_t i = 0; i < response->processes_len; i++) { + gresponse->add_processes(response->processes[i]); + } + + return 0; +} + +int ContainerServiceImpl::stop_request_from_grpc(const StopRequest *grequest, container_stop_request **request) +{ + container_stop_request *tmpreq = (container_stop_request *)util_common_calloc_s(sizeof(container_stop_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + tmpreq->force = grequest->force(); + tmpreq->timeout = grequest->timeout(); + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::restart_request_from_grpc(const RestartRequest *grequest, container_restart_request **request) +{ + container_restart_request *tmpreq = (container_restart_request *)util_common_calloc_s( + sizeof(container_restart_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + tmpreq->timeout = grequest->timeout(); + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::kill_request_from_grpc(const KillRequest *grequest, container_kill_request **request) +{ + container_kill_request *tmpreq = (container_kill_request *)util_common_calloc_s(sizeof(container_kill_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + tmpreq->signal = grequest->signal(); + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::delete_request_from_grpc(const DeleteRequest *grequest, container_delete_request **request) +{ + container_delete_request *tmpreq = (container_delete_request *)util_common_calloc_s( + sizeof(container_delete_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + tmpreq->force = grequest->force(); + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::delete_response_to_grpc(const container_delete_response *response, DeleteResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + gresponse->set_cc(response->cc); + if (response->id != nullptr) { + gresponse->set_id(response->id); + } + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; +} + +int ContainerServiceImpl::exec_request_from_grpc(const ExecRequest *grequest, container_exec_request **request) +{ + container_exec_request *tmpreq = (container_exec_request *)util_common_calloc_s(sizeof(container_exec_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->container_id().empty()) { + tmpreq->container_id = util_strdup_s(grequest->container_id().c_str()); + } + + tmpreq->tty = grequest->tty(); + tmpreq->attach_stdin = grequest->attach_stdin(); + tmpreq->attach_stdout = grequest->attach_stdout(); + tmpreq->attach_stderr = grequest->attach_stderr(); + + if (!grequest->stdin().empty()) { + tmpreq->stdin = util_strdup_s(grequest->stdin().c_str()); + } + if (!grequest->stdout().empty()) { + tmpreq->stdout = util_strdup_s(grequest->stdout().c_str()); + } + if (!grequest->stderr().empty()) { + tmpreq->stderr = util_strdup_s(grequest->stderr().c_str()); + } + + if (grequest->argv_size() > 0) { + if ((size_t)grequest->argv_size() > SIZE_MAX / sizeof(char *)) { + ERROR("Too many arguments!"); + free_container_exec_request(tmpreq); + return -1; + } + tmpreq->argv = (char **)util_common_calloc_s(sizeof(char *) * grequest->argv_size()); + if (tmpreq->argv == nullptr) { + ERROR("Out of memory"); + free_container_exec_request(tmpreq); + return -1; + } + for (int i = 0; i < grequest->argv_size(); i++) { + tmpreq->argv[i] = util_strdup_s(grequest->argv(i).c_str()); + } + tmpreq->argv_len = grequest->argv_size(); + } + + if (grequest->env_size() > 0) { + if ((size_t)grequest->argv_size() > SIZE_MAX / sizeof(char *)) { + ERROR("Too many environmental variables!"); + free_container_exec_request(tmpreq); + return -1; + } + tmpreq->env = (char **)util_common_calloc_s(sizeof(char *) * grequest->env_size()); + if (tmpreq->env == nullptr) { + ERROR("Out of memory"); + free_container_exec_request(tmpreq); + return -1; + } + for (int i = 0; i < grequest->env_size(); i++) { + tmpreq->env[i] = util_strdup_s(grequest->env(i).c_str()); + } + tmpreq->env_len = grequest->env_size(); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::exec_response_to_grpc(const container_exec_response *response, ExecResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + gresponse->set_pid(response->pid); + gresponse->set_exit_code(response->exit_code); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; +} + +int ContainerServiceImpl::inspect_request_from_grpc(const InspectContainerRequest *grequest, + container_inspect_request **request) +{ + container_inspect_request *tmpreq = (container_inspect_request *)util_common_calloc_s( + sizeof(container_inspect_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + tmpreq->bformat = grequest->bformat(); + tmpreq->timeout = grequest->timeout(); + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::inspect_response_to_grpc(const container_inspect_response *response, + InspectContainerResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + if (response->container_json != nullptr) { + gresponse->set_containerjson(response->container_json); + } + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; +} + +int ContainerServiceImpl::list_request_from_grpc(const ListRequest *grequest, container_list_request **request) +{ + size_t len = 0; + container_list_request *tmpreq = (container_list_request *)util_common_calloc_s( + sizeof(container_list_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + tmpreq->all = grequest->all(); + tmpreq->filters = (defs_filters *)util_common_calloc_s(sizeof(defs_filters)); + if (tmpreq->filters == nullptr) { + ERROR("Out of memory"); + goto cleanup; + } + + len = (size_t)grequest->filters_size(); + if (len == 0) { + *request = tmpreq; + return 0; + } + if (len > SIZE_MAX / sizeof(char *)) { + ERROR("invalid filters size"); + goto cleanup; + } + tmpreq->filters->keys = (char **)util_common_calloc_s(len * sizeof(char *)); + if (tmpreq->filters->keys == nullptr) { + goto cleanup; + } + tmpreq->filters->values = (json_map_string_bool **)util_common_calloc_s(len * sizeof(json_map_string_bool *)); + if (tmpreq->filters->values == nullptr) { + free(tmpreq->filters->keys); + tmpreq->filters->keys = nullptr; + goto cleanup; + } + + for (auto &iter : grequest->filters()) { + tmpreq->filters->values[tmpreq->filters->len] = (json_map_string_bool *) + util_common_calloc_s(sizeof(json_map_string_bool)); + if (tmpreq->filters->values[tmpreq->filters->len] == nullptr) { + ERROR("Out of memory"); + goto cleanup; + } + if (append_json_map_string_bool(tmpreq->filters->values[tmpreq->filters->len], + iter.second.empty() ? "" : iter.second.c_str(), true)) { + free(tmpreq->filters->values[tmpreq->filters->len]); + tmpreq->filters->values[tmpreq->filters->len] = nullptr; + ERROR("Append failed"); + goto cleanup; + } + tmpreq->filters->keys[tmpreq->filters->len] = util_strdup_s(iter.first.empty() ? "" : iter.first.c_str()); + tmpreq->filters->len++; + } + + *request = tmpreq; + return 0; +cleanup: + free_container_list_request(tmpreq); + return -1; +} + +int ContainerServiceImpl::list_response_to_grpc(const container_list_response *response, ListResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + for (size_t i = 0; i < response->containers_len; i++) { + Container *container = gresponse->add_containers(); + if (response->containers[i]->id != nullptr) { + container->set_id(response->containers[i]->id); + } + if (response->containers[i]->name != nullptr) { + container->set_name(response->containers[i]->name); + } + if (response->containers[i]->pid != 0) { + container->set_pid(response->containers[i]->pid); + } + container->set_status((ContainerStatus)response->containers[i]->status); + if (response->containers[i]->image != nullptr) { + container->set_image(response->containers[i]->image); + } + if (response->containers[i]->command != nullptr) { + container->set_command(response->containers[i]->command); + } + container->set_exit_code(response->containers[i]->exit_code); + container->set_restartcount(response->containers[i]->restartcount); + if (response->containers[i]->startat != nullptr) { + container->set_startat(response->containers[i]->startat); + } + if (response->containers[i]->finishat != nullptr) { + container->set_finishat(response->containers[i]->finishat); + } + if (response->containers[i]->runtime != nullptr) { + container->set_runtime(response->containers[i]->runtime); + } + if (response->containers[i]->health_state != nullptr) { + container->set_health_state(response->containers[i]->health_state); + } + } + return 0; +} + +int ContainerServiceImpl::pause_request_from_grpc(const PauseRequest *grequest, container_pause_request **request) +{ + container_pause_request *tmpreq = (container_pause_request *)util_common_calloc_s(sizeof(container_pause_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::resume_request_from_grpc(const ResumeRequest *grequest, container_resume_request **request) +{ + container_resume_request *tmpreq = (container_resume_request *)util_common_calloc_s( + sizeof(container_resume_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::container_conf_request_from_grpc(const Container_conf_Request *grequest, + struct lcrd_container_conf_request **request) +{ + struct lcrd_container_conf_request *tmpreq = (struct lcrd_container_conf_request *)util_common_calloc_s( + sizeof(struct lcrd_container_conf_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->container_id().empty()) { + tmpreq->name = util_strdup_s(grequest->container_id().c_str()); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::container_conf_response_to_grpc(const struct lcrd_container_conf_response *response, + Container_conf_Response *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + if (response->container_logpath != nullptr) { + gresponse->set_container_logpath(response->container_logpath); + } + gresponse->set_container_logrotate(response->container_logrotate); + if (response->container_logsize != nullptr) { + gresponse->set_container_logsize(response->container_logsize); + } + return 0; +} + +int ContainerServiceImpl::container_rename_request_from_grpc(const RenameRequest *grequest, + struct lcrd_container_rename_request **request) +{ + struct lcrd_container_rename_request *tmpreq = (struct lcrd_container_rename_request *)util_common_calloc_s( + sizeof(struct lcrd_container_rename_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->oldname().empty()) { + tmpreq->old_name = util_strdup_s(grequest->oldname().c_str()); + } + + if (!grequest->newname().empty()) { + tmpreq->new_name = util_strdup_s(grequest->newname().c_str()); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::container_rename_response_to_grpc(const struct lcrd_container_rename_response *response, + RenameResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + if (response->id != nullptr) { + gresponse->set_id(response->id); + } + + return 0; +} + + +int ContainerServiceImpl::update_request_from_grpc(const UpdateRequest *grequest, container_update_request **request) +{ + container_update_request *tmpreq = (container_update_request *)util_common_calloc_s( + sizeof(container_update_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->name = util_strdup_s(grequest->id().c_str()); + } + + if (!grequest->hostconfig().empty()) { + tmpreq->host_config = util_strdup_s(grequest->hostconfig().c_str()); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::update_response_to_grpc(const container_update_response *response, UpdateResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + if (response->id != nullptr) { + gresponse->set_id(response->id); + } + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; +} + +int ContainerServiceImpl::stats_request_from_grpc(const StatsRequest *grequest, container_stats_request **request) +{ + container_stats_request *tmpreq = (container_stats_request *)util_common_calloc_s(sizeof(container_stats_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->runtime().empty()) { + tmpreq->runtime = util_strdup_s(grequest->runtime().c_str()); + } + + if (grequest->containers_size() > 0) { + tmpreq->containers = (char **)util_common_calloc_s(grequest->containers_size() * sizeof(char *)); + if (tmpreq->containers == nullptr) { + ERROR("Out of memory"); + free_container_stats_request(tmpreq); + return -1; + } + for (int i = 0; i < grequest->containers_size(); i++) { + tmpreq->containers[i] = util_strdup_s(grequest->containers(i).c_str()); + tmpreq->containers_len++; + } + } + + tmpreq->all = grequest->all(); + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::stats_response_to_grpc(const container_stats_response *response, StatsResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + if (response->container_stats && response->container_stats_len) { + for (size_t i = 0; i < response->container_stats_len; i++) { + containers::Container_info *stats = gresponse->add_containers(); + if (response->container_stats[i]->id != nullptr) { + stats->set_id(response->container_stats[i]->id); + } + if (response->container_stats[i]->has_pid) { + stats->set_pid(response->container_stats[i]->pid); + } else { + stats->set_pid(-1); + } + stats->set_status((ContainerStatus)response->container_stats[i]->status); + stats->set_pids_current(response->container_stats[i]->pids_current); + stats->set_cpu_use_nanos(response->container_stats[i]->cpu_use_nanos); + stats->set_cpu_system_use(response->container_stats[i]->cpu_system_use); + stats->set_online_cpus(response->container_stats[i]->online_cpus); + stats->set_blkio_read(response->container_stats[i]->blkio_read); + stats->set_blkio_write(response->container_stats[i]->blkio_write); + stats->set_mem_used(response->container_stats[i]->mem_used); + stats->set_mem_limit(response->container_stats[i]->mem_limit); + stats->set_kmem_used(response->container_stats[i]->kmem_used); + stats->set_kmem_limit(response->container_stats[i]->kmem_limit); + } + } + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; +} + +int ContainerServiceImpl::wait_request_from_grpc(const WaitRequest *grequest, container_wait_request **request) +{ + container_wait_request *tmpreq = (container_wait_request *)util_common_calloc_s(sizeof(container_wait_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + tmpreq->condition = grequest->condition(); + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::wait_response_to_grpc(const container_wait_response *response, WaitResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + gresponse->set_exit_code(response->exit_code); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; +} + +int ContainerServiceImpl::events_request_from_grpc(const EventsRequest *grequest, struct lcrd_events_request **request) +{ + struct lcrd_events_request *tmpreq = (struct lcrd_events_request *)util_common_calloc_s( + sizeof(struct lcrd_events_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + tmpreq->storeonly = grequest->storeonly(); + + if (grequest->has_since()) { + protobuf_timestamp_from_grpc(&tmpreq->since, grequest->since()); + } + + if (grequest->has_until()) { + protobuf_timestamp_from_grpc(&tmpreq->until, grequest->until()); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::copy_from_container_request_from_grpc( + const CopyFromContainerRequest *grequest, struct lcrd_copy_from_container_request **request) +{ + struct lcrd_copy_from_container_request *tmpreq = (struct lcrd_copy_from_container_request *)util_common_calloc_s( + sizeof(lcrd_copy_from_container_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + if (!grequest->runtime().empty()) { + tmpreq->runtime = util_strdup_s(grequest->runtime().c_str()); + } + + if (!grequest->srcpath().empty()) { + tmpreq->srcpath = util_strdup_s(grequest->srcpath().c_str()); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::remote_exec_request_from_stream(ServerContext *context, + container_exec_request **request, std::string &errmsg) +{ + const std::multimap init_metadata = context->client_metadata(); + auto iter = init_metadata.find("isulad-remote-exec"); + if (iter != init_metadata.end()) { + char *err = nullptr; + std::string json = std::string(iter->second.data(), iter->second.length()); + *request = container_exec_request_parse_data(json.c_str(), nullptr, &err); + if (*request == nullptr) { + errmsg = "Invalid remote exec container json: "; + errmsg += (err != nullptr) ? err : "unknown"; + free(err); + return -1; + } + } else { + errmsg = "No metadata 'isulad-remote-exec' received"; + return -1; + } + return 0; +} + +void ContainerServiceImpl::add_exec_trailing_metadata(ServerContext *context, container_exec_response *response) +{ + if (response == nullptr) { + context->AddTrailingMetadata("cc", std::to_string((int)LCRD_ERR_MEMOUT)); + return; + } + context->AddTrailingMetadata("cc", std::to_string(response->cc)); + context->AddTrailingMetadata("pid", std::to_string(response->pid)); + context->AddTrailingMetadata("exit_code", std::to_string(response->exit_code)); + if (response->errmsg != nullptr) { + context->AddTrailingMetadata("errmsg", response->errmsg); + } +} + +int ContainerServiceImpl::attach_request_from_stream( + const std::multimap &metadata, + container_attach_request **request) +{ + container_attach_request *tmpreq = (container_attach_request *)util_common_calloc_s( + sizeof(container_attach_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + std::multimap::const_iterator std_kv; + std_kv = metadata.find("container-id"); + if (std_kv == metadata.end()) { + goto cleanup; + } + tmpreq->container_id = util_strdup_s(std::string(std_kv->second.data(), std_kv->second.length()).c_str()); + + std_kv = metadata.find("attach-stdin"); + if (std_kv == metadata.end()) { + goto cleanup; + } + tmpreq->attach_stdin = (std::string(std_kv->second.data(), std_kv->second.length()) == "true"); + + std_kv = metadata.find("attach-stdout"); + if (std_kv == metadata.end()) { + goto cleanup; + } + tmpreq->attach_stdout = (std::string(std_kv->second.data(), std_kv->second.length()) == "true"); + + std_kv = metadata.find("attach-stderr"); + if (std_kv == metadata.end()) { + goto cleanup; + } + tmpreq->attach_stderr = (std::string(std_kv->second.data(), std_kv->second.length()) == "true"); + + *request = tmpreq; + return 0; +cleanup: + free_container_attach_request(tmpreq); + return -1; +} + +void ContainerServiceImpl::add_attach_trailing_metadata(ServerContext *context, container_attach_response *response) +{ + if (response == nullptr) { + context->AddTrailingMetadata("cc", std::to_string((int)LCRD_ERR_MEMOUT)); + return; + } + context->AddTrailingMetadata("cc", std::to_string(response->cc)); + + if (response->errmsg != nullptr) { + context->AddTrailingMetadata("errmsg", response->errmsg); + } +} + +int ContainerServiceImpl::remote_start_request_from_stream( + const std::multimap &metadata, container_start_request **request) +{ + container_start_request *tmpreq = (container_start_request *)util_common_calloc_s(sizeof(container_start_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + std::multimap::const_iterator std_kv; + std_kv = metadata.find("container-id"); + if (std_kv == metadata.end()) { + goto cleanup; + } + tmpreq->id = util_strdup_s(std::string(std_kv->second.data(), std_kv->second.length()).c_str()); + + std_kv = metadata.find("attach-stdin"); + if (std_kv == metadata.end()) { + goto cleanup; + } + tmpreq->attach_stdin = (std::string(std_kv->second.data(), std_kv->second.length()) == "true"); + + std_kv = metadata.find("attach-stdout"); + if (std_kv == metadata.end()) { + goto cleanup; + } + tmpreq->attach_stdout = (std::string(std_kv->second.data(), std_kv->second.length()) == "true"); + + std_kv = metadata.find("attach-stderr"); + if (std_kv == metadata.end()) { + goto cleanup; + } + tmpreq->attach_stderr = (std::string(std_kv->second.data(), std_kv->second.length()) == "true"); + + *request = tmpreq; + return 0; +cleanup: + free_container_start_request(tmpreq); + return -1; +} + +void ContainerServiceImpl::add_start_trailing_metadata(ServerContext *context, container_start_response *response) +{ + if (response == nullptr) { + context->AddTrailingMetadata("cc", std::to_string((int)LCRD_ERR_MEMOUT)); + return; + } + context->AddTrailingMetadata("cc", std::to_string(response->cc)); + + if (response->errmsg != nullptr) { + context->AddTrailingMetadata("errmsg", response->errmsg); + } +} + +int ContainerServiceImpl::export_request_from_grpc(const ExportRequest *grequest, container_export_request **request) +{ + container_export_request *tmpreq = (container_export_request *)util_common_calloc_s( + sizeof(container_export_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + if (!grequest->file().empty()) { + tmpreq->file = util_strdup_s(grequest->file().c_str()); + } + + *request = tmpreq; + return 0; +} + +int ContainerServiceImpl::pack_os_info_to_grpc(const host_info_response *response, InfoResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + if (response->kversion != nullptr) { + gresponse->set_kversion(response->kversion); + } + + if (response->os_type != nullptr) { + gresponse->set_os_type(response->os_type); + } + + if (response->architecture != nullptr) { + gresponse->set_architecture(response->architecture); + } + + if (response->nodename != nullptr) { + gresponse->set_nodename(response->nodename); + } + + gresponse->set_cpus((::google::protobuf::uint32)response->cpus); + + if (response->operating_system != nullptr) { + gresponse->set_operating_system(response->operating_system); + } + + if (response->cgroup_driver != nullptr) { + gresponse->set_cgroup_driver(response->cgroup_driver); + } + + if (response->huge_page_size != nullptr) { + gresponse->set_huge_page_size(response->huge_page_size); + } + + return 0; +} + +int ContainerServiceImpl::pack_proxy_info_to_grpc(const host_info_response *response, InfoResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + if (response->http_proxy != nullptr) { + gresponse->set_http_proxy(response->http_proxy); + } + + if (response->https_proxy != nullptr) { + gresponse->set_https_proxy(response->https_proxy); + } + + if (response->no_proxy != nullptr) { + gresponse->set_no_proxy(response->no_proxy); + } + + return 0; +} + + diff --git a/src/connect/service/grpc/grpc_images_service.cc b/src/connect/service/grpc/grpc_images_service.cc new file mode 100644 index 0000000..9874378 --- /dev/null +++ b/src/connect/service/grpc/grpc_images_service.cc @@ -0,0 +1,421 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc images functions + ******************************************************************************/ + +#include "grpc_images_service.h" + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "grpc_server_tls_auth.h" + +int ImagesServiceImpl::image_list_request_from_grpc(const ListImagesRequest *grequest, + image_list_images_request **request) +{ + image_list_images_request *tmpreq = (image_list_images_request *)util_common_calloc_s( + sizeof(image_list_images_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + *request = tmpreq; + + return 0; +} + +int ImagesServiceImpl::image_list_response_to_grpc(image_list_images_response *response, ListImagesResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + + for (size_t i = 0; i < response->images_len; i++) { + Descriptor *target = nullptr; + Image *image = gresponse->add_images(); + if (response->images[i]->name != nullptr) { + image->set_name(response->images[i]->name); + } + target = new (std::nothrow) Descriptor; + if (target == nullptr) { + ERROR("Out of memory"); + return -1; + } + if (response->images[i]->target->digest != nullptr) { + target->set_digest(response->images[i]->target->digest); + } + Timestamp *timestamp = image->mutable_created_at(); + if (timestamp == nullptr) { + delete target; + ERROR("Out of memory"); + return -1; + } + timestamp->set_seconds(response->images[i]->created_at->seconds); + timestamp->set_nanos(response->images[i]->created_at->nanos); + if (response->images[i]->target->media_type != nullptr) { + target->set_media_type(response->images[i]->target->media_type); + } + target->set_size(response->images[i]->target->size); + image->set_allocated_target(target); + } + + return 0; +} + +int ImagesServiceImpl::image_remove_request_from_grpc(const DeleteImageRequest *grequest, + image_delete_image_request **request) +{ + image_delete_image_request *tmpreq = (image_delete_image_request *)util_common_calloc_s( + sizeof(image_delete_image_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + tmpreq->force = grequest->force(); + if (!grequest->name().empty()) { + tmpreq->image_name = util_strdup_s(grequest->name().c_str()); + } + *request = tmpreq; + + return 0; +} + +int ImagesServiceImpl::image_load_request_from_grpc( + const LoadImageRequest *grequest, image_load_image_request **request) +{ + image_load_image_request *tmpreq = (image_load_image_request *)util_common_calloc_s( + sizeof(image_load_image_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->file().empty()) { + tmpreq->file = util_strdup_s(grequest->file().c_str()); + } + if (!grequest->type().empty()) { + tmpreq->type = util_strdup_s(grequest->type().c_str()); + } + if (!grequest->tag().empty()) { + tmpreq->tag = util_strdup_s(grequest->tag().c_str()); + } + *request = tmpreq; + + return 0; +} + +int ImagesServiceImpl::inspect_request_from_grpc(const InspectImageRequest *grequest, image_inspect_request **request) +{ + image_inspect_request *tmpreq = (image_inspect_request *)util_common_calloc_s( + sizeof(image_inspect_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->id().empty()) { + tmpreq->id = util_strdup_s(grequest->id().c_str()); + } + + tmpreq->bformat = grequest->bformat(); + tmpreq->timeout = grequest->timeout(); + + *request = tmpreq; + return 0; +} + +int ImagesServiceImpl::inspect_response_to_grpc(const image_inspect_response *response, + InspectImageResponse *gresponse) +{ + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + + gresponse->set_cc(response->cc); + if (response->image_json != nullptr) { + gresponse->set_imagejson(response->image_json); + } + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; +} + +Status ImagesServiceImpl::List(ServerContext *context, const ListImagesRequest *request, ListImagesResponse *reply) +{ + auto status = GrpcServerTlsAuth::auth(context, "image_list"); + if (!status.ok()) { + return status; + } + service_callback_t *cb = get_service_callback(); + if (cb == nullptr || cb->image.list == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + image_list_images_request *image_req = nullptr; + int tret = image_list_request_from_grpc(request, &image_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + image_list_images_response *image_res = nullptr; + int ret = cb->image.list(image_req, &image_res); + tret = image_list_response_to_grpc(image_res, reply); + + free_image_list_images_request(image_req); + free_image_list_images_response(image_res); + if (tret != 0) { + reply->set_errmsg(util_strdup_s(errno_to_error_message(LCRD_ERR_INTERNAL))); + reply->set_cc(LCRD_ERR_INPUT); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ImagesServiceImpl::Delete(ServerContext *context, const DeleteImageRequest *request, DeleteImageResponse *reply) +{ + auto status = GrpcServerTlsAuth::auth(context, "image_delete"); + if (!status.ok()) { + return status; + } + service_callback_t *cb = get_service_callback(); + if (cb == nullptr || cb->image.remove == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + image_delete_image_request *image_req = nullptr; + int tret = image_remove_request_from_grpc(request, &image_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + image_delete_image_response *image_res = nullptr; + int ret = cb->image.remove(image_req, &image_res); + tret = response_to_grpc(image_res, reply); + + free_image_delete_image_request(image_req); + free_image_delete_image_response(image_res); + if (tret != 0) { + reply->set_errmsg(util_strdup_s(errno_to_error_message(LCRD_ERR_INTERNAL))); + reply->set_cc(LCRD_ERR_INPUT); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +Status ImagesServiceImpl::Load(ServerContext *context, const LoadImageRequest *request, LoadImageResponse *reply) +{ + auto status = GrpcServerTlsAuth::auth(context, "image_load"); + if (!status.ok()) { + return status; + } + service_callback_t *cb = get_service_callback(); + if (cb == nullptr || cb->image.load == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + image_load_image_request *image_req = nullptr; + int tret = image_load_request_from_grpc(request, &image_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + image_load_image_response *image_res = nullptr; + int ret = cb->image.load(image_req, &image_res); + tret = response_to_grpc(image_res, reply); + + free_image_load_image_request(image_req); + free_image_load_image_response(image_res); + if (tret != 0) { + reply->set_errmsg(util_strdup_s(errno_to_error_message(LCRD_ERR_INTERNAL))); + reply->set_cc(LCRD_ERR_INPUT); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + + return Status::OK; +} + +Status ImagesServiceImpl::Inspect(ServerContext *context, const InspectImageRequest *request, + InspectImageResponse *reply) +{ + int ret, tret; + service_callback_t *cb = nullptr; + image_inspect_request *image_req = nullptr; + image_inspect_response *image_res = nullptr; + + cb = get_service_callback(); + if (cb == nullptr || cb->image.inspect == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + tret = inspect_request_from_grpc(request, &image_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + Status status = GrpcServerTlsAuth::auth(context, "image_inspect"); + if (!status.ok()) { + return status; + } + + ret = cb->image.inspect(image_req, &image_res); + tret = inspect_response_to_grpc(image_res, reply); + + free_image_inspect_request(image_req); + free_image_inspect_response(image_res); + if (tret != 0) { + reply->set_errmsg(errno_to_error_message(LCRD_ERR_INTERNAL)); + reply->set_cc(LCRD_ERR_INTERNAL); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + return Status::OK; +} + +int ImagesServiceImpl::image_login_request_from_grpc( + const LoginRequest *grequest, image_login_request **request) +{ + image_login_request *tmpreq = (image_login_request *)util_common_calloc_s( + sizeof(image_login_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->username().empty()) { + tmpreq->username = util_strdup_s(grequest->username().c_str()); + } + if (!grequest->password().empty()) { + tmpreq->password = util_strdup_s(grequest->password().c_str()); + } + if (!grequest->server().empty()) { + tmpreq->server = util_strdup_s(grequest->server().c_str()); + } + if (!grequest->type().empty()) { + tmpreq->type = util_strdup_s(grequest->type().c_str()); + } + *request = tmpreq; + + return 0; +} + +int ImagesServiceImpl::image_logout_request_from_grpc( + const LogoutRequest *grequest, image_logout_request **request) +{ + image_logout_request *tmpreq = (image_logout_request *)util_common_calloc_s( + sizeof(image_logout_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->server().empty()) { + tmpreq->server = util_strdup_s(grequest->server().c_str()); + } + if (!grequest->type().empty()) { + tmpreq->type = util_strdup_s(grequest->type().c_str()); + } + *request = tmpreq; + + return 0; +} + +Status ImagesServiceImpl::Login(ServerContext *context, const LoginRequest *request, LoginResponse *reply) +{ + auto status = GrpcServerTlsAuth::auth(context, "login"); + if (!status.ok()) { + return status; + } + service_callback_t *cb = get_service_callback(); + if (cb == nullptr || cb->image.login == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + image_login_request *image_req = nullptr; + int tret = image_login_request_from_grpc(request, &image_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + image_login_response *image_res = nullptr; + int ret = cb->image.login(image_req, &image_res); + tret = response_to_grpc(image_res, reply); + + free_image_login_request(image_req); + free_image_login_response(image_res); + if (tret != 0) { + reply->set_errmsg(util_strdup_s(errno_to_error_message(LCRD_ERR_INTERNAL))); + reply->set_cc(LCRD_ERR_INPUT); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + + return Status::OK; +} + +Status ImagesServiceImpl::Logout(ServerContext *context, const LogoutRequest *request, LogoutResponse *reply) +{ + auto status = GrpcServerTlsAuth::auth(context, "logout"); + if (!status.ok()) { + return status; + } + service_callback_t *cb = get_service_callback(); + if (cb == nullptr || cb->image.logout == nullptr) { + return Status(StatusCode::UNIMPLEMENTED, "Unimplemented callback"); + } + + image_logout_request *image_req = nullptr; + int tret = image_logout_request_from_grpc(request, &image_req); + if (tret != 0) { + ERROR("Failed to transform grpc request"); + reply->set_cc(LCRD_ERR_INPUT); + return Status::OK; + } + + image_logout_response *image_res = nullptr; + int ret = cb->image.logout(image_req, &image_res); + tret = response_to_grpc(image_res, reply); + + free_image_logout_request(image_req); + free_image_logout_response(image_res); + if (tret != 0) { + reply->set_errmsg(util_strdup_s(errno_to_error_message(LCRD_ERR_INTERNAL))); + reply->set_cc(LCRD_ERR_INPUT); + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + + return Status::OK; +} + diff --git a/src/connect/service/grpc/grpc_images_service.h b/src/connect/service/grpc/grpc_images_service.h new file mode 100644 index 0000000..b41111a --- /dev/null +++ b/src/connect/service/grpc/grpc_images_service.h @@ -0,0 +1,91 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc images functions + ******************************************************************************/ + +#ifndef _GRPC_IMAGES_SERVICE_H_ +#define _GRPC_IMAGES_SERVICE_H_ + +#include + +#include "images.grpc.pb.h" +#include "callback.h" +#include "error.h" + +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::ServerReader; +using grpc::ServerReaderWriter; +using grpc::ServerWriter; +using grpc::Status; +using grpc::StatusCode; +using google::protobuf::Timestamp; + +using namespace images; +using namespace containerd::types; + +// Implement of images service +class ImagesServiceImpl final : public ImagesService::Service { +public: + ImagesServiceImpl() = default; + ImagesServiceImpl(const ImagesServiceImpl &) = delete; + ImagesServiceImpl &operator=(const ImagesServiceImpl &) = delete; + virtual ~ImagesServiceImpl() = default; + + Status List(ServerContext *context, const ListImagesRequest *request, ListImagesResponse *reply) override; + + Status Delete(ServerContext *context, const DeleteImageRequest *request, DeleteImageResponse *reply) override; + + Status Load(ServerContext *context, const LoadImageRequest *request, LoadImageResponse *reply) override; + + Status Inspect(ServerContext *context, const InspectImageRequest *request, InspectImageResponse *reply) override; + + Status Login(ServerContext *context, const LoginRequest *request, + LoginResponse *reply) override; + + Status Logout(ServerContext *context, const LogoutRequest *request, + LogoutResponse *reply) override; + +private: + template + int response_to_grpc(const T1 *response, T2 *gresponse) + { + if (response == nullptr) { + gresponse->set_cc(LCRD_ERR_MEMOUT); + return 0; + } + gresponse->set_cc(response->cc); + if (response->errmsg != nullptr) { + gresponse->set_errmsg(response->errmsg); + } + return 0; + } + int image_list_request_from_grpc(const ListImagesRequest *grequest, image_list_images_request **request); + + int image_list_response_to_grpc(image_list_images_response *response, ListImagesResponse *gresponse); + + int image_remove_request_from_grpc(const DeleteImageRequest *grequest, image_delete_image_request **request); + + int image_load_request_from_grpc(const LoadImageRequest *grequest, image_load_image_request **request); + + int inspect_request_from_grpc(const InspectImageRequest *grequest, image_inspect_request **request); + + int inspect_response_to_grpc(const image_inspect_response *response, InspectImageResponse *gresponse); + + int image_login_request_from_grpc(const LoginRequest *grequest, image_login_request **request); + + int image_logout_request_from_grpc(const LogoutRequest *grequest, image_logout_request **request); +}; + +#endif /*_GRPC_IMAGES_SERVICE_H_*/ diff --git a/src/connect/service/grpc/grpc_server_tls_auth.cc b/src/connect/service/grpc/grpc_server_tls_auth.cc new file mode 100644 index 0000000..41ca235 --- /dev/null +++ b/src/connect/service/grpc/grpc_server_tls_auth.cc @@ -0,0 +1,60 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: zhangsong + * Create: 2019-04-26 + * Description: provide grpc tls request authorization + ******************************************************************************/ + +#include "grpc_server_tls_auth.h" +#include +#include +#include "http.h" + +namespace AuthorizationPluginConfig { +std::string auth_plugin = ""; +}; + +namespace GrpcServerTlsAuth { +Status auth(ServerContext *context, std::string action) +{ + const std::multimap init_metadata = context->client_metadata(); + auto tls_mode_kv = init_metadata.find("tls_mode"); + if (tls_mode_kv == init_metadata.end()) { + return Status(StatusCode::UNKNOWN, "unkown error"); + } + std::string tls_mode = std::string(tls_mode_kv->second.data(), tls_mode_kv->second.length()); + if (tls_mode == "0") { + return Status::OK; + } + if (AuthorizationPluginConfig::auth_plugin.empty()) { + return Status::OK; + } else if (AuthorizationPluginConfig::auth_plugin == "authz-broker") { + auto username_kv = init_metadata.find("username"); + if (username_kv == init_metadata.end()) { + return Status(StatusCode::UNKNOWN, "unkown error"); + } + std::string username = std::string(username_kv->second.data(), username_kv->second.length()); + char *errmsg = nullptr; + if (authz_http_request(username.c_str(), action.c_str(), &errmsg)) { + std::string err = errmsg; + free(errmsg); + return Status(StatusCode::PERMISSION_DENIED, err); + } else { + if (errmsg != nullptr) { + free(errmsg); + } + } + } else { + return Status(StatusCode::UNIMPLEMENTED, "authorization plugin invalid"); + } + return Status::OK; +} +} // namespace GrpcServerTlsAuth diff --git a/src/connect/service/grpc/grpc_server_tls_auth.h b/src/connect/service/grpc/grpc_server_tls_auth.h new file mode 100644 index 0000000..02c247d --- /dev/null +++ b/src/connect/service/grpc/grpc_server_tls_auth.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: zhangsong + * Create: 2019-04-26 + * Description: provide grpc tls request authorization + ******************************************************************************/ + +#ifndef _GRPC_SERVER_TLS_AUTH_H_ +#define _GRPC_SERVER_TLS_AUTH_H_ +#include +#include + +using grpc::ServerContext; +using grpc::Status; +using grpc::StatusCode; + +namespace AuthorizationPluginConfig { +extern std::string auth_plugin; +}; + +namespace GrpcServerTlsAuth { +Status auth(ServerContext *context, std::string action); +}; + +#endif /* _GRPC_SERVER_TLS_AUTH_H_ */ diff --git a/src/connect/service/grpc/grpc_service.cc b/src/connect/service/grpc/grpc_service.cc new file mode 100644 index 0000000..b0498d2 --- /dev/null +++ b/src/connect/service/grpc/grpc_service.cc @@ -0,0 +1,233 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide grpc server functions + ******************************************************************************/ +#include "grpc_service.h" +#include +#include +#include +#include +#include +#include +#include +#include "grpc_containers_service.h" +#include "grpc_images_service.h" +#include "runtime_runtime_service.h" +#include "runtime_image_service.h" +#include "log.h" +#include "network_plugin.h" +#include "errors.h" +#include "grpc_server_tls_auth.h" + +using grpc::SslServerCredentialsOptions; + +class GRPCServerImpl { +public: + explicit GRPCServerImpl(Network::NetworkPluginConf &conf) : m_conf(conf) {} + + virtual ~GRPCServerImpl() = default; + + int Init(const struct service_arguments *args) + { + if (args == nullptr || args->hosts == nullptr) { + ERROR("lcrd config socket address is empty"); + return -1; + } + + Errors err; + m_runtimeRuntimeService.Init(m_conf, args->json_confs, err); + if (err.NotEmpty()) { + ERROR("Init runtime service failed: %s", err.GetCMessage()); + return -1; + } + auto hosts = std::vector(args->hosts, + args->hosts + args->hosts_len); + for (auto host : hosts) { + if (host.find("tcp://") == 0) { + m_tcpPath.push_back(host.erase(0, std::string("tcp://").length())); + } else { + m_socketPath.push_back(host); + } + } + + if (ListeningPort(args, err)) { + return -1; + } + + // Register "service" as the instance through which we'll communicate with + // clients. In this case it corresponds to an *synchronous* service. + m_builder.RegisterService(&m_containerService); + m_builder.RegisterService(&m_imagesService); + m_builder.RegisterService(&m_runtimeRuntimeService); + m_builder.RegisterService(&m_runtimeImageService); + + // Finally assemble the server. + m_server = m_builder.BuildAndStart(); + if (m_server == nullptr) { + ERROR("Failed to build and start grpc m_server"); + return -1; + } + return 0; + } + + void Wait(void) + { + // Wait for the server to shutdown. Note that some other thread must be + // responsible for shutting down the server for this call to ever return. + m_server->Wait(); + m_runtimeRuntimeService.Wait(); + } + + void Shutdown(void) + { + // Shutdown daemon, this operation should remove socket file. + for (const auto &address : m_socketPath) { + if (address.find(UNIX_SOCKET_PREFIX) == 0) { + if (unlink(address.c_str() + strlen(UNIX_SOCKET_PREFIX)) < 0 && errno != ENOENT) { + WARN("Failed to remove '%s':%s", address.c_str(), strerror(errno)); + } + } + } + m_runtimeRuntimeService.Shutdown(); + } + +private: + int ListeningPort(const struct service_arguments *args, Errors &err) + { + if (args->json_confs->tls) { + if (args->json_confs->authorization_plugin != nullptr) { + AuthorizationPluginConfig::auth_plugin = args->json_confs->authorization_plugin; + } + + std::string key = ReadTextFile(args->json_confs->tls_config->key_file, err); + if (err.NotEmpty()) { + return -1; + } + std::string cert = ReadTextFile(args->json_confs->tls_config->cert_file, err); + if (err.NotEmpty()) { + return -1; + } + std::string root { "" }; + if (args->json_confs->tls_verify) { + root = ReadTextFile(args->json_confs->tls_config->ca_file, err); + if (err.NotEmpty()) { + return -1; + } + } + + grpc::SslServerCredentialsOptions::PemKeyCertPair key_cert { key, cert }; + grpc::SslServerCredentialsOptions sslOps { + args->json_confs->tls_verify ? GRPC_SSL_REQUEST_AND_REQUIRE_CLIENT_CERTIFICATE_AND_VERIFY + : GRPC_SSL_DONT_REQUEST_CLIENT_CERTIFICATE + }; + // Daemon modes : if tls_verify is set, Authenticate clients, otherwise do not + // --tlsverify --tlscacert, --tlscert, --tlskey set: Authenticate clients + // --tls, --tlscert, --tlskey: Do not authenticate clients + sslOps.pem_root_certs = root; + sslOps.pem_key_cert_pairs.push_back(key_cert); + // Listen on the given tcp address with ssl/tls authentication mechanism. + for (const auto &address : m_tcpPath) { + m_builder.AddListeningPort(address, grpc::SslServerCredentials(sslOps)); + INFO("Server listening on %s", address.c_str()); + } + } else { + // Listen on the given tcp address without any authentication mechanism. + for (const auto &address : m_tcpPath) { + m_builder.AddListeningPort(address, grpc::InsecureServerCredentials()); + INFO("Server listening on %s", address.c_str()); + } + } + // Listen on the given socket address without any authentication mechanism. + for (const auto &address : m_socketPath) { + m_builder.AddListeningPort(address, grpc::InsecureServerCredentials()); + INFO("Server listening on %s", address.c_str()); + } + + return 0; + } + + std::string ReadTextFile(const std::string &file, Errors &err) + { + if (file.empty()) { + return ""; + } + std::ifstream context(file.c_str(), std::ios::in); + if (!context) { + err.SetError("file does not exist: " + file); + return ""; + } + std::stringstream ss; + if (context.is_open()) { + ss << context.rdbuf(); + context.close(); + } + return ss.str(); + } + +private: + Network::NetworkPluginConf m_conf; + ContainerServiceImpl m_containerService; + ImagesServiceImpl m_imagesService; + RuntimeRuntimeServiceImpl m_runtimeRuntimeService; + RuntimeImageServiceImpl m_runtimeImageService; + ServerBuilder m_builder; + std::vector m_tcpPath; + std::vector m_socketPath; + std::unique_ptr m_server; +}; + +GRPCServerImpl *g_grpcserver { nullptr }; + +int grpc_server_init(const struct service_arguments *args) +{ + if (args == nullptr) { + return -1; + } + + if (g_grpcserver != nullptr) { + return 0; + } + + /* note: get config from args, now use defaults */ + Network::NetworkPluginConf conf; + if (args != nullptr && args->json_confs != nullptr) { + if (args->json_confs->network_plugin != nullptr) { + conf.SetPluginName(args->json_confs->network_plugin); + } + if (args->json_confs->cni_bin_dir != nullptr) { + conf.SetPluginBinDir(args->json_confs->cni_bin_dir); + } + if (args->json_confs->cni_conf_dir != nullptr) { + conf.SetPluginConfDir(args->json_confs->cni_conf_dir); + } + } + g_grpcserver = new (std::nothrow) GRPCServerImpl(conf); + if (g_grpcserver == nullptr) { + return -1; + } + if (g_grpcserver->Init(args) != 0) { + return -1; + } + + return 0; +} + +void grpc_server_wait(void) +{ + g_grpcserver->Wait(); +} + +void grpc_server_shutdown(void) +{ + g_grpcserver->Shutdown(); +} diff --git a/src/connect/service/grpc/grpc_service.h b/src/connect/service/grpc/grpc_service.h new file mode 100644 index 0000000..f9261b4 --- /dev/null +++ b/src/connect/service/grpc/grpc_service.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide grpc service definition + ******************************************************************************/ +#ifndef __GRPC_SERVICE_H +#define __GRPC_SERVICE_H + +#include +#include "service_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int grpc_server_init(const struct service_arguments *args); + +void grpc_server_wait(void); + +void grpc_server_shutdown(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __GRPC_SERVICE_H */ diff --git a/src/connect/service/grpc/runtime_image_service.cc b/src/connect/service/grpc/runtime_image_service.cc new file mode 100644 index 0000000..c2b217b --- /dev/null +++ b/src/connect/service/grpc/runtime_image_service.cc @@ -0,0 +1,113 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide runtime image functions + ******************************************************************************/ + +#include "runtime_image_service.h" +#include +#include +#include +#include "cri_helpers.h" + +#include "log.h" + +grpc::Status RuntimeImageServiceImpl::PullImage(grpc::ServerContext *context, + const runtime::PullImageRequest *request, + runtime::PullImageResponse *reply) +{ + Errors error; + std::string imageRef = rService.PullImage(request->image(), request->auth(), error); + if (!error.Empty() || imageRef.empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + reply->set_image_ref(imageRef); + return grpc::Status::OK; +} + +grpc::Status RuntimeImageServiceImpl::ListImages(grpc::ServerContext *context, + const runtime::ListImagesRequest *request, + runtime::ListImagesResponse *reply) +{ + std::vector> images; + Errors error; + + rService.ListImages(request->filter(), &images, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + for (auto iter = images.begin(); iter != images.end(); iter++) { + runtime::Image *image = reply->add_images(); + if (image == nullptr) { + return grpc::Status(grpc::StatusCode::UNKNOWN, "Out of memory"); + } + *image = *(iter->get()); + } + return grpc::Status::OK; +} + +grpc::Status RuntimeImageServiceImpl::ImageStatus(grpc::ServerContext *context, + const runtime::ImageStatusRequest *request, + runtime::ImageStatusResponse *reply) +{ + std::unique_ptr image_info = nullptr; + Errors error; + + image_info = rService.ImageStatus(request->image(), error); + if (!error.Empty() && !CRIHelpers::IsImageNotFoundError(error.GetMessage())) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + if (image_info != nullptr) { + runtime::Image *image = reply->mutable_image(); + *image = *image_info; + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeImageServiceImpl::ImageFsInfo(grpc::ServerContext *context, + const runtime::ImageFsInfoRequest *request, + runtime::ImageFsInfoResponse *reply) +{ + std::vector> usages; + Errors error; + + rService.ImageFsInfo(&usages, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + for (auto iter = usages.begin(); iter != usages.end(); ++iter) { + runtime::FilesystemUsage *fs_info = reply->add_image_filesystems(); + if (fs_info == nullptr) { + return grpc::Status(grpc::StatusCode::UNKNOWN, "Out of memory"); + } + *fs_info = *(iter->get()); + } + return grpc::Status::OK; +} + +grpc::Status RuntimeImageServiceImpl::RemoveImage(grpc::ServerContext *context, + const runtime::RemoveImageRequest *request, + runtime::RemoveImageResponse *reply) +{ + Errors error; + + rService.RemoveImage(request->image(), error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + return grpc::Status::OK; +} + diff --git a/src/connect/service/grpc/runtime_image_service.h b/src/connect/service/grpc/runtime_image_service.h new file mode 100644 index 0000000..17001f8 --- /dev/null +++ b/src/connect/service/grpc/runtime_image_service.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide runtime image functions + ******************************************************************************/ + +#ifndef _RUNTIME_IMAGE_SERVICES_IMPL_H_ +#define _RUNTIME_IMAGE_SERVICES_IMPL_H_ + +#include "api.grpc.pb.h" +#include "callback.h" +#include "cri_image_service.h" + +// Implement of runtime RuntimeService +class RuntimeImageServiceImpl: public + runtime::ImageService::Service { +public: + grpc::Status PullImage(grpc::ServerContext *context, + const runtime::PullImageRequest *request, + runtime::PullImageResponse *reply) override; + grpc::Status ListImages(grpc::ServerContext *context, + const runtime::ListImagesRequest *request, + runtime::ListImagesResponse *reply) override; + grpc::Status ImageStatus(grpc::ServerContext *context, + const runtime::ImageStatusRequest *request, + runtime::ImageStatusResponse *reply) override; + + grpc::Status ImageFsInfo(grpc::ServerContext *context, + const runtime::ImageFsInfoRequest *request, + runtime::ImageFsInfoResponse *reply) override; + grpc::Status RemoveImage(grpc::ServerContext *context, + const runtime::RemoveImageRequest *request, + runtime::RemoveImageResponse *reply) override; + +private: + CRIImageServiceImpl rService; +}; +#endif /* _RUNTIME_IMAGE_SERVICES_IMPL_H_ */ + diff --git a/src/connect/service/grpc/runtime_runtime_service.cc b/src/connect/service/grpc/runtime_runtime_service.cc new file mode 100644 index 0000000..440eadd --- /dev/null +++ b/src/connect/service/grpc/runtime_runtime_service.cc @@ -0,0 +1,327 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide runtime functions + ******************************************************************************/ +#include "runtime_runtime_service.h" +#include +#include +#include +#include "stream_server.h" +#include "route_callback_register.h" +#include "log.h" + +void RuntimeRuntimeServiceImpl::Init(Network::NetworkPluginConf mConf, isulad_daemon_configs *config, Errors &err) +{ + std::string podSandboxImage; + if (config->pod_sandbox_image != nullptr) { + podSandboxImage = config->pod_sandbox_image; + } + rService.Init(mConf, podSandboxImage, err); + if (err.NotEmpty()) { + ERROR("%s", err.GetMessage().c_str()); + return; + } + websocket_server_init(err); + if (err.NotEmpty()) { + ERROR("%s", err.GetMessage().c_str()); + return; + } +} + +void RuntimeRuntimeServiceImpl::Wait() +{ + websocket_server_wait(); +} + + +void RuntimeRuntimeServiceImpl::Shutdown() +{ + websocket_server_shutdown(); +} + +grpc::Status RuntimeRuntimeServiceImpl::Version(grpc::ServerContext *context, const runtime::VersionRequest *request, + runtime::VersionResponse *reply) +{ + Errors error; + rService.Version(request->version(), reply, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::CreateContainer(grpc::ServerContext *context, + const runtime::CreateContainerRequest *request, + runtime::CreateContainerResponse *reply) +{ + Errors error; + std::string responseID = rService.CreateContainer(request->pod_sandbox_id(), request->config(), + request->sandbox_config(), error); + if (!error.Empty() || responseID.empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + reply->set_container_id(responseID); + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::StartContainer(grpc::ServerContext *context, + const runtime::StartContainerRequest *request, + runtime::StartContainerResponse *reply) +{ + Errors error; + rService.StartContainer(request->container_id(), error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::StopContainer(grpc::ServerContext *context, + const runtime::StopContainerRequest *request, + runtime::StopContainerResponse *reply) +{ + Errors error; + rService.StopContainer(request->container_id(), (int64_t)request->timeout(), error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::RemoveContainer(grpc::ServerContext *context, + const runtime::RemoveContainerRequest *request, + runtime::RemoveContainerResponse *reply) +{ + Errors error; + rService.RemoveContainer(request->container_id(), error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::ListContainers(grpc::ServerContext *context, + const runtime::ListContainersRequest *request, + runtime::ListContainersResponse *reply) +{ + Errors error; + std::vector> containers; + rService.ListContainers(request->has_filter() ? &request->filter() : nullptr, &containers, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + for (auto iter = containers.begin(); iter != containers.end(); ++iter) { + runtime::Container *container = reply->add_containers(); + if (container == nullptr) { + return grpc::Status(grpc::StatusCode::UNKNOWN, "Out of memory"); + } + *container = *(iter->get()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::ListContainerStats(grpc::ServerContext *context, + const runtime::ListContainerStatsRequest *request, + runtime::ListContainerStatsResponse *reply) +{ + Errors error; + + std::vector> containers; + rService.ListContainerStats(request->has_filter() ? &request->filter() : nullptr, &containers, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + for (auto iter = containers.begin(); iter != containers.end(); ++iter) { + runtime::ContainerStats *container = reply->add_stats(); + if (container == nullptr) { + return grpc::Status(grpc::StatusCode::UNKNOWN, "Out of memory"); + } + *container = *(iter->get()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::ContainerStatus(grpc::ServerContext *context, + const runtime::ContainerStatusRequest *request, + runtime::ContainerStatusResponse *reply) +{ + Errors error; + std::unique_ptr contStatus = rService.ContainerStatus(request->container_id(), error); + if (!error.Empty() || !contStatus) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + *(reply->mutable_status()) = *contStatus; + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::ExecSync(grpc::ServerContext *context, const runtime::ExecSyncRequest *request, + runtime::ExecSyncResponse *reply) +{ + Errors error; + rService.ExecSync(request->container_id(), request->cmd(), request->timeout(), reply, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::RunPodSandbox( + grpc::ServerContext *context, const runtime::RunPodSandboxRequest *request, + runtime::RunPodSandboxResponse *reply) +{ + Errors error; + std::string responseID = rService.RunPodSandbox(request->config(), error); + if (!error.Empty() || responseID.empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + reply->set_pod_sandbox_id(responseID); + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::StopPodSandbox( + grpc::ServerContext *context, const runtime::StopPodSandboxRequest *request, + runtime::StopPodSandboxResponse *reply) +{ + Errors error; + rService.StopPodSandbox(request->pod_sandbox_id(), error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::RemovePodSandbox( + grpc::ServerContext *context, const runtime::RemovePodSandboxRequest *request, + runtime::RemovePodSandboxResponse *reply) +{ + Errors error; + rService.RemovePodSandbox(request->pod_sandbox_id(), error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::PodSandboxStatus( + grpc::ServerContext *context, const runtime::PodSandboxStatusRequest *request, + runtime::PodSandboxStatusResponse *reply) +{ + Errors error; + std::unique_ptr podStatus; + podStatus = rService.PodSandboxStatus(request->pod_sandbox_id(), error); + if (!error.Empty() || !podStatus) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + *(reply->mutable_status()) = *podStatus; + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::ListPodSandbox( + grpc::ServerContext *context, const runtime::ListPodSandboxRequest *request, + runtime::ListPodSandboxResponse *reply) +{ + Errors error; + std::vector> pods; + rService.ListPodSandbox(request->has_filter() ? &request->filter() : nullptr, &pods, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + for (auto iter = pods.begin(); iter != pods.end(); ++iter) { + runtime::PodSandbox *pod = reply->add_items(); + if (pod == nullptr) { + return grpc::Status(grpc::StatusCode::UNKNOWN, "Out of memory"); + } + *pod = *(iter->get()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::UpdateContainerResources( + grpc::ServerContext *context, const runtime::UpdateContainerResourcesRequest *request, + runtime::UpdateContainerResourcesResponse *reply) +{ + Errors error; + rService.UpdateContainerResources(request->container_id(), request->linux(), error); + if (error.NotEmpty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + + +grpc::Status RuntimeRuntimeServiceImpl::Exec(grpc::ServerContext *context, const runtime::ExecRequest *request, + runtime::ExecResponse *response) +{ + Errors error; + rService.Exec(*request, response, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::Attach(grpc::ServerContext *context, const runtime::AttachRequest *request, + runtime::AttachResponse *response) +{ + Errors error; + rService.Attach(*request, response, error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::UpdateRuntimeConfig(grpc::ServerContext *context, + const runtime::UpdateRuntimeConfigRequest *request, + runtime::UpdateRuntimeConfigResponse *reply) +{ + Errors error; + rService.UpdateRuntimeConfig(request->runtime_config(), error); + if (!error.Empty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + + return grpc::Status::OK; +} + +grpc::Status RuntimeRuntimeServiceImpl::Status(grpc::ServerContext *context, const runtime::StatusRequest *request, + runtime::StatusResponse *reply) +{ + Errors error; + std::unique_ptr status = rService.Status(error); + if (status == nullptr || error.NotEmpty()) { + return grpc::Status(grpc::StatusCode::UNKNOWN, error.GetMessage()); + } + *(reply->mutable_status()) = *status; + + return grpc::Status::OK; +} + diff --git a/src/connect/service/grpc/runtime_runtime_service.h b/src/connect/service/grpc/runtime_runtime_service.h new file mode 100644 index 0000000..cd8815a --- /dev/null +++ b/src/connect/service/grpc/runtime_runtime_service.h @@ -0,0 +1,94 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container runtime functions + ******************************************************************************/ + +#ifndef _RUNTIME_RUNTIME_SERVICES_IMPL_H_ +#define _RUNTIME_RUNTIME_SERVICES_IMPL_H_ + +#include "api.grpc.pb.h" +#include "callback.h" +#include "cri_runtime_service.h" +#include "network_plugin.h" +#include "isulad_daemon_configs.h" +#include "errors.h" + +// Implement of runtime RuntimeService +class RuntimeRuntimeServiceImpl : public runtime::RuntimeService::Service { +public: + void Init(Network::NetworkPluginConf mConf, isulad_daemon_configs *config, Errors &err); + void Wait(); + void Shutdown(); + grpc::Status Version(grpc::ServerContext *context, const runtime::VersionRequest *request, + runtime::VersionResponse *reply) override; + + grpc::Status CreateContainer(grpc::ServerContext *context, const runtime::CreateContainerRequest *request, + runtime::CreateContainerResponse *reply) override; + + grpc::Status StartContainer(grpc::ServerContext *context, const runtime::StartContainerRequest *request, + runtime::StartContainerResponse *reply) override; + + grpc::Status StopContainer(grpc::ServerContext *context, const runtime::StopContainerRequest *request, + runtime::StopContainerResponse *reply) override; + + grpc::Status RemoveContainer(grpc::ServerContext *context, const runtime::RemoveContainerRequest *request, + runtime::RemoveContainerResponse *reply) override; + + grpc::Status ListContainers(grpc::ServerContext *context, const runtime::ListContainersRequest *request, + runtime::ListContainersResponse *reply) override; + + grpc::Status ListContainerStats(grpc::ServerContext *context, const runtime::ListContainerStatsRequest *request, + runtime::ListContainerStatsResponse *reply) override; + + grpc::Status ContainerStatus(grpc::ServerContext *context, const runtime::ContainerStatusRequest *request, + runtime::ContainerStatusResponse *reply) override; + + grpc::Status ExecSync(grpc::ServerContext *context, const runtime::ExecSyncRequest *request, + runtime::ExecSyncResponse *reply) override; + + grpc::Status RunPodSandbox(grpc::ServerContext *context, const runtime::RunPodSandboxRequest *request, + runtime::RunPodSandboxResponse *reply) override; + + grpc::Status StopPodSandbox(grpc::ServerContext *context, const runtime::StopPodSandboxRequest *request, + runtime::StopPodSandboxResponse *reply) override; + + grpc::Status RemovePodSandbox(grpc::ServerContext *context, const runtime::RemovePodSandboxRequest *request, + runtime::RemovePodSandboxResponse *reply) override; + + grpc::Status PodSandboxStatus(grpc::ServerContext *context, const runtime::PodSandboxStatusRequest *request, + runtime::PodSandboxStatusResponse *reply) override; + + grpc::Status ListPodSandbox(grpc::ServerContext *context, const runtime::ListPodSandboxRequest *request, + runtime::ListPodSandboxResponse *reply) override; + + grpc::Status UpdateContainerResources(grpc::ServerContext *context, + const runtime::UpdateContainerResourcesRequest *request, + runtime::UpdateContainerResourcesResponse *reply) override; + + grpc::Status Exec(grpc::ServerContext *context, const runtime::ExecRequest *request, + runtime::ExecResponse *response) override; + + grpc::Status Attach(grpc::ServerContext *context, const runtime::AttachRequest *request, + runtime::AttachResponse *response) override; + + grpc::Status UpdateRuntimeConfig(grpc::ServerContext *context, const runtime::UpdateRuntimeConfigRequest *request, + runtime::UpdateRuntimeConfigResponse *reply) override; + + grpc::Status Status(grpc::ServerContext *context, const runtime::StatusRequest *request, + runtime::StatusResponse *reply) override; + +private: + CRIRuntimeServiceImpl rService; +}; + +#endif /* _RUNTIME_RUNTIME_SERVICES_IMPL_H_ */ diff --git a/src/connect/service/rest/CMakeLists.txt b/src/connect/service/rest/CMakeLists.txt new file mode 100644 index 0000000..4583c31 --- /dev/null +++ b/src/connect/service/rest/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_service_rest_srcs) + +set(SERVICE_REST_SRCS + ${local_service_rest_srcs} + PARENT_SCOPE + ) diff --git a/src/connect/service/rest/rest_containers_service.c b/src/connect/service/rest/rest_containers_service.c new file mode 100644 index 0000000..a565e49 --- /dev/null +++ b/src/connect/service/rest/rest_containers_service.c @@ -0,0 +1,1227 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful service functions + ******************************************************************************/ +#include +#include + +#include "log.h" +#include "utils.h" +#include "error.h" +#include "securec.h" +#include "callback.h" +#include "container.rest.h" +#include "rest_service_common.h" +#include "rest_containers_service.h" + +struct rest_handle_st { + const char *name; + void *(*request_parse_data)(const char *jsondata, struct parser_context *ctx, parser_error *err); + int (*request_check)(void *reqeust); +}; + +/* update request check */ +static int update_request_check(void *req) +{ + int ret = 0; + container_update_request *req_update = (container_update_request *)req; + if (req_update->name == NULL) { + DEBUG("container name required!"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* restart request check */ +static int restart_request_check(void *req) +{ + int ret = 0; + container_restart_request *req_restart = (container_restart_request *)req; + if (req_restart->id == NULL) { + ERROR("Container name required!"); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* version request check */ +static int version_request_check(void *req) +{ + return 0; +} + +/* stop request check */ +static int stop_request_check(void *req) +{ + int ret = 0; + container_stop_request *req_stop = (container_stop_request *)req; + if (req_stop->id == NULL) { + DEBUG("container name required!"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* wait request check */ +static int wait_request_check(void *req) +{ + int ret = 0; + container_wait_request *req_wait = (container_wait_request *)req; + if (req_wait->id == NULL) { + DEBUG("container name error"); + ret = -1; + goto out; + } + + if (req_wait->condition != WAIT_CONDITION_REMOVED && req_wait->condition != WAIT_CONDITION_STOPPED) { + ERROR("container wait condition error"); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* kill request check */ +static int kill_request_check(void *req) +{ + int ret = 0; + container_kill_request *req_kill = (container_kill_request *)req; + if (req_kill->id == NULL) { + DEBUG("container name required!"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* remove request check */ +static int remove_request_check(void *req) +{ + int ret = 0; + container_delete_request *req_rm = (container_delete_request *)req; + if (req_rm->id == NULL) { + DEBUG("container name required!"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* exec request check */ +static int exec_request_check(void *req) +{ + int ret = 0; + container_exec_request *req_exec = (container_exec_request *)req; + if (req_exec->container_id == NULL) { + DEBUG("Missing container name in the request!"); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* list request check */ +static int list_request_check(void *req) +{ + int ret = 0; + + return ret; +} + +/* container conf response check */ +static int container_conf_request_check(void *req) +{ + int ret = 0; + struct lcrd_container_conf_request *req_conf = (struct lcrd_container_conf_request *)req; + if (req_conf->name == NULL) { + DEBUG("container name error"); + ret = -1; + goto out; + } + +out: + return ret; +} +/* create request check */ +static int create_request_check(void *req) +{ + int ret = 0; + container_create_request *req_create = (container_create_request *)req; + if (req_create->rootfs == NULL && req_create->image == NULL) { + DEBUG("container image or rootfs error"); + ret = -1; + goto out; + } + + if (req_create->runtime == NULL) { + DEBUG("recive NULL Request runtime"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* container inspect request check */ +static int container_inspect_request_check(void *req) +{ + int ret = 0; + container_inspect_request *req_inspect = (container_inspect_request *)req; + if (req_inspect->id == NULL) { + DEBUG("container name required!"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* start request check */ +static int start_request_check(void *req) +{ + int ret = 0; + container_start_request *req_start = (container_start_request *)req; + if (req_start->id == NULL) { + DEBUG("container name error"); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* evhtp send create repsponse */ +static void evhtp_send_create_repsponse(evhtp_request_t *req, container_create_response *response, int rescode) +{ + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate create response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + responsedata = container_create_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Create: failed to generate request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +static struct rest_handle_st g_rest_handle[] = { + { + .name = ContainerServiceCreate, + .request_parse_data = (void *)container_create_request_parse_data, + .request_check = create_request_check, + }, + { + .name = ContainerServiceStart, + .request_parse_data = (void *)container_start_request_parse_data, + .request_check = start_request_check, + }, + { + .name = ContainerServiceRestart, + .request_parse_data = (void *)container_restart_request_parse_data, + .request_check = restart_request_check, + }, + { + .name = ContainerServiceStop, + .request_parse_data = (void *)container_stop_request_parse_data, + .request_check = stop_request_check, + }, + { + .name = ContainerServiceVersion, + .request_parse_data = (void *)container_version_request_parse_data, + .request_check = version_request_check, + }, + { + .name = ContainerServiceUpdate, + .request_parse_data = (void *)container_update_request_parse_data, + .request_check = update_request_check, + }, + { + .name = ContainerServiceKill, + .request_parse_data = (void *)container_kill_request_parse_data, + .request_check = kill_request_check, + }, + { + .name = ContainerServiceExec, + .request_parse_data = (void *)container_exec_request_parse_data, + .request_check = exec_request_check, + }, + { + .name = ContainerServiceRemove, + .request_parse_data = (void *)container_delete_request_parse_data, + .request_check = remove_request_check, + }, + { + .name = ContainerServiceList, + .request_parse_data = (void *)container_list_request_parse_data, + .request_check = list_request_check, + }, + { + .name = ContainerServiceWait, + .request_parse_data = (void *)container_wait_request_parse_data, + .request_check = wait_request_check, + }, + { + .name = ContainerServiceConf, + .request_parse_data = (void *)container_conf_request_parse_data, + .request_check = container_conf_request_check, + }, + { + .name = ContainerServiceInspect, + .request_parse_data = (void *)container_inspect_request_parse_data, + .request_check = container_inspect_request_check, + }, +}; + +static int action_request_from_rest(evhtp_request_t *req, void **request, const char *req_type) +{ + char *body = NULL; + size_t body_len; + int ret = 0; + parser_error err = NULL; + int array_size = 0; + int i = 0; + struct rest_handle_st *ops = NULL; + + array_size = sizeof(g_rest_handle) / sizeof(g_rest_handle[0]); + for (i = 0; i < array_size; i++) { + if (strcmp(req_type, g_rest_handle[i].name) == 0) { + ops = &g_rest_handle[i]; + break; + } + } + if (i >= array_size) { + ERROR("Unknown action type"); + return -1; + } + + if (get_body(req, &body_len, &body) != 0) { + ERROR("Failed to get body"); + return -1; + } + + *request = (void *)ops->request_parse_data(body, NULL, &err); + if (*request == NULL) { + ERROR("Invalid request body:%s", err); + ret = -1; + goto out; + } + + if (ops->request_check(*request) < 0) { + ret = -1; + goto out; + } +out: + put_body(body); + if (err != NULL) { + free(err); + } + return ret; +} + +/* evhtp send start repsponse */ +static void evhtp_send_start_repsponse(evhtp_request_t *req, container_start_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate start response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + responsedata = container_start_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate start request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* evhtp send list repsponse */ +static void evhtp_send_list_repsponse(evhtp_request_t *req, container_list_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate inspect response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + responsedata = container_list_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate list request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* evhtp send container conf repsponse */ +static void evhtp_send_container_conf_repsponse(evhtp_request_t *req, container_conf_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + responsedata = container_conf_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate container_conf request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* container conf response to rest */ +static int container_conf_response_to_rest(container_conf_response *cresponse, + struct lcrd_container_conf_response *response) +{ + if (response == NULL) { + cresponse->cc = LCRD_ERR_MEMOUT; + return 0; + } + cresponse->cc = response->cc; + if (response->errmsg) { + cresponse->errmsg = util_strdup_s(response->errmsg); + } + if (response->container_logpath) { + cresponse->container_logpath = util_strdup_s(response->container_logpath); + } + cresponse->container_logrotate = response->container_logrotate; + if (response->container_logsize) { + cresponse->container_logsize = util_strdup_s(response->container_logsize); + } + return 0; +} + +/* evhtp send wait repsponse */ +static void evhtp_send_wait_repsponse(evhtp_request_t *req, container_wait_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + responsedata = container_wait_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate wait request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest create cb */ +static void rest_create_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_create_response *cresponse = NULL; + container_create_request *crequest = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.create == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceCreate); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.create(crequest, &cresponse); + + evhtp_send_create_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_create_response(cresponse); + free_container_create_request(crequest); +} + +/* rest start cb */ +static void rest_start_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_start_response *cresponse = NULL; + container_start_request *crequest = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.start == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceStart); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.start(crequest, &cresponse, -1, NULL, NULL); + + evhtp_send_start_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_start_request(crequest); + free_container_start_response(cresponse); +} + +/* rest container conf cb */ +static void rest_container_conf_cb(evhtp_request_t *req, void *arg) +{ + int ret, tret; + service_callback_t *cb = NULL; + struct lcrd_container_conf_request *lcrdreq = NULL; + struct lcrd_container_conf_response *lcrdres = NULL; + container_conf_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.conf == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + cresponse = util_common_calloc_s(sizeof(container_conf_response)); + if (cresponse == NULL) { + evhtp_send_reply(req, EVHTP_RES_ERROR); + return; + } + + tret = action_request_from_rest(req, (void **)&lcrdreq, ContainerServiceConf); + if (tret < 0) { + ERROR("Bad request"); + cresponse->cc = LCRD_ERR_EXEC; + evhtp_send_container_conf_repsponse(req, cresponse, EVHTP_RES_SERVERR); + goto out; + } + + ret = cb->container.conf(lcrdreq, &lcrdres); + + tret = container_conf_response_to_rest(cresponse, lcrdres); + if (tret) { + ERROR("Failed to translate response to rest,operation is %s", ret ? "failed" : "success"); + cresponse->cc = LCRD_ERR_EXEC; + if (cresponse->errmsg == NULL) { + cresponse->errmsg = util_strdup_s(errno_to_error_message(LCRD_ERR_INTERNAL)); + } + } + evhtp_send_container_conf_repsponse(req, cresponse, EVHTP_RES_OK); +out: + lcrd_container_conf_request_free(lcrdreq); + lcrd_container_conf_response_free(lcrdres); + free_container_conf_response(cresponse); +} + +/* rest wait cb */ +static void rest_wait_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_wait_request *crequest = NULL; + container_wait_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.wait == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceWait); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.wait(crequest, &cresponse); + + evhtp_send_wait_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_wait_request(crequest); + free_container_wait_response(cresponse); +} + +/* evhtp send stop repsponse */ +static void evhtp_send_stop_repsponse(evhtp_request_t *req, container_stop_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate stop response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + responsedata = container_stop_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate stop request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest stop cb */ +static void rest_stop_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_stop_request *crequest = NULL; + container_stop_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.stop == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceStop); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.stop(crequest, &cresponse); + + evhtp_send_stop_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_stop_response(cresponse); + free_container_stop_request(crequest); +} + +/* evhtp send restart response */ +static void evhtp_send_restart_response(evhtp_request_t *req, container_restart_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate restart response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + responsedata = container_restart_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate restart response json: %s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest restart cb */ +static void rest_restart_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_restart_request *crequest = NULL; + container_restart_response *cresponse = NULL; + + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + cb = get_service_callback(); + if (cb == NULL || cb->container.restart == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceRestart); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.restart(crequest, &cresponse); + + evhtp_send_restart_response(req, cresponse, EVHTP_RES_OK); +out: + free_container_restart_request(crequest); + free_container_restart_response(cresponse); +} + +/* evhtp send version repsponse */ +static void evhtp_send_version_repsponse(evhtp_request_t *req, container_version_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + responsedata = container_version_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate version request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest version cb */ +static void rest_version_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_version_request *crequest = NULL; + container_version_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.version == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceVersion); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.version(crequest, &cresponse); + + evhtp_send_version_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_version_request(crequest); + free_container_version_response(cresponse); +} + +/* evhtp send update repsponse */ +static void evhtp_send_update_repsponse(evhtp_request_t *req, container_update_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Invalid NULL response"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + responsedata = container_update_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate update request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest update cb */ +static void rest_update_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_update_request *container_req = NULL; + container_update_response *container_res = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.update == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&container_req, ContainerServiceUpdate); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.update(container_req, &container_res); + evhtp_send_update_repsponse(req, container_res, EVHTP_RES_OK); + +out: + free_container_update_request(container_req); + free_container_update_response(container_res); +} + +/* evhtp send kill repsponse */ +static void evhtp_send_kill_repsponse(evhtp_request_t *req, container_kill_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate kill response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + responsedata = container_kill_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate kill request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest kill cb */ +static void rest_kill_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_kill_request *crequest = NULL; + container_kill_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.kill == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceKill); + if (tret < 0) { + ERROR("bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.kill(crequest, &cresponse); + + evhtp_send_kill_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_kill_request(crequest); + free_container_kill_response(cresponse); +} + +/* evhtp send container inspect repsponse */ +static void evhtp_send_container_inspect_repsponse(evhtp_request_t *req, container_inspect_response *response, + int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate inspect response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + responsedata = container_inspect_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate inspect request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest container inspect cb */ +static void rest_container_inspect_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_inspect_request *crequest = NULL; + container_inspect_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.inspect == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceInspect); + if (tret < 0) { + ERROR("bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.inspect(crequest, &cresponse); + + evhtp_send_container_inspect_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_inspect_request(crequest); + free_container_inspect_response(cresponse); +} + +/* evhtp send exec repsponse */ +static void evhtp_send_exec_repsponse(evhtp_request_t *req, container_exec_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate exec response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + responsedata = container_exec_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate exec request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest exec cb */ +static void rest_exec_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_exec_request *crequest = NULL; + container_exec_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || !cb->container.exec) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceExec); + if (tret < 0) { + ERROR("bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.exec(crequest, &cresponse, -1, NULL); + + evhtp_send_exec_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_exec_request(crequest); + free_container_exec_response(cresponse); +} + +/* evhtp send remove repsponse */ +static void evhtp_send_remove_repsponse(evhtp_request_t *req, container_delete_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate remove response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + responsedata = container_delete_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate remove request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest remove cb */ +static void rest_remove_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_delete_request *crequest = NULL; + container_delete_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.remove == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceRemove); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.remove(crequest, &cresponse); + + evhtp_send_remove_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_delete_request(crequest); + free_container_delete_response(cresponse); +} + +/* rest list cb */ +static void rest_list_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + container_list_request *crequest = NULL; + container_list_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->container.list == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = action_request_from_rest(req, (void **)&crequest, ContainerServiceList); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->container.list(crequest, &cresponse); + + evhtp_send_list_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_container_list_request(crequest); + free_container_list_response(cresponse); +} + +/* rest register containers handler */ +int rest_register_containers_handler(evhtp_t *htp) +{ + if (evhtp_set_cb(htp, ContainerServiceCreate, rest_create_cb, NULL) == NULL) { + ERROR("Failed to register create callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceStop, rest_stop_cb, NULL) == NULL) { + ERROR("Failed to register stop callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceRestart, rest_restart_cb, NULL) == NULL) { + ERROR("Failed to register restart callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceVersion, rest_version_cb, NULL) == NULL) { + ERROR("Failed to register version callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceUpdate, rest_update_cb, NULL) == NULL) { + ERROR("Failed to register update callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceKill, rest_kill_cb, NULL) == NULL) { + ERROR("Failed to register kill callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceInspect, rest_container_inspect_cb, NULL) == NULL) { + ERROR("Failed to register inspect callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceExec, rest_exec_cb, NULL) == NULL) { + ERROR("Failed to register exec callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceRemove, rest_remove_cb, NULL) == NULL) { + ERROR("Failed to register remove callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceStart, rest_start_cb, NULL) == NULL) { + ERROR("Failed to register start callback"); + return -1; + } + if (evhtp_set_cb(htp, ContainerServiceList, rest_list_cb, NULL) == NULL) { + ERROR("Failed to register list callback"); + return -1; + } + + if (evhtp_set_cb(htp, ContainerServiceConf, rest_container_conf_cb, NULL) == NULL) { + ERROR("Failed to register container_conf callback"); + return -1; + } + + if (evhtp_set_cb(htp, ContainerServiceWait, rest_wait_cb, NULL) == NULL) { + ERROR("Failed to register wait callback"); + return -1; + } + return 0; +} diff --git a/src/connect/service/rest/rest_containers_service.h b/src/connect/service/rest/rest_containers_service.h new file mode 100644 index 0000000..21eb434 --- /dev/null +++ b/src/connect/service/rest/rest_containers_service.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful service definition + ******************************************************************************/ +#ifndef __REST_CONTAINERS_SERVICE_H +#define __REST_CONTAINERS_SERVICE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int rest_register_containers_handler(evhtp_t *htp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/service/rest/rest_images_service.c b/src/connect/service/rest/rest_images_service.c new file mode 100644 index 0000000..fc24cb8 --- /dev/null +++ b/src/connect/service/rest/rest_images_service.c @@ -0,0 +1,446 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide image restful service functions + ******************************************************************************/ +#include + +#include "log.h" +#include "securec.h" +#include "callback.h" +#include "image.rest.h" +#include "rest_service_common.h" +#include "rest_images_service.h" + +/* image load request check */ +static int image_load_request_check( + image_load_image_request *req) +{ + if (req->type == NULL) { + DEBUG("recive NULL Request runtime"); + return -1; + } + + if (req->file == NULL) { + DEBUG("container name error"); + return -1; + } + + return 0; +} + +/* image load request from rest */ +static int image_load_request_from_rest(evhtp_request_t *req, + image_load_image_request **crequest) +{ + size_t body_len; + char *body = NULL; + parser_error err = NULL; + int ret = 0; + + if (get_body(req, &body_len, &body) != 0) { + ERROR("Failed to get body"); + return -1; + } + + *crequest = image_load_image_request_parse_data(body, NULL, &err); + if (*crequest == NULL) { + ERROR("Invalid create request body:%s", err); + ret = -1; + goto out; + } + + if (image_load_request_check(*crequest) < 0) { + ret = -1; + goto out; + } +out: + put_body(body); + free(err); + return ret; +} + +/* evhtp send image load repsponse */ +static void evhtp_send_image_load_repsponse(evhtp_request_t *req, + image_load_image_response *response, int rescode) +{ + parser_error err = NULL; + char *responsedata = NULL; + + responsedata = image_load_image_response_generate_json(response, NULL, &err); + if (responsedata == NULL) { + ERROR("Load: failed to generate request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + evhtp_send_response(req, responsedata, rescode); + +out: + free(responsedata); + free(err); + return; +} + +/* image list request from rest */ +static int image_list_request_from_rest(evhtp_request_t *req, + image_list_images_request **crequest) +{ + char *body = NULL; + int ret = 0; + parser_error err = NULL; + size_t body_len; + + if (get_body(req, &body_len, &body) != 0) { + ERROR("Failed to get body"); + return -1; + } + + *crequest = image_list_images_request_parse_data(body, NULL, &err); + if (*crequest == NULL) { + ERROR("Invalid create request body:%s", err); + ret = -1; + goto out; + } + +out: + put_body(body); + free(err); + return ret; +} + +/* evhtp send image list repsponse */ +static void evhtp_send_image_list_repsponse(evhtp_request_t *req, + image_list_images_response *response, int rescode) +{ + parser_error err = NULL; + char *responsedata = NULL; + + responsedata = image_list_images_response_generate_json(response, NULL, &err); + if (responsedata == NULL) { + ERROR("List: failed to generate request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* image delete request check */ +static int image_delete_request_check( + image_delete_image_request *req) +{ + int ret = 0; + + if (req->image_name == NULL) { + ERROR("container name error"); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* image delete request from rest */ +static int image_delete_request_from_rest(evhtp_request_t *req, + image_delete_image_request **crequest) +{ + parser_error err = NULL; + int ret = 0; + char *body = NULL; + size_t body_len; + + if (get_body(req, &body_len, &body) != 0) { + ERROR("Failed to get body"); + return -1; + } + + *crequest = image_delete_image_request_parse_data(body, NULL, &err); + if (*crequest == NULL) { + ERROR("Invalid create request body:%s", err); + ret = -1; + goto out; + } + + if (image_delete_request_check(*crequest) < 0) { + ret = -1; + goto out; + } +out: + put_body(body); + free(err); + return ret; +} + +/* evhtp send image delete repsponse */ +static void evhtp_send_image_delete_repsponse(evhtp_request_t *req, + image_delete_image_response *response, int rescode) +{ + parser_error err = NULL; + char *responsedata = NULL; + + responsedata = image_delete_image_response_generate_json(response, NULL, &err); + if (responsedata != NULL) { + evhtp_send_response(req, responsedata, rescode); + goto out; + } + + ERROR("Delete: failed to generate request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); +out: + free(responsedata); + free(err); + return; +} + +/* image inspect request check */ +static int image_inspect_request_check(void *req) +{ + int ret = 0; + image_inspect_request *req_inspect = (image_inspect_request *)req; + if (req_inspect->id == NULL) { + DEBUG("image name or id required!"); + ret = -1; + } + + return ret; +} + +/* image inspect request from rest */ +static int image_inspect_request_from_rest(const evhtp_request_t *req, + image_inspect_request **crequest) +{ + parser_error err = NULL; + int ret = 0; + char *body = NULL; + size_t body_len; + + if (get_body(req, &body_len, &body) != 0) { + ERROR("Failed to get body"); + return -1; + } + + *crequest = image_inspect_request_parse_data(body, NULL, &err); + if (*crequest == NULL) { + ERROR("Invalid create request body:%s", err); + ret = -1; + goto out; + } + + if (image_inspect_request_check(*crequest) < 0) { + ret = -1; + goto out; + } +out: + put_body(body); + free(err); + return ret; +} + +/* evhtp send image inspect repsponse */ +static void evhtp_send_image_inspect_repsponse(evhtp_request_t *req, + image_inspect_response *response, int rescode) +{ + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + char *responsedata = NULL; + + if (response == NULL) { + ERROR("Failed to generate inspect response info"); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + responsedata = image_inspect_response_generate_json(response, &ctx, &err); + if (responsedata == NULL) { + ERROR("Failed to generate inspect request json:%s", err); + evhtp_send_reply(req, EVHTP_RES_ERROR); + goto out; + } + + evhtp_send_response(req, responsedata, rescode); + +out: + free(err); + free(responsedata); + return; +} + +/* rest image load cb */ +static void rest_image_load_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + image_load_image_request *crequest = NULL; + image_load_image_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->image.load == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = image_load_request_from_rest(req, &crequest); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->image.load(crequest, &cresponse); + + evhtp_send_image_load_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_image_load_image_request(crequest); + free_image_load_image_response(cresponse); +} + +/* rest image list cb */ +static void rest_image_list_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + image_list_images_request *crequest = NULL; + image_list_images_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->image.list == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = image_list_request_from_rest(req, &crequest); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->image.list(crequest, &cresponse); + + evhtp_send_image_list_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_image_list_images_request(crequest); + free_image_list_images_response(cresponse); +} + +/* rest image delete cb */ +static void rest_image_delete_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + image_delete_image_request *crequest = NULL; + image_delete_image_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->image.remove == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = image_delete_request_from_rest(req, &crequest); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->image.remove(crequest, &cresponse); + + evhtp_send_image_delete_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_image_delete_image_request(crequest); + free_image_delete_image_response(cresponse); +} + +/* rest image inspect cb */ +static void rest_image_inspect_cb(evhtp_request_t *req, void *arg) +{ + int tret; + service_callback_t *cb = NULL; + image_inspect_request *crequest = NULL; + image_inspect_response *cresponse = NULL; + + // only deal with POST request + if (evhtp_request_get_method(req) != htp_method_POST) { + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + cb = get_service_callback(); + if (cb == NULL || cb->image.inspect == NULL) { + ERROR("Unimplemented callback"); + evhtp_send_reply(req, EVHTP_RES_NOTIMPL); + return; + } + + tret = image_inspect_request_from_rest(req, &crequest); + if (tret < 0) { + ERROR("Bad request"); + evhtp_send_reply(req, EVHTP_RES_SERVERR); + goto out; + } + + (void)cb->image.inspect(crequest, &cresponse); + + evhtp_send_image_inspect_repsponse(req, cresponse, EVHTP_RES_OK); +out: + free_image_inspect_request(crequest); + free_image_inspect_response(cresponse); +} + +/* rest register images handler */ +int rest_register_images_handler(evhtp_t *htp) +{ + if (evhtp_set_cb(htp, ImagesServiceLoad, rest_image_load_cb, NULL) == NULL) { + ERROR("Failed to register image load callback"); + return -1; + } + + if (evhtp_set_cb(htp, ImagesServiceList, rest_image_list_cb, NULL) == NULL) { + ERROR("Failed to register image list callback"); + return -1; + } + + if (evhtp_set_cb(htp, ImagesServiceDelete, rest_image_delete_cb, NULL) == NULL) { + ERROR("Failed to register image list callback"); + return -1; + } + + if (evhtp_set_cb(htp, ImagesServiceInspect, rest_image_inspect_cb, NULL) == NULL) { + ERROR("Failed to register image inspect callback"); + return -1; + } + + return 0; +} diff --git a/src/connect/service/rest/rest_images_service.h b/src/connect/service/rest/rest_images_service.h new file mode 100644 index 0000000..95a4f7d --- /dev/null +++ b/src/connect/service/rest/rest_images_service.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide image restful service definition + ******************************************************************************/ +#ifndef __REST_IMAGES_SERVICE_H +#define __REST_IMAGES_SERVICE_H + +#include "rest_service_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int rest_register_images_handler(evhtp_t *htp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/service/rest/rest_service.c b/src/connect/service/rest/rest_service.c new file mode 100644 index 0000000..286ac7a --- /dev/null +++ b/src/connect/service/rest/rest_service.c @@ -0,0 +1,137 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful service functions + ******************************************************************************/ +#include "rest_service.h" +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "securec.h" +#include "rest_containers_service.h" +#include "rest_images_service.h" + +#define REST_PTHREAD_NUM 100 +#define BACKLOG 2048 +static char *g_socketpath = NULL; +static evbase_t *g_evbase = NULL; +static evhtp_t *g_htp = NULL; + +/* rest server free */ +static void rest_server_free() +{ + if (g_socketpath != NULL) { + free(g_socketpath); + g_socketpath = NULL; + } + if (g_evbase != NULL) { + event_base_free(g_evbase); + g_evbase = NULL; + } + if (g_htp != NULL) { + evhtp_free(g_htp); + g_htp = NULL; + } +} + +/* rest register handler */ +static int rest_register_handler(evhtp_t *g_htp) +{ + if (rest_register_containers_handler(g_htp) != 0) { + return -1; + } + + if (rest_register_images_handler(g_htp) != 0) { + return -1; + } + + return 0; +} + +/* libevent log cb */ +static void libevent_log_cb(int severity, const char *msg) +{ + switch (severity) { + case EVENT_LOG_DEBUG: + break; + case EVENT_LOG_MSG: + break; + case EVENT_LOG_WARN: + break; + case EVENT_LOG_ERR: + ERROR("libevent: %s", msg); + break; + default: + FATAL("libevent: %s", msg); + break; + } +} + +/* rest server init */ +int rest_server_init(const char *socket) +{ + g_socketpath = util_strdup_s(socket); + + event_set_log_callback(libevent_log_cb); + + g_evbase = event_base_new(); + if (g_evbase == NULL) { + ERROR("Failed to init rest server"); + goto error_out; + } + g_htp = evhtp_new(g_evbase, NULL); + if (g_htp == NULL) { + ERROR("Failed to init rest server"); + goto error_out; + } + + if (unlink(g_socketpath + strlen(UNIX_SOCKET_PREFIX)) < 0 && errno != ENOENT) { + ERROR("Failed to remove '%s':%s, abort", strerror(errno), g_socketpath); + goto error_out; + } + + if (rest_register_handler(g_htp) < 0) { + ERROR("Register hanler failed"); + goto error_out; + } + + evhtp_use_dynamic_threads(g_htp, NULL, NULL, 0, 0, 0, NULL); + if (evhtp_bind_socket(g_htp, g_socketpath, 0, BACKLOG) < 0) { + ERROR("Evhtp_bind_socket error"); + goto error_out; + } + + return 0; + +error_out: + rest_server_free(); + return -1; +} + +/* rest server wait */ +void rest_server_wait(void) +{ + event_base_loop(g_evbase, 0); +} + +/* rest server shutdown */ +void rest_server_shutdown(void) +{ + if (g_socketpath != NULL) { + if (unlink(g_socketpath + strlen(UNIX_SOCKET_PREFIX)) < 0 && errno != ENOENT) { + ERROR("Failed to remove '%s':%s", g_socketpath, strerror(errno)); + } + } +} diff --git a/src/connect/service/rest/rest_service.h b/src/connect/service/rest/rest_service.h new file mode 100644 index 0000000..3a995cb --- /dev/null +++ b/src/connect/service/rest/rest_service.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful service definition + ******************************************************************************/ +#ifndef __REST_SERVICE_H +#define __REST_SERVICE_H + +#ifdef __cplusplus +extern "C" { +#endif + +int rest_server_init(const char *socket); + +void rest_server_wait(void); + +void rest_server_shutdown(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/service/rest/rest_service_common.c b/src/connect/service/rest/rest_service_common.c new file mode 100644 index 0000000..63ff9d7 --- /dev/null +++ b/src/connect/service/rest/rest_service_common.c @@ -0,0 +1,86 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful service common functions + ******************************************************************************/ +#include + +#include "log.h" +#include "utils.h" +#include "securec.h" +#include "rest_service_common.h" + +#define UNIX_PATH_MAX 128 +#define MAX_BODY_SIZE 8192 + +/* get body */ +int get_body(const evhtp_request_t *req, size_t *size_out, char **record_out) +{ + evbuf_t *buf = req->buffer_in; + size_t read_count = 0; + size_t total = 0; + size_t content_len = 0; + char *body_out = NULL; + char *body_p = NULL; + int ret = 0; + + content_len = (size_t)evbuffer_get_length(buf); + + if (content_len >= MAX_BODY_SIZE) { + ERROR("too big request,size: %u", content_len); + ret = -1; + goto empty; + } else if (content_len == 0) { + ret = 0; + goto empty; + } else { + body_out = util_common_calloc_s(content_len + 1); + if (body_out == NULL) { + ret = -1; + ERROR("no valid memory"); + goto empty; + } + } + + body_p = body_out; + read_count = (size_t)evbuffer_get_length(buf); + while (read_count != 0) { + int n; + total += read_count; + if (total > content_len) { + ret = -1; + ERROR("Read count out of range"); + free(body_out); + goto empty; + } + n = evbuffer_remove(buf, body_p, content_len); + body_p += n; + + read_count = (size_t)evbuffer_get_length(buf); + } + *size_out = content_len; + *record_out = body_out; + return ret; + +empty: + *size_out = 0; + *record_out = NULL; + return ret; +} + +/* evhtp send repsponse */ +void evhtp_send_response(evhtp_request_t *req, const char *responsedata, int rescode) +{ + evhtp_headers_add_header(req->headers_out, evhtp_header_new("Content-Type", "application/json", 0, 0)); + evbuffer_add(req->buffer_out, responsedata, strlen(responsedata)); + evhtp_send_reply(req, rescode); +} diff --git a/src/connect/service/rest/rest_service_common.h b/src/connect/service/rest/rest_service_common.h new file mode 100644 index 0000000..36e9e61 --- /dev/null +++ b/src/connect/service/rest/rest_service_common.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful service common definition + ******************************************************************************/ +#ifndef __REST_SERVICE_COMMON_H +#define __REST_SERVICE_COMMON_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int get_body(const evhtp_request_t *req, size_t *size_out, char **record_out); + +void put_body(char *body); + +void evhtp_send_response(evhtp_request_t *req, const char *responsedata, int rescode); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/connect/service/service_common.c b/src/connect/service/service_common.c new file mode 100644 index 0000000..9374486 --- /dev/null +++ b/src/connect/service/service_common.c @@ -0,0 +1,60 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide common service definition + ******************************************************************************/ + +#include "service_common.h" +#ifdef GRPC_CONNECTOR +#include "grpc_service.h" +#else +#include "rest_service.h" +#include "log.h" +#endif + +/* server common init */ +int server_common_init(const struct service_arguments *args) +{ + if (args == NULL || args->hosts == NULL) { + return -1; + } + +#ifdef GRPC_CONNECTOR + return grpc_server_init(args); +#else + if (args->hosts_len > 1) { + ERROR("Rest server dest not support multiple hosts"); + return -1; + } + return rest_server_init(args->hosts[0]); +#endif +} + +/* server common start */ +void server_common_start(void) +{ +#ifdef GRPC_CONNECTOR + grpc_server_wait(); +#else + rest_server_wait(); +#endif +} + +/* server common shutdown */ +void server_common_shutdown(void) +{ +#ifdef GRPC_CONNECTOR + grpc_server_shutdown(); +#else + rest_server_shutdown(); +#endif +} diff --git a/src/connect/service/service_common.h b/src/connect/service/service_common.h new file mode 100644 index 0000000..5a5e51d --- /dev/null +++ b/src/connect/service/service_common.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-08 + * Description: provide common service definition + ******************************************************************************/ +#ifndef __SERVICE_COMMON_H +#define __SERVICE_COMMON_H + +#include "arguments.h" +#include "liblcrd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int server_common_init(const struct service_arguments *args); + +void server_common_start(void); + +void server_common_shutdown(void); + +void event_monitor_exit_callback(void *arg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/console/CMakeLists.txt b/src/console/CMakeLists.txt new file mode 100644 index 0000000..bf1904d --- /dev/null +++ b/src/console/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_console_srcs) + +set(CONSOLE_SRCS + ${local_console_srcs} + PARENT_SCOPE + ) diff --git a/src/console/console.c b/src/console/console.c new file mode 100644 index 0000000..f443f60 --- /dev/null +++ b/src/console/console.c @@ -0,0 +1,685 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide console definition + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#include "console.h" +#include "securec.h" +#include "mainloop.h" +#include "log.h" +#include "utils.h" +#include "constants.h" + +static ssize_t fifo_write_function(void *context, const void *data, size_t len) +{ + ssize_t ret; + int fd; + + fd = *(int *)context; + ret = util_write_nointr(fd, data, len); + // Ignore EAGAIN to prevent hang, do not report error + if (errno == EAGAIN) { + return (ssize_t)len; + } + + if ((ret <= 0) || (ret != (ssize_t)len)) { + ERROR("Failed to write %d: %s", fd, strerror(errno)); + return -1; + } + return ret; +} + +static ssize_t fd_write_function(void *context, const void *data, size_t len) +{ + ssize_t ret; + ret = util_write_nointr(*(int *)context, data, len); + if ((ret <= 0) || (ret != (ssize_t)len)) { + ERROR("Failed to write: %s", strerror(errno)); + return -1; + } + return ret; +} + +/* console cb tty fifoin */ +static int console_cb_tty_stdin_with_escape(int fd, uint32_t events, void *cbdata, + struct epoll_descr *descr) +{ + struct tty_state *ts = cbdata; + char c; + int ret = 0; + ssize_t r_ret, w_ret; + + if (fd != ts->stdin_reader) { + ret = 1; + goto out; + } + + r_ret = util_read_nointr(ts->stdin_reader, &c, 1); + if (r_ret <= 0) { + ret = 1; + goto out; + } + + if (ts->tty_exit != -1) { + if (c == ts->tty_exit && !(ts->saw_tty_exit)) { + ts->saw_tty_exit = 1; + goto out; + } + + if (c == 'q' && ts->saw_tty_exit) { + ret = 1; + goto out; + } + + ts->saw_tty_exit = 0; + } + + if (ts->stdin_writer.context && ts->stdin_writer.write_func) { + w_ret = ts->stdin_writer.write_func(ts->stdin_writer.context, &c, 1); + if ((w_ret <= 0) || (w_ret != r_ret)) { + ret = 1; + goto out; + } + } + +out: + return ret; +} + + +static int console_writer_write_data(const struct io_write_wrapper *writer, const char *buf, ssize_t len) +{ + ssize_t ret; + + if (writer == NULL || writer->context == NULL || writer->write_func == NULL || len <= 0) { + return 0; + } + ret = writer->write_func(writer->context, buf, (size_t)len); + if (ret <= 0 || ret != len) { + ERROR("failed to write, error:%s", strerror(errno)); + return -1; + } + return 0; +} + +/* console cb tty fifoin */ +static int console_cb_stdio_copy(int fd, uint32_t events, void *cbdata, struct epoll_descr *descr) +{ + struct tty_state *ts = cbdata; + char *buf = NULL; + size_t buf_len = MAX_MSG_BUFFER_SIZE; + int ret = 0; + ssize_t r_ret; + + buf = util_common_calloc_s(buf_len); + if (buf == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (fd == ts->sync_fd) { + ret = 1; + goto out; + } + + if (fd != ts->stdin_reader && fd != ts->stdout_reader && fd != ts->stderr_reader) { + ret = 1; + goto out; + } + + r_ret = util_read_nointr(fd, buf, buf_len - 1); + if (r_ret <= 0) { + ret = 1; + goto out; + } + + if (fd == ts->stdin_reader) { + if (console_writer_write_data(&ts->stdin_writer, buf, r_ret) != 0) { + ret = 1; + goto out; + } + } + + if (fd == ts->stdout_reader) { + if (console_writer_write_data(&ts->stdout_writer, buf, r_ret) != 0) { + ret = 1; + goto out; + } + } + + if (fd == ts->stderr_reader) { + if (console_writer_write_data(&ts->stderr_writer, buf, r_ret) != 0) { + ret = 1; + goto out; + } + } + +out: + free(buf); + return ret; +} + +/* console fifo name */ +int console_fifo_name(const char *rundir, const char *subpath, + const char *stdflag, + char *fifo_name, size_t fifo_name_sz, + char *fifo_path, size_t fifo_path_sz, bool do_mkdirp) +{ + int ret = 0; + int nret = 0; + + nret = sprintf_s(fifo_path, fifo_path_sz, "%s/%s/", rundir, subpath); + if (nret < 0) { + ERROR("FIFO path:%s/%s/ is too long.", rundir, subpath); + ret = -1; + goto out; + } + + if (do_mkdirp) { + ret = util_mkdir_p(fifo_path, CONSOLE_FIFO_DIRECTORY_MODE); + if (ret < 0) { + COMMAND_ERROR("Unable to create console fifo directory %s: %s.", fifo_path, strerror(errno)); + goto out; + } + } + + nret = sprintf_s(fifo_name, fifo_name_sz, "%s/%s/%s-fifo", rundir, subpath, stdflag); + if (nret < 0) { + ERROR("FIFO name %s/%s/%s-fifo is too long.", rundir, subpath, stdflag); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* console fifo create */ +int console_fifo_create(const char *fifo_path) +{ + int ret; + + ret = mknod(fifo_path, S_IFIFO | S_IRUSR | S_IWUSR, (dev_t)0); + if (ret < 0 && errno != EEXIST) { + ERROR("Failed to mknod monitor fifo %s: %s.", fifo_path, strerror(errno)); + return -1; + } + + return 0; +} + +/* console fifo delete */ +int console_fifo_delete(const char *fifo_path) +{ + char *ret = NULL; + char real_path[PATH_MAX + 1] = { 0x00 }; + + if (fifo_path == NULL || strlen(fifo_path) > PATH_MAX) { + ERROR("Invalid input!"); + return -1; + } + + if (strlen(fifo_path) == 0) { + return 0; + } + + ret = realpath(fifo_path, real_path); + if (ret == NULL) { + if (errno != ENOENT) { + ERROR("Failed to get real path: %s", fifo_path); + return -1; + } + return 0; + } + + if (unlink(real_path) && errno != ENOENT) { + WARN("Unlink %s failed", real_path); + return -1; + } + return 0; +} + +/* console fifo open */ +int console_fifo_open(const char *fifo_path, int *fdout, int flags) +{ + int fd = 0; + + fd = util_open(fifo_path, O_RDONLY | O_NONBLOCK, (mode_t)0); + if (fd < 0) { + ERROR("Failed to open fifo %s to send message: %s.", fifo_path, + strerror(errno)); + return -1; + } + + *fdout = fd; + return 0; +} + +/* console fifo open withlock */ +int console_fifo_open_withlock(const char *fifo_path, int *fdout, int flags) +{ + int fd = 0; + struct flock lk; + + fd = util_open(fifo_path, flags, 0); + if (fd < 0) { + WARN("Failed to open fifo %s to send message: %s.", fifo_path, strerror(errno)); + return -1; + } + + lk.l_type = F_WRLCK; + lk.l_whence = SEEK_SET; + lk.l_start = 0; + lk.l_len = 0; + if (fcntl(fd, F_SETLK, &lk) != 0) { + /* another console instance is already running, don't start up */ + DEBUG("Another console instance already running with path : %s.", fifo_path); + close(fd); + return -1; + } + + *fdout = fd; + return 0; +} + +/* console fifo close */ +void console_fifo_close(int fd) +{ + close(fd); +} + +/* setup tios */ +int setup_tios(int fd, struct termios *curr_tios) +{ + struct termios tmptios; + int ret = 0; + + if (!isatty(fd)) { + ERROR("Specified fd: '%d' is not a tty", fd); + return -1; + } + + if (tcgetattr(fd, curr_tios)) { + ERROR("Failed to get current terminal settings"); + ret = -1; + goto out; + } + + tmptios = *curr_tios; + + cfmakeraw(&tmptios); + tmptios.c_oflag |= OPOST; + + if (tcsetattr(fd, TCSAFLUSH, &tmptios)) { + ERROR("Set terminal settings failed"); + ret = -1; + goto out; + } + +out: + return ret; +} + +static void client_console_tty_state_close(struct epoll_descr *descr, const struct tty_state *ts) +{ + if (ts->stdin_reader >= 0) { + epoll_loop_del_handler(descr, ts->stdin_reader); + } + if (ts->stdout_reader >= 0) { + epoll_loop_del_handler(descr, ts->stdout_reader); + } + if (ts->stderr_reader >= 0) { + epoll_loop_del_handler(descr, ts->stderr_reader); + } +} + +/* console loop */ +/* data direction: */ +/* read stdinfd, write fifoinfd */ +/* read fifooutfd, write stdoutfd */ +/* read stderrfd, write stderrfd */ +int client_console_loop(int stdinfd, int stdoutfd, int stderrfd, + int fifoinfd, int fifooutfd, int fifoerrfd, int tty_exit, bool tty) +{ + int ret; + struct epoll_descr descr; + struct tty_state ts; + + ret = epoll_loop_open(&descr); + if (ret) { + ERROR("Create epoll_loop error"); + return ret; + } + + ts.tty_exit = tty_exit; + ts.saw_tty_exit = 0; + ts.sync_fd = -1; + ts.stdin_reader = -1; + ts.stdout_reader = -1; + ts.stderr_reader = -1; + + if (fifoinfd >= 0) { + ts.stdin_reader = stdinfd; + ts.stdin_writer.context = &fifoinfd; + ts.stdin_writer.write_func = fd_write_function; + if (tty) { + ret = epoll_loop_add_handler(&descr, ts.stdin_reader, console_cb_tty_stdin_with_escape, &ts); + if (ret) { + INFO("Add handler for stdinfd faied. with error %s", strerror(errno)); + } + } else { + ret = epoll_loop_add_handler(&descr, ts.stdin_reader, console_cb_stdio_copy, &ts); + if (ret) { + INFO("Add handler for stdinfd faied. with error %s", strerror(errno)); + } + } + } + + if (fifooutfd >= 0) { + ts.stdout_reader = fifooutfd; + ts.stdout_writer.context = &stdoutfd; + ts.stdout_writer.write_func = fd_write_function; + ret = epoll_loop_add_handler(&descr, ts.stdout_reader, console_cb_stdio_copy, &ts); + if (ret) { + ERROR("Add handler for masterfd failed"); + goto err_out; + } + } + + if (fifoerrfd >= 0) { + ts.stderr_reader = fifoerrfd; + ts.stderr_writer.context = &stderrfd; + ts.stderr_writer.write_func = fd_write_function; + ret = epoll_loop_add_handler(&descr, ts.stderr_reader, console_cb_stdio_copy, &ts); + if (ret) { + ERROR("Add handler for masterfd failed"); + goto err_out; + } + } + + ret = epoll_loop(&descr, -1); + if (ret) { + ERROR("Epoll_loop error"); + goto err_out; + } + + ret = 0; + +err_out: + client_console_tty_state_close(&descr, &ts); + epoll_loop_close(&descr); + return ret; +} + +/* console loop copy */ +static int console_loop_io_copy(int sync_fd, const int *srcfds, struct io_write_wrapper *writers, size_t len) +{ + int ret = 0; + size_t i = 0; + struct epoll_descr descr; + struct tty_state *ts = NULL; + if (len > (SIZE_MAX / sizeof(struct tty_state)) - 1) { + ERROR("Invalid io size"); + return -1; + } + ts = util_common_calloc_s(sizeof(struct tty_state) * (len + 1)); + if (ts == NULL) { + ERROR("Out of memory"); + return -1; + } + + ret = epoll_loop_open(&descr); + if (ret) { + ERROR("Create epoll_loop error"); + free(ts); + return ret; + } + + for (i = 0; i < len; i++) { + // Reusing ts.stdout_reader and ts.stdout_writer for coping io + ts[i].stdout_reader = srcfds[i]; + ts[i].stdout_writer.context = writers[i].context; + ts[i].stdout_writer.write_func = writers[i].write_func; + ts[i].sync_fd = -1; + ret = epoll_loop_add_handler(&descr, ts[i].stdout_reader, console_cb_stdio_copy, &ts[i]); + if (ret != 0) { + ERROR("Add handler for masterfd failed"); + goto err_out; + } + } + if (sync_fd >= 0) { + ts[i].sync_fd = sync_fd; + epoll_loop_add_handler(&descr, ts[i].sync_fd, console_cb_stdio_copy, &ts[i]); + if (ret) { + ERROR("Add handler for syncfd failed"); + goto err_out; + } + } + + ret = epoll_loop(&descr, -1); + if (ret != 0) { + ERROR("Epoll_loop error"); + goto err_out; + } + +err_out: + + for (i = 0; i < (len + 1); i++) { + epoll_loop_del_handler(&descr, ts[i].stdout_reader); + } + epoll_loop_close(&descr); + free(ts); + return ret; +} + +struct io_copy_thread_arg { + struct io_copy_arg *copy_arg; + bool detach; + size_t len; + int sync_fd; + sem_t wait_sem; +}; + +static int io_copy_init_fds(size_t len, int **infds, int **outfds, int **srcfds, struct io_write_wrapper **writers) +{ + size_t i; + + if (len > SIZE_MAX / sizeof(struct io_write_wrapper)) { + ERROR("Invalid arguments"); + return -1; + } + *srcfds = util_common_calloc_s(sizeof(int) * len); + if (*srcfds == NULL) { + ERROR("Out of memory"); + return -1; + } + + *infds = util_common_calloc_s(sizeof(int) * len); + if (*infds == NULL) { + ERROR("Out of memory"); + return -1; + } + for (i = 0; i < len; i++) { + (*infds)[i] = -1; + } + *outfds = util_common_calloc_s(sizeof(int) * len); + if (*outfds == NULL) { + ERROR("Out of memory"); + return -1; + } + for (i = 0; i < len; i++) { + (*outfds)[i] = -1; + } + + *writers = util_common_calloc_s(sizeof(struct io_write_wrapper) * len); + if (*writers == NULL) { + ERROR("Out of memory"); + return -1; + } + return 0; +} + +static int io_copy_make_srcfds(size_t len, struct io_copy_arg *copy_arg, int *infds, int *srcfds) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (copy_arg[i].srctype == IO_FIFO) { + if (console_fifo_open((const char *)copy_arg[i].src, &(infds[i]), O_RDONLY | O_NONBLOCK)) { + ERROR("failed to open console fifo."); + return -1; + } + srcfds[i] = infds[i]; + } else if (copy_arg[i].srctype == IO_FD) { + srcfds[i] = *(int *)(copy_arg[i].src); + } else { + ERROR("Got invalid src fd type"); + return -1; + } + } + return 0; +} + +static int io_copy_make_dstfds(size_t len, struct io_copy_arg *copy_arg, int *outfds, + struct io_write_wrapper *writers) +{ + size_t i; + + for (i = 0; i < len; i++) { + if (copy_arg[i].dsttype == IO_FIFO) { + if (console_fifo_open_withlock((const char *)copy_arg[i].dst, &outfds[i], O_RDWR | O_NONBLOCK)) { + ERROR("Failed to open console fifo."); + return -1; + } + writers[i].context = &outfds[i]; + writers[i].write_func = fifo_write_function; + } else if (copy_arg[i].dsttype == IO_FD) { + writers[i].context = copy_arg[i].dst; + writers[i].write_func = fd_write_function; + } else if (copy_arg[i].dsttype == IO_FUNC) { + struct io_write_wrapper *io_write = copy_arg[i].dst; + writers[i].context = io_write->context; + writers[i].write_func = io_write->write_func; + writers[i].close_func = io_write->close_func; + } else { + ERROR("Got invalid dst fd type"); + return -1; + } + } + return 0; +} + +static void io_copy_thread_cleanup(struct io_write_wrapper *writers, struct io_copy_thread_arg *thread_arg, + int *infds, int *outfds, int *srcfds, size_t len) +{ + size_t i = 0; + for (i = 0; i < len; i++) { + if (writers != NULL && writers[i].close_func != NULL) { + (void)writers[i].close_func(writers[i].context, NULL); + } + } + free(srcfds); + for (i = 0; i < len; i++) { + if ((infds != NULL) && (infds[i] >= 0)) { + console_fifo_close(infds[i]); + } + if ((outfds != NULL) && (outfds[i] >= 0)) { + console_fifo_close(outfds[i]); + } + } + free(infds); + free(outfds); + free(writers); +} + +static void *io_copy_thread_main(void *arg) +{ + int ret = -1; + struct io_copy_thread_arg *thread_arg = (struct io_copy_thread_arg *)arg; + struct io_copy_arg *copy_arg = thread_arg->copy_arg; + size_t len = 0; + int *infds = NULL; + int *outfds = NULL; // recored fds to close + int *srcfds = NULL; + struct io_write_wrapper *writers = NULL; + int sync_fd = thread_arg->sync_fd; + bool posted = false; + + if (thread_arg->detach) { + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Set thread detach fail"); + goto err; + } + } + + (void)prctl(PR_SET_NAME, "IoCopy"); + + len = thread_arg->len; + if (io_copy_init_fds(len, &infds, &outfds, &srcfds, &writers) != 0) { + goto err; + } + + if (io_copy_make_srcfds(len, copy_arg, infds, srcfds) != 0) { + goto err; + } + + if (io_copy_make_dstfds(len, copy_arg, outfds, writers) != 0) { + goto err; + } + + sem_post(&thread_arg->wait_sem); + posted = true; + (void)console_loop_io_copy(sync_fd, srcfds, writers, len); +err: + if (!posted) { + sem_post(&thread_arg->wait_sem); + } + io_copy_thread_cleanup(writers, thread_arg, infds, outfds, srcfds, len); + return NULL; +} + +int start_io_copy_thread(int sync_fd, bool detach, struct io_copy_arg *copy_arg, size_t len, pthread_t *tid) +{ + int res = 0; + struct io_copy_thread_arg thread_arg; + + if (copy_arg == NULL || len == 0) { + return 0; + } + + thread_arg.detach = detach; + thread_arg.copy_arg = copy_arg; + thread_arg.len = len; + thread_arg.sync_fd = sync_fd; + if (sem_init(&thread_arg.wait_sem, 0, 0)) { + ERROR("Failed to init start semaphore"); + return -1; + } + + res = pthread_create(tid, NULL, io_copy_thread_main, (void *)(&thread_arg)); + if (res != 0) { + CRIT("Thread creation failed"); + return -1; + } + sem_wait(&thread_arg.wait_sem); + sem_destroy(&thread_arg.wait_sem); + return 0; +} diff --git a/src/console/console.h b/src/console/console.h new file mode 100644 index 0000000..a7b2801 --- /dev/null +++ b/src/console/console.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide console definition + ******************************************************************************/ +#ifndef _CONSOLE_H +#define _CONSOLE_H + +#include +#include +#include +#include +#include "constants.h" +#ifdef __cplusplus +extern "C" { +#endif + +typedef ssize_t (*io_write_func_t)(void *context, const void *data, size_t len); +typedef int (*io_close_func_t)(void *context, char **err); + +struct io_write_wrapper { + void *context; + io_write_func_t write_func; + io_close_func_t close_func; +}; + +typedef ssize_t (*io_read_func_t)(void *context, void *buf, size_t len); + +struct io_read_wrapper { + void *context; + io_read_func_t read; + io_close_func_t close; +}; + +struct tty_state { + int sync_fd; + int stdin_reader; + struct io_write_wrapper stdin_writer; + int stdout_reader; + struct io_write_wrapper stdout_writer; + int stderr_reader; + struct io_write_wrapper stderr_writer; + /* Escape sequence for quiting from tty. Exiteing by : Ctrl + specified_char + q */ + int tty_exit; + /* Flag to mark whether detected escape sequence. */ + int saw_tty_exit; +}; + +typedef enum { + IO_FD, + IO_FIFO, + IO_FUNC, + IO_MAX +} io_type; + +struct io_copy_arg { + io_type srctype; + void *src; + io_type dsttype; + void *dst; +}; + +int console_fifo_name(const char *rundir, const char *subpath, + const char *stdflag, + char *fifo_name, size_t fifo_name_sz, + char *fifo_path, size_t fifo_path_sz, bool do_mkdirp); + +int console_fifo_create(const char *fifo_path); + +int console_fifo_delete(const char *fifo_path); + +int console_fifo_open(const char *fifo_path, int *fdout, int flags); + +int console_fifo_open_withlock(const char *fifo_path, int *fdout, int flags); + +void console_fifo_close(int fd); + +int client_console_loop(int stdinfd, int stdoutfd, int stderrfd, + int fifoinfd, int fifooutfd, int fifoerrfd, int tty_exit, bool tty); + +int start_io_copy_thread(int sync_fd, bool detach, struct io_copy_arg *copy_arg, size_t len, pthread_t *tid); + +int setup_tios(int fd, struct termios *curr_tios); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 0000000..588e292 --- /dev/null +++ b/src/constants.h @@ -0,0 +1,62 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide macro definition + ******************************************************************************/ + +#ifndef _LCRD_CONSTANTS_H +#define _LCRD_CONSTANTS_H + +/* mode of file and directory */ + +#define DEFAULT_SECURE_FILE_MODE 0640 + +#define DEFAULT_SECURE_DIRECTORY_MODE 0750 + +#define USER_REMAP_DIRECTORY_MODE 0751 + +#define ROOTFS_MNT_DIRECTORY_MODE 0640 + +#define CONFIG_DIRECTORY_MODE 0750 + +#define CONFIG_FILE_MODE 0640 + +#define ARCH_LOG_FILE_MODE 0440 + +#define WORKING_LOG_FILE_MODE 0640 + +#define LOG_DIRECTORY_MODE 0750 + +#define TEMP_DIRECTORY_MODE 0750 + +#define CONSOLE_FIFO_DIRECTORY_MODE 0770 + +#define SOCKET_GROUP_DIRECTORY_MODE 0660 + +#define DEBUG_FILE_MODE 0640 + +#define DEBUG_DIRECTORY_MODE 0750 + +#define ISULAD_CONFIG "/etc/isulad" + +#define ISULAD_DAEMON_JSON_CONF_FILE ISULAD_CONFIG "/daemon.json" + +#define DEFAULT_CA_FILE "ca.pem" +#define DEFAULT_KEY_FILE "key.pem" +#define DEFAULT_CERT_FILE "cert.pem" + + +#define LOG_MAX_RETRIES 10 + +#define MAX_MSG_BUFFER_SIZE (32 * 1024) + +#endif diff --git a/src/container_def.c b/src/container_def.c new file mode 100644 index 0000000..7fc497a --- /dev/null +++ b/src/container_def.c @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container definition + ******************************************************************************/ +#include + +#include "container_def.h" + +/* container cgroup resources free */ +void container_cgroup_resources_free(container_cgroup_resources_t *cr) +{ + if (cr == NULL) { + return; + } + free(cr->cpuset_cpus); + cr->cpuset_cpus = NULL; + + free(cr->cpuset_mems); + cr->cpuset_mems = NULL; + + free(cr); +} diff --git a/src/container_def.h b/src/container_def.h new file mode 100644 index 0000000..95ecd6f --- /dev/null +++ b/src/container_def.h @@ -0,0 +1,139 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container definition + ******************************************************************************/ +#ifndef __CONTAINER_DEF_H_ +#define __CONTAINER_DEF_H_ + +#include +#include +#include + +#include "types_def.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DEFAULT_UNIX_SOCKET +#define DEFAULT_UNIX_SOCKET "unix:///var/run/lcrd.sock" +#endif +#ifndef DEFAULT_ROOTFS_PATH +#define DEFAULT_ROOTFS_PATH "/dev/ram0" +#endif +#ifndef OCICONFIG_PATH +#define OCICONFIG_PATH "/etc/default/lcrd/config.json" +#endif +#ifndef SECCOMP_DEFAULT_PATH +#define SECCOMP_DEFAULT_PATH "/etc/isulad/seccomp_default.json" +#endif +#ifndef OCI_VERSION +#define OCI_VERSION "1.0.0-rc5-dev" +#endif + +typedef enum { + EVENTS_TYPE_EXIT = 0, + EVENTS_TYPE_STOPPED1 = 1, + EVENTS_TYPE_STARTING = 2, + EVENTS_TYPE_RUNNING1 = 3, + EVENTS_TYPE_STOPPING = 4, + EVENTS_TYPE_ABORTING = 5, + EVENTS_TYPE_FREEZING = 6, + EVENTS_TYPE_FROZEN = 7, + EVENTS_TYPE_THAWED = 8, + EVENTS_TYPE_OOM = 9, + EVENTS_TYPE_CREATE = 10, + EVENTS_TYPE_START = 11, + EVENTS_TYPE_EXEC_ADDED = 12, + EVENTS_TYPE_PAUSED1 = 13, + EVENTS_TYPE_MAX_STATE = 14 +} container_events_type_t; + +typedef enum { + CONTAINER_STATUS_UNKNOWN = 0, + CONTAINER_STATUS_CREATED = 1, + CONTAINER_STATUS_STARTING = 2, + CONTAINER_STATUS_RUNNING = 3, + CONTAINER_STATUS_STOPPED = 4, + CONTAINER_STATUS_PAUSED = 5, + CONTAINER_STATUS_RESTARTING = 6, + CONTAINER_STATUS_MAX_STATE = 7 +} Container_Status; + +typedef enum { + STOPPED, STARTING, RUNNING, STOPPING, + ABORTING, FREEZING, FROZEN, THAWED, MAX_STATE +} runtime_state_t; + +typedef enum { + HEALTH_SERVING_STATUS_UNKNOWN = 0, + HEALTH_SERVING_STATUS_SERVING = 1, + HEALTH_SERVING_STATUS_NOT_SERVING = 2, + HEALTH_SERVING_STATUS_MAX = 3 +} Health_Serving_Status; + +typedef enum { + NAMESPACE_USER = 0, + NAMESPACE_MNT, + NAMESPACE_PID, + NAMESPACE_UTS, + NAMESPACE_IPC, + NAMESPACE_NET, + NAMESPACE_CGROUP, + NAMESPACE_MAX +} Namespace_Type_t; + +typedef enum { + WAIT_CONDITION_STOPPED = 0, + WAIT_CONDITION_REMOVED = 1 +} wait_condition_t; + +typedef struct container_cgroup_resources { + uint16_t blkio_weight; + int64_t cpu_shares; + int64_t cpu_period; + int64_t cpu_quota; + int64_t cpu_realtime_period; + int64_t cpu_realtime_runtime; + char *cpuset_cpus; + char *cpuset_mems; + int64_t memory; + int64_t memory_swap; + int64_t memory_reservation; + int64_t kernel_memory; + int64_t pids_limit; + int64_t files_limit; + int64_t oom_score_adj; +} container_cgroup_resources_t; + +typedef struct container_events_format { + char *id; + uint32_t has_type; + container_events_type_t type; + uint32_t has_pid; + uint32_t pid; + uint32_t has_exit_status; + uint32_t exit_status; + types_timestamp_t timestamp; +} container_events_format_t; + +void container_cgroup_resources_free(container_cgroup_resources_t *cr); + + +typedef void (*container_events_callback_t)(const container_events_format_t *event); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/contrib/config/cni/default.json b/src/contrib/config/cni/default.json new file mode 100644 index 0000000..aa325c1 --- /dev/null +++ b/src/contrib/config/cni/default.json @@ -0,0 +1,14 @@ +{ + "cniVersion": "0.3.0", + "name": "default", + "type": "bridge", + "bridge": "cni0", + "ipam": { + "type": "host-local", + "subnet": "10.1.0.0/16", + "gateway": "10.1.0.1" + }, + "dns": { + "nameservers": [ "10.1.0.1" ] + } +} diff --git a/src/contrib/config/config.json b/src/contrib/config/config.json new file mode 100644 index 0000000..b20495e --- /dev/null +++ b/src/contrib/config/config.json @@ -0,0 +1,287 @@ +{ + "ociVersion": "1.0.0-rc5-dev", + "process": { + "terminal": true, + "consoleSize": { + "height": 0, + "width": 0 + }, + "user": { + "uid": 0, + "gid": 0 + }, + "args": [ + "/bin/bash" + ], + "env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm" + ], + "cwd": "/", + "capabilities": { + "bounding": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "effective": [ + ], + "inheritable": [ + ], + "permitted": [ + ], + "ambient": [ + ] + } + }, + "root": { + "path": "rootfs", + "readonly": false + }, + "hostname": "ubuntu", + "mounts": [ + { + "destination": "/proc", + "type": "proc", + "source": "proc", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + }, + { + "destination": "/dev", + "type": "tmpfs", + "source": "tmpfs", + "options": [ + "nosuid", + "strictatime", + "mode=755", + "size=65536k" + ] + }, + { + "destination": "/dev/pts", + "type": "devpts", + "source": "devpts", + "options": [ + "nosuid", + "noexec", + "newinstance", + "ptmxmode=0666", + "mode=0620", + "gid=5" + ] + }, + { + "destination": "/sys", + "type": "sysfs", + "source": "sysfs", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/dev/shm", + "type": "tmpfs", + "source": "shm", + "options": [ + "nosuid", + "noexec", + "nodev", + "mode=1777", + "size=65536k" + ] + }, + { + "destination": "/sys/fs/cgroup", + "type": "cgroup", + "source": "cgroup", + "options": [ + "nosuid", + "noexec", + "nodev", + "ro" + ] + }, + { + "destination": "/dev/mqueue", + "type": "mqueue", + "source": "mqueue", + "options": [ + "nosuid", + "noexec", + "nodev" + ] + } + ], + "linux": { + "resources": { + "devices": [ + { + "allow": false, + "type": "a", + "major": -1, + "minor": -1, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": -1, + "minor": -1, + "access": "m" + }, + { + "allow": true, + "type": "b", + "major": -1, + "minor": -1, + "access": "m" + }, + { + "allow": true, + "type": "c", + "major": 1, + "minor": 3, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 1, + "minor": 5, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 1, + "minor": 7, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 5, + "minor": 0, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 5, + "minor": 1, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 5, + "minor": 2, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 1, + "minor": 8, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 1, + "minor": 9, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 136, + "minor": -1, + "access": "rwm" + }, + { + "allow": true, + "type": "c", + "major": 10, + "minor": 200, + "access": "rwm" + }, + { + "allow": false, + "type": "c", + "major": 10, + "minor": 229, + "access": "rwm" + } + ] + }, + "namespaces": [ + { + "type": "pid" + }, + { + "type": "network" + }, + { + "type": "ipc" + }, + { + "type": "uts" + }, + { + "type": "mount" + } + ], + "maskedPaths": [ + "/proc/acpi", + "/proc/config.gz", + "/proc/kcore", + "/proc/keys", + "/proc/latency_stats", + "/proc/timer_list", + "/proc/timer_stats", + "/proc/sched_debug", + "/proc/scsi", + "/proc/signo", + "/proc/sig_catch", + "/proc/kbox", + "/proc/oom_extend", + "/proc/fdthreshold", + "/proc/fdstat", + "/proc/fdenable", + "/proc/files_panic_enable", + "/sys/firmware", + "/proc/cpuirqstat", + "/proc/memstat", + "/proc/iomem_ext", + "/proc/livepatch" + ], + "readonlyPaths": [ + "/proc/asound", + "/proc/bus", + "/proc/fs", + "/proc/irq", + "/proc/sys", + "/proc/sysrq-trigger", + "/proc/sysrq-region-size" + ] + } +} diff --git a/src/contrib/config/daemon.json b/src/contrib/config/daemon.json new file mode 100644 index 0000000..392b6d8 --- /dev/null +++ b/src/contrib/config/daemon.json @@ -0,0 +1,36 @@ +{ + "group": "lcrd", + "graph": "/var/lib/lcrd", + "state": "/var/run/lcrd", + "engine": "lcr", + "log-level": "ERROR", + "pidfile": "/var/run/lcrd.pid", + "log-opts": { + "log-file-mode": "0600", + "log-path": "/var/lib/lcrd", + "max-file": "1", + "max-size": "30KB" + }, + "log-driver": "stdout", + "hook-spec": "/etc/default/lcrd/hooks/default.json", + "start-timeout": "2m", + "storage-driver": "overlay2", + "storage-opts": [ + "overlay2.override_kernel_check=true" + ], + "registry-mirrors": [ + "docker.io" + ], + "insecure-registries": [ + "rnd-dockerhub.huawei.com" + ], + "pod-sandbox-image": "", + "image-opt-timeout": "5m", + "native.umask": "secure", + "network-plugin": "", + "cni-bin-dir": "", + "cni-conf-dir": "", + "image-layer-check": false, + "use-decrypted-key": true, + "insecure-skip-verify-enforce": false +} diff --git a/src/contrib/config/hooks/default.json b/src/contrib/config/hooks/default.json new file mode 100644 index 0000000..58fbe5e --- /dev/null +++ b/src/contrib/config/hooks/default.json @@ -0,0 +1,5 @@ +{ +"prestart":[], +"poststop":[], +"poststart":[] +} diff --git a/src/contrib/config/iSulad.sysconfig b/src/contrib/config/iSulad.sysconfig new file mode 100644 index 0000000..c25da0b --- /dev/null +++ b/src/contrib/config/iSulad.sysconfig @@ -0,0 +1,22 @@ +# /etc/sysconfig/iSulad + +# the iSulad daemon socket configuration +# +# Modify these options if you want to change the way the iSulad daemon runs +# OPTIONS='-H unix:///var/run/lcrd.sock' +# OPTIONS='-H tcp://0.0.0.0:2375' +# OPTIONS='-H tcp://0.0.0.0:2375 --tlsverify --tlscacert=/root/.iSulad/ca.pem --tlscert=/root/.iSulad/server-cert.pem --tlskey=/root/.iSulad/server-key.pem' + +# iSulad client machine env configuration +# +# 'DOCKER_CERT_PATH' contains the location of the client configuration files used for TLS verification. +#ISULAD_CERT_PATH=/etc/iSulad +# 'DOCKER_HOST' specifies the daemon socket to connect to. The default is a local socket(unix:///var/run/lcrd.sock). +#ISULAD_HOST=tcp://127.0.0.1:2375 +# 'DOCKER_TLS_VERIFY' enables Transport Layer Security (TLS) for the local Docker client +#ISULAD_TLS_VERIFY=1 + +# Modify 'SYSMONITOR_OPTIONS' if you want to change the way the iSulad daemon runs,because client need to adapt daemon +#SYSMONITOR_OPTIONS='-H unix:///var/run/lcrd.sock' +#SYSMONITOR_OPTIONS='-H tcp://127.0.0.1:2375' +#SYSMONITOR_OPTIONS='-H tcp://127.0.0.1:2375 --tlsverify --tlscacert=/root/.iSulad/ca.pem --tlscert=/root/.iSulad/cert.pem --tlskey=/root/.iSulad/key.pem' diff --git a/src/contrib/config/seccomp_default.json b/src/contrib/config/seccomp_default.json new file mode 100644 index 0000000..728355c --- /dev/null +++ b/src/contrib/config/seccomp_default.json @@ -0,0 +1,808 @@ +{ + "defaultAction": "SCMP_ACT_ERRNO", + "archMap": [ + { + "architecture": "SCMP_ARCH_X86_64", + "subArchitectures": [ + "SCMP_ARCH_X86", + "SCMP_ARCH_X32" + ] + }, + { + "architecture": "SCMP_ARCH_AARCH64", + "subArchitectures": [ + "SCMP_ARCH_ARM" + ] + } + ], + "syscalls": [ + { + "names": [ + "accept", + "accept4", + "access", + "adjtimex", + "alarm", + "bind", + "brk", + "capget", + "capset", + "chdir", + "chmod", + "chown", + "chown32", + "clock_getres", + "clock_gettime", + "clock_nanosleep", + "close", + "connect", + "copy_file_range", + "creat", + "dup", + "dup2", + "dup3", + "epoll_create", + "epoll_create1", + "epoll_ctl", + "epoll_ctl_old", + "epoll_pwait", + "epoll_wait", + "epoll_wait_old", + "eventfd", + "eventfd2", + "execve", + "execveat", + "exit", + "exit_group", + "faccessat", + "fadvise64", + "fadvise64_64", + "fallocate", + "fanotify_mark", + "fchdir", + "fchmod", + "fchmodat", + "fchown", + "fchown32", + "fchownat", + "fcntl", + "fcntl64", + "fdatasync", + "fgetxattr", + "flistxattr", + "flock", + "fork", + "fremovexattr", + "fsetxattr", + "fstat", + "fstat64", + "fstatat64", + "fstatfs", + "fstatfs64", + "fsync", + "ftruncate", + "ftruncate64", + "futex", + "futimesat", + "getcpu", + "getcwd", + "getdents", + "getdents64", + "getegid", + "getegid32", + "geteuid", + "geteuid32", + "getgid", + "getgid32", + "getgroups", + "getgroups32", + "getitimer", + "getpeername", + "getpgid", + "getpgrp", + "getpid", + "getppid", + "getpriority", + "getrandom", + "getresgid", + "getresgid32", + "getresuid", + "getresuid32", + "getrlimit", + "get_robust_list", + "getrusage", + "getsid", + "getsockname", + "getsockopt", + "get_thread_area", + "gettid", + "gettimeofday", + "getuid", + "getuid32", + "getxattr", + "inotify_add_watch", + "inotify_init", + "inotify_init1", + "inotify_rm_watch", + "io_cancel", + "ioctl", + "io_destroy", + "io_getevents", + "ioprio_get", + "ioprio_set", + "io_setup", + "io_submit", + "ipc", + "kill", + "lchown", + "lchown32", + "lgetxattr", + "link", + "linkat", + "listen", + "listxattr", + "llistxattr", + "_llseek", + "lremovexattr", + "lseek", + "lsetxattr", + "lstat", + "lstat64", + "madvise", + "memfd_create", + "mincore", + "mkdir", + "mkdirat", + "mknod", + "mknodat", + "mlock", + "mlock2", + "mlockall", + "mmap", + "mmap2", + "mprotect", + "mq_getsetattr", + "mq_notify", + "mq_open", + "mq_timedreceive", + "mq_timedsend", + "mq_unlink", + "mremap", + "msgctl", + "msgget", + "msgrcv", + "msgsnd", + "msync", + "munlock", + "munlockall", + "munmap", + "nanosleep", + "newfstatat", + "_newselect", + "open", + "openat", + "pause", + "pipe", + "pipe2", + "poll", + "ppoll", + "prctl", + "pread64", + "preadv", + "preadv2", + "prlimit64", + "pselect6", + "pwrite64", + "pwritev", + "pwritev2", + "read", + "readahead", + "readlink", + "readlinkat", + "readv", + "recv", + "recvfrom", + "recvmmsg", + "recvmsg", + "remap_file_pages", + "removexattr", + "rename", + "renameat", + "renameat2", + "restart_syscall", + "rmdir", + "rt_sigaction", + "rt_sigpending", + "rt_sigprocmask", + "rt_sigqueueinfo", + "rt_sigreturn", + "rt_sigsuspend", + "rt_sigtimedwait", + "rt_tgsigqueueinfo", + "sched_getaffinity", + "sched_getattr", + "sched_getparam", + "sched_get_priority_max", + "sched_get_priority_min", + "sched_getscheduler", + "sched_rr_get_interval", + "sched_setaffinity", + "sched_setattr", + "sched_setparam", + "sched_setscheduler", + "sched_yield", + "seccomp", + "select", + "semctl", + "semget", + "semop", + "semtimedop", + "send", + "sendfile", + "sendfile64", + "sendmmsg", + "sendmsg", + "sendto", + "setfsgid", + "setfsgid32", + "setfsuid", + "setfsuid32", + "setgid", + "setgid32", + "setgroups", + "setgroups32", + "setitimer", + "setpgid", + "setpriority", + "setregid", + "setregid32", + "setresgid", + "setresgid32", + "setresuid", + "setresuid32", + "setreuid", + "setreuid32", + "setrlimit", + "set_robust_list", + "setsid", + "setsockopt", + "set_thread_area", + "set_tid_address", + "setuid", + "setuid32", + "setxattr", + "shmat", + "shmctl", + "shmdt", + "shmget", + "shutdown", + "sigaltstack", + "signalfd", + "signalfd4", + "sigreturn", + "socket", + "socketcall", + "socketpair", + "splice", + "stat", + "stat64", + "statfs", + "statfs64", + "statx", + "symlink", + "symlinkat", + "sync", + "sync_file_range", + "syncfs", + "sysinfo", + "tee", + "tgkill", + "time", + "timer_create", + "timer_delete", + "timerfd_create", + "timerfd_gettime", + "timerfd_settime", + "timer_getoverrun", + "timer_gettime", + "timer_settime", + "times", + "tkill", + "truncate", + "truncate64", + "ugetrlimit", + "umask", + "uname", + "unlink", + "unlinkat", + "utime", + "utimensat", + "utimes", + "vfork", + "vmsplice", + "wait4", + "waitid", + "waitpid", + "write", + "writev" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + }, + "excludes": { + } + }, + { + "names": [ + "ptrace" + ], + "action": "SCMP_ACT_ALLOW", + "args": null, + "comment": "", + "includes": { + "minKernel": "4.8" + }, + "excludes": { + } + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 0, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": { + }, + "excludes": { + } + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 8, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": { + }, + "excludes": { + } + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 131072, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": { + }, + "excludes": { + } + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 131080, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": { + }, + "excludes": { + } + }, + { + "names": [ + "personality" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 4294967295, + "valueTwo": 0, + "op": "SCMP_CMP_EQ" + } + ], + "comment": "", + "includes": { + }, + "excludes": { + } + }, + { + "names": [ + "sync_file_range2" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "arches": [ + "ppc64le" + ] + }, + "excludes": { + } + }, + { + "names": [ + "arm_fadvise64_64", + "arm_sync_file_range", + "sync_file_range2", + "breakpoint", + "cacheflush", + "set_tls" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "arches": [ + "arm", + "arm64" + ] + }, + "excludes": { + } + }, + { + "names": [ + "arch_prctl" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "arches": [ + "amd64", + "x32" + ] + }, + "excludes": { + } + }, + { + "names": [ + "modify_ldt" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "arches": [ + "amd64", + "x32", + "x86" + ] + }, + "excludes": { + } + }, + { + "names": [ + "s390_pci_mmio_read", + "s390_pci_mmio_write", + "s390_runtime_instr" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "arches": [ + "s390", + "s390x" + ] + }, + "excludes": { + } + }, + { + "names": [ + "open_by_handle_at" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_DAC_READ_SEARCH" + ] + }, + "excludes": { + } + }, + { + "names": [ + "bpf", + "clone", + "fanotify_init", + "lookup_dcookie", + "mount", + "name_to_handle_at", + "perf_event_open", + "quotactl", + "setdomainname", + "sethostname", + "setns", + "syslog", + "umount", + "umount2", + "unshare" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + }, + "excludes": { + } + }, + { + "names": [ + "clone" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 0, + "value": 2080505856, + "valueTwo": 0, + "op": "SCMP_CMP_MASKED_EQ" + } + ], + "comment": "", + "includes": { + }, + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ], + "arches": [ + "s390", + "s390x" + ] + } + }, + { + "names": [ + "clone" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + { + "index": 1, + "value": 2080505856, + "valueTwo": 0, + "op": "SCMP_CMP_MASKED_EQ" + } + ], + "comment": "s390 parameter ordering for clone is different", + "includes": { + "arches": [ + "s390", + "s390x" + ] + }, + "excludes": { + "caps": [ + "CAP_SYS_ADMIN" + ] + } + }, + { + "names": [ + "reboot" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_BOOT" + ] + }, + "excludes": { + } + }, + { + "names": [ + "chroot" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_CHROOT" + ] + }, + "excludes": { + } + }, + { + "names": [ + "delete_module", + "init_module", + "finit_module", + "query_module" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_MODULE" + ] + }, + "excludes": { + } + }, + { + "names": [ + "acct" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_PACCT" + ] + }, + "excludes": { + } + }, + { + "names": [ + "kcmp", + "process_vm_readv", + "process_vm_writev", + "ptrace" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_PTRACE" + ] + }, + "excludes": { + } + }, + { + "names": [ + "iopl", + "ioperm" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_RAWIO" + ] + }, + "excludes": { + } + }, + { + "names": [ + "settimeofday", + "stime", + "clock_settime" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_TIME" + ] + }, + "excludes": { + } + }, + { + "names": [ + "vhangup" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_TTY_CONFIG" + ] + }, + "excludes": { + } + }, + { + "names": [ + "get_mempolicy", + "mbind", + "set_mempolicy" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYS_NICE" + ] + }, + "excludes": { + } + }, + { + "names": [ + "syslog" + ], + "action": "SCMP_ACT_ALLOW", + "args": [ + ], + "comment": "", + "includes": { + "caps": [ + "CAP_SYSLOG" + ] + }, + "excludes": { + } + } + ] +} diff --git a/src/contrib/docker b/src/contrib/docker new file mode 100755 index 0000000..ab3b606 --- /dev/null +++ b/src/contrib/docker @@ -0,0 +1,572 @@ +####################################################################### +##- @Copyright (C) Huawei Technologies., Ltd. 2017-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +##- @Description: generate cetification +##- @Author: wujing +##- @Create: 2019-04-25 +####################################################################### +#!/bin/bash +#set -x +LWC_PATH=/usr +DEFAULT_ROOTFS_PATH=/dev/ram0 +LCRC_COMMON_OPTION= +SOCKET_PATH=unix:///var/run/docker.sock +SPACE_REPLACE_MAGIC="[#)" +export IMAGE_NONE_PATH=$DEFAULT_ROOTFS_PATH +info_lcrc_command_help() +{ + echo "See 'docker $1 --help'." 1>&2 +} + +error_invalid_flag() +{ + echo "flag provided but not defined: $2" 1>&2 + info_lcrc_command_help $1 +} + +INFO_DOCKER_COMMAND_NO_ARGUMENT() +{ + echo "\"docker $1\" requires at least 1 argument(s)." 1>&2 + info_lcrc_command_help $1 +} + +LOG_ERROR() +{ + echo "[ERROR] $1" 1>&2 +} + +RUN_USAGE=" +Usage: docker run [OPTIONS] none|IMAGE [COMMAND] [ARG...] + +Run a command in a new container + +Options: + --cap-add=[] Add Linux capabilities + --cap-drop=[] Drop Linux capabilities + --cpuset-cpus CPUs in which to allow execution (e.g. 0-3, 0,1) + --cpu-shares CPU shares (relative weight) + --cpu-quota Limit CPU CFS (Completely Fair Scheduler) quota + -d, --detach Run container in background and print container ID + --device=[] Add a host device to the container + -e, --env=[] Set environment variables + --entrypoint Entrypoint to run when starting the container + --external-rootfs=PATH Specify the custom rootfs that is not managed by LCRD for the container, directory or block device + --help Print usage + --hook-spec File containing hook definition(prestart, poststart, poststop) + -h, --hostname Container host name + --hugetlb-limit=[] Huge page limit (format: [size:], e.g. --hugetlb-limit 2MB:32MB) + -i, --interactive Keep STDIN open even if not attached + --log-opt=[] Log driver options + -m, --memory Memory limit + --memory-swap Swap limit equal to memory plus swap: '-1' to enable unlimited swap + --mount Attach a filesystem mount to the service + --name=NAME NAME of the container + --net=none Connect a container to a network + --privileged Give extended privileges to this container + -R, --runtime Runtime to use for containers(default: lcr) + --restart=no Restart policy to apply when a container exits(no, always, on-failure[:max-retries]) + -t, --tty Allocate a pseudo-TTY + -u, --user Username or UID (format: [:]) + -v, --volume=[] Bind mount a volume" + +command_run() +{ + shift + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT run + exit 1 + fi + for param in $* + do + if [ --help == $param ]; then + echo "$RUN_USAGE" + exit 0 + fi + done + exec $LWC_PATH/bin/lcrc run $LCRC_COMMON_OPTION "$@" +} + +STOP_USAGE=" +Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] + +Stop a running container. +Sending SIGTERM and then SIGKILL after a grace period + +Options: + --help Print usage + -t, --time int Seconds to wait for stop before killing it (default 10)" + +command_stop() +{ + shift + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT stop + exit 1 + fi + + for param in $* + do + if [ --help == $param ]; then + echo "$STOP_USAGE" + exit 0 + fi + done + + exec $LWC_PATH/bin/lcrc stop $LCRC_COMMON_OPTION "$@" +} + +KILL_USAGE=" +Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] + +Kill one or more running containers + +Options: + --help Print usage + -s, --signal string Signal to send to the container (default "KILL")" + +command_kill() +{ + shift + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT kill + exit 1 + fi + for param in $* + do + if [ --help == $param ]; then + echo "$KILL_USAGE" + exit 0 + fi + done + exec $LWC_PATH/bin/lcrc kill $LCRC_COMMON_OPTION "$@" +} + +PS_USAGE=" +Usage: docker ps [OPTIONS] + +List containers + +Options: + -a, --all Show all containers (default shows just running) + --help Print usage + -q, --quiet Only display containers name" + +command_ps() +{ + shift + for param in $* + do + if [ --help == $param ]; then + echo "$PS_USAGE" + exit 0 + fi + done + RET=0 + $LWC_PATH/bin/lcrc ps $LCRC_COMMON_OPTION "$@" + let "RET=$?" + if [ $RET == 1 -o $RET == 125 ]; then + info_lcrc_command_help ps + fi + exit $RET +} + +PULL_USAGE=" +Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST] + +Pull an image or a repository from a registry + + --help Print STOP_USAGE" + +command_pull() +{ + shift + while true ; do + case "$1" in + --help) + echo "$PULL_USAGE" + exit 0 ;; + *) + break ;; + esac # --- end of case --- + done + + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT pull + exit 1 + fi + + IMAGE_NAME=`echo "$1" | awk -F : '{print $1}'` + echo "Pulling repository docker.io/library/$IMAGE_NAME" + echo "Error while pulling image: Get https://index.docker.io/v1/repositories/library/$IMAGE_NAME/images" 1>&2 + exit 1 +} + +command_version() +{ + exec $LWC_PATH/bin/lcrc version $LCRC_COMMON_OPTION +} + +INSPECT_USAGE=" +Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...] + +Return low-level information on a container or image + +Options: + -f, --format string Format the output using the given Go template + --help Print usage" + +command_inspect() +{ + shift + while true ; do + case "$1" in + --help) + echo "$INSPECT_USAGE" + exit 0;; + -f | --format) + FORMAT="$2" + shift 2 ;; + --format=*) + FORMAT="${1#*=}" + shift ;; + -f=*) + FORMAT="${1#*=}" + shift ;; + --) + shift + break ;; + -*) + error_invalid_flag inspect $1 + exit 125 ;; + *) + break ;; + esac # --- end of case --- + done + + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT inspect + exit 1 + fi + + if [ -z "$FORMAT" ] ; then + exec $LWC_PATH/bin/lcrc inspect $LCRC_COMMON_OPTION $LCRC_INSPECT_OPTION "$@" + else + exec $LWC_PATH/bin/lcrc inspect $LCRC_COMMON_OPTION $LCRC_INSPECT_OPTION -f "$FORMAT" "$@" + fi +} + +EXEC_USAGE=" +Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] + +Run a command in a running container + +Options: + -d, --detach Detached mode: run command in the background + --help Print usage" + +command_exec() +{ + shift + if [ -z "$*" ] ; then + INFO_DOCKER_COMMAND_NO_ARGUMENT exec + exit 1 + fi + for param in $* + do + if [ --help == $param ]; then + echo "$EXEC_USAGE" + exit 0 + fi + done + + exec $LWC_PATH/bin/lcrc exec $LCRC_COMMON_OPTION "$@" +} + +RM_USAGE=" +Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...] + +Remove one or more containers + +Options: + -f, --force Force the removal of a running container (uses SIGKILL) + --help Print usage" + +command_rm() +{ + shift + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT rm + exit 1 + fi + + for param in $* + do + if [ --help == $param ]; then + echo "$RM_USAGE" + exit 0 + fi + done + exec $LWC_PATH/bin/lcrc rm $LCRC_COMMON_OPTION "$@" +} + +RMI_USAGE=" +Usage: docker rmi [OPTIONS] IMAGE [IMAGE...] + +Remove one or more images + +Options: + -f, --force Force removal of the image + --help Print usage" + +command_rmi() +{ + shift + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT rmi + exit 1 + fi + for param in $* + do + if [ --help == $param ]; then + echo "$RMI_USAGE" + exit 0 + fi + done + exec $LWC_PATH/bin/lcrc rmi $LCRC_COMMON_OPTION "$@" +} + +LOAD_USAGE=" +Usage: docker load [OPTIONS] --input=FILE --type=TYPE + +load an image from a manifest + +Options: + --help Print usage + -i, --input Read from a manifest + -t, --type Image type, only support 'embedded' currently" + +command_load() +{ + shift + for param in $* + do + if [ --help == $param ]; then + echo "$LOAD_USAGE" + exit 0 + fi + done + exec $LWC_PATH/bin/lcrc load $LCRC_COMMON_OPTION "$@" +} + +IMAGES_USAGE=" +Usage: docker images + +List images + +Options: + --help Print usage + -q, --quiet Only display image names" + +command_images() +{ + shift + for param in $* + do + if [ --help == $param ]; then + echo "$IMAGES_USAGE" + exit 0 + fi + done + exec $LWC_PATH/bin/lcrc images $LCRC_COMMON_OPTION "$@" +} + +RESTART_USAGE=" +Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...] + +Restart a container + +Options: + --help Print usage + -t, --time int Seconds to wait for stop before killing the container (default 10)" + +command_restart() +{ + shift + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT restart + exit 1 + fi + for param in $* + do + if [ --help == $param ]; then + echo "$RESTART_USAGE" + exit 0 + fi + done + exec $LWC_PATH/bin/lcrc restart $LCRC_COMMON_OPTION "$@" +} + +WAIT_USAGE=" +Usage: docker wait CONTAINER + +Block until one container stop, then print their exit codes + +Options: + --help Print usage" + +command_wait() +{ + shift + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT wait + exit 1 + fi + for param in $* + do + if [ --help == $param ]; then + echo "$WAIT_USAGE" + exit 0 + fi + done + exec $LWC_PATH/bin/lcrc wait $LCRC_COMMON_OPTION "$@" +} + +LOGS_USAGE=" +Usage: docker logs [OPTIONS] CONTAINER + +Fetch the logs of a container + +Options: + -f, --follow Follow log output + --help Print usage + --tail=all Number of lines to show from the end of the logs" + +command_logs() +{ + shift + if [ -z "$*" ]; then + INFO_DOCKER_COMMAND_NO_ARGUMENT logs + exit 1 + fi + + for param in $* + do + if [ --help == $param ]; then + echo "$LOGS_USAGE" + exit 0 + fi + done + + RET=0 + $LWC_PATH/bin/lcrc logs $LCRC_COMMON_OPTION "$@" + let "RET=$?" + if [ $RET == 1 ]; then + info_lcrc_command_help logs + fi + exit $RET +} + +DOCKER_COMMAND_USAGE=" +Usage: docker [OPTIONS] COMMAND [arg...] + docker [ --help | -v | --version ] + +A self-sufficient runtime for containers + +Options: + --help Print usage + -H, --host=[] Daemon socket(s) to connect to + -v, --version Print version information and quit + +Commands: + + exec Run a command in a running container + images List images + inspect Return low-level information on a container or image + kill Kill one or more running containers + load Load an image from a manifest + logs Fetch the logs of a container + ps List containers + restart Restart one or more containers + rm Remove one or more containers + rmi Remove one or more images + run Run a command in a new container + stop Stop one or more running containers + version Show the Docker version information + wait Block until one or more containers stop, then print their exit codes + +Run 'docker COMMAND --help' for more information on a command." + +command_docker() +{ + case $1 in + run) + command_run "$@" ;; + stop) + command_stop "$@" ;; + kill) + command_kill "$@" ;; + ps) + command_ps "$@" ;; + pull) + command_pull "$@";; + version) + command_version "$@";; + inspect) + command_inspect "$@";; + exec) + command_exec "$@";; + rm) + command_rm "$@";; + rmi) + command_rmi "$@";; + load) + command_load "$@";; + images) + command_images "$@";; + restart) + command_restart "$@";; + wait) + command_wait "$@";; + logs) + command_logs "$@";; + *) + echo "docker: '$1' is not a docker command." ; + echo "See 'docker --help'" ; + exit 1 ;; + esac +} + +if [ -z "$*" ] ; then + echo "$DOCKER_COMMAND_USAGE" + exit 0 +fi + +while true ; do + case $1 in + --help | help | "") + echo "$DOCKER_COMMAND_USAGE" ; + exit 0 ;; + -H | --host) + SOCKET_PATH="$2" + shift 2 ;; + --host=*) + SOCKET_PATH=${1#*=} + shift ;; + -v | --version) + echo "Docker version 1.11.2, build df5f654" + exit 0 ;; + *) + break;; + esac +done + +LCRC_COMMON_OPTION="$LCRC_COMMON_OPTION -H $SOCKET_PATH" + +command_docker "$@"; diff --git a/src/contrib/env_checkconfig b/src/contrib/env_checkconfig new file mode 100755 index 0000000..a89cfb9 --- /dev/null +++ b/src/contrib/env_checkconfig @@ -0,0 +1,165 @@ +#!/bin/sh + + +KERNELCONFIG="/proc/config.gz" +MODNAME="configs" +KVER="$(uname -r)" +HEADERS_CFG="/lib/modules/$KVER/build/.config" +BOOT_CFG="/boot/config-$KVER" + +CAT="cat" + +SETCOLOR_SUCCESS="echo -en \\033[1;32m" +SETCOLOR_FAILURE="echo -en \\033[1;31m" +SETCOLOR_WARNING="echo -en \\033[1;33m" +SETCOLOR_NORMAL="echo -en \\033[0;39m" + +$SETCOLOR_SUCCESS +echo "" +echo "---This is iSula environment check program---" +echo "" +if [ ! -f /etc/euleros-release ]; then + $SETCOLOR_FAILURE + echo "Make sure you are running this program on EulerOS System" + exit 1 +fi + +config_set() +{ + $CAT $KERNELCONFIG | grep "$1=[y|m]" > /dev/null + return $? +} + +config_enable() +{ + + config_set $1 + force=$2 + + if [ $? -eq 0 ]; then + $SETCOLOR_SUCCESS + echo "enabled" + $SETCOLOR_NORMAL + else + if [ "$force" = yes ]; then + $SETCOLOR_FAILURE + echo "required" + $SETCOLOR_NORMAL + else + $SETCOLOR_WARNING + echo "missing" + $SETCOLOR_NORMAL + fi + fi +} + +has_lib() +{ + ldconfig -v 2>&1 | grep $1 > /dev/null 2>&1 + + if [ $? -eq 0 ]; then + $SETCOLOR_SUCCESS + echo "installed" + $SETCOLOR_NORMAL + else + $SETCOLOR_WARNING + echo "missing" + $SETCOLOR_NORMAL + fi +} + +print_cgroup() +{ + awk '$1 !~ /#/ && $3 == mp { print $2; } ; END { exit(0); } ' "mp=$1" "$2" ; +} + + +CGROUP_PATH=`print_cgroup cgroup /proc/self/mounts | head -n 1` + +if [ ! -f $KERNELCONFIG ]; then + + if [ -f "${HEADERS_CFG}" ]; then + KERNELCONFIG=${HEADERS_CFG} + fi + + if [ -f "${BOOT_CFG}" ]; then + KERNELCONFIG=${BOOT_CFG} + fi + + if [ ! -f $KERNELCONFIG ]; then + $SETCOLOR_FAILURE && echo "Can not get kernel configuration" >&2 + exit 1 + fi +fi + +if gunzip -tq < $KERNELCONFIG 2>/dev/null; then + CAT="zcat" +fi + +$SETCOLOR_NORMAL +echo "--- Namespaces Config ---" +#you can add more namespace type here + +echo -n "Ipc Namespace Result: " && config_enable CONFIG_IPC_NS yes +echo -n "Pid Namespace Result: " && config_enable CONFIG_PID_NS yes +echo -n "User Namespace Result: " && config_enable CONFIG_USER_NS +echo -n "Network Namespace Result: " && config_enable CONFIG_NET_NS +echo -n "Mount Namespaces Result: " && config_enable CONFIG_NAMESPACES yes +echo -n "Utsname Namespace Result: " && config_enable CONFIG_UTS_NS + +if config_set CONFIG_USER_NS; then + if type newuidmap > /dev/null 2>&1; then + f=`type -P newuidmap` + if [ ! -u "${f}" ]; then + $SETCOLOR_WARNING + echo "Warning: newuidmap is not setuid-root" + fi + else + echo "newuidmap not installed" + fi + if type newgidmap > /dev/null 2>&1; then + f=`type -P newgidmap` + if [ ! -u "${f}" ]; then + $SETCOLOR_WARNING + echo "Warning: newgidmap is not setuid-root" + fi + else + echo "newgidmap not installed" + fi +fi + + +echo "" +echo "--- Cgroups Config---" +CGROUP_PATH=`print_cgroup cgroup /proc/self/mounts | head -n 1` + +echo -n "Cgroup: " && config_enable CONFIG_CGROUPS yes + +if [ -f $CGROUP_PATH/cgroup.clone_children ]; then + echo -n "Cgroup clone_children flag: " && + $SETCOLOR_SUCCESS + echo "enabled" + $SETCOLOR_NORMAL +else + echo -n "Cgroup namespace: " && config_enable CONFIG_CGROUP_NS yes +fi +echo -n "Cpu account Cgroup Result: " && config_enable CONFIG_CGROUP_CPUACCT +echo -n "Device Cgroup Result: " && config_enable CONFIG_CGROUP_DEVICE +echo -n "Pids Cgroup Result: " && config_enable CONFIG_CGROUP_PIDS +echo -n "Hugetlb Cgroup Result: " && config_enable CONFIG_CGROUP_HUGETLB +echo -n "Freezer Cgroup Result: " && config_enable CONFIG_CGROUP_FREEZER +echo -n "Memory controller Cgroup Result: " +config_enable CONFIG_MEMCG +config_set CONFIG_SMP && echo -n "Cpuset Cgroup Result: " && config_enable CONFIG_CPUSETS + +echo "" +echo "--- Third-party Packages ---" +echo -n "libyajl: " && has_lib libyajl +echo -n "libhttp_parser: " && has_lib libhttp_parser +echo -n "libevhtp.so.1.2.16: " && has_lib libevhtp.so.1.2.16 +echo -n "libseccomp: " && has_lib libseccomp +echo -n "libcap.so: " && has_lib libcap.so +echo -n "libsecurec.so: " && has_lib libsecurec.so + +echo "---------------------------------" + diff --git a/src/contrib/generate_certificate.bash b/src/contrib/generate_certificate.bash new file mode 100755 index 0000000..831ade2 --- /dev/null +++ b/src/contrib/generate_certificate.bash @@ -0,0 +1,88 @@ +####################################################################### +##- @Copyright (C) Huawei Technologies., Ltd. 2017-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +##- @Description: generate cetification +##- @Author: wujing +##- @Create: 2019-04-25 +####################################################################### +#!/bin/bash +set -e +echo -n "Enter pass phrase:" +read password +echo -n "Enter public network ip:" +read publicip +echo -n "Enter host:" +read HOST + +echo " => Using hostname: tcp://$publicip:2375, You MUST connect to iSulad using this host!" + +mkdir -p $HOME/.iSulad +cd $HOME/.iSulad +rm -rf $HOME/.iSulad/* + +echo " => Generating CA key" +openssl genrsa -passout pass:$password -aes256 -out ca-key.pem 4096 +echo " => Generating CA certificate" +openssl req -passin pass:$password -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem -subj "/C=CN/ST=zhejiang/L=hangzhou/O=Huawei/OU=iSulad/CN=iSulad@huawei.com" +echo " => Generating server key" +openssl genrsa -passout pass:$password -out server-key.pem 4096 +echo " => Generating server CSR" +openssl req -passin pass:$password -subj /CN=$HOST -sha256 -new -key server-key.pem -out server.csr +echo subjectAltName = DNS:$HOST,IP:$publicip,IP:127.0.0.1 >> extfile.cnf +echo extendedKeyUsage = serverAuth >> extfile.cnf +echo " => Signing server CSR with CA" +openssl x509 -req -passin pass:$password -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf +echo " => Generating client key" +openssl genrsa -passout pass:$password -out key.pem 4096 +echo " => Generating client CSR" +openssl req -passin pass:$password -subj '/CN=client' -new -key key.pem -out client.csr +echo " => Creating extended key usage" +echo extendedKeyUsage = clientAuth > extfile-client.cnf +echo " => Signing client CSR with CA" +openssl x509 -req -passin pass:$password -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile-client.cnf +rm -v client.csr server.csr extfile.cnf extfile-client.cnf +chmod -v 0400 ca-key.pem key.pem server-key.pem +chmod -v 0444 ca.pem server-cert.pem cert.pem +if [ -d "/etc/profile.d" ]; then + echo " => Creating profile.d/iSulad" + sudo bash -c "echo '#!/bin/bash + export ISULAD_HOST=tcp://$publicip:2375 + export ISULAD_CERT_PATH=$HOME/.iSulad + export ISULAD_TLS_VERIFY=1' > /etc/profile.d/iSulad.sh" + sudo chmod +x /etc/profile.d/iSulad.sh + source /etc/profile.d/iSulad.sh +else + echo " => WARNING: No /etc/profile.d directoy on your system." + echo " => You will need to set the following environment variables before running the iSulad client(lcrc):" + echo " => ISULAD_HOST=tcp://$publicip:2375" + echo " => ISULAD_CERT_PATH=$HOME/.iSulad" + echo " => ISULAD_TLS_VERIFY=1" +fi + +OPTIONS="--tlsverify --tlscacert=$HOME/.iSulad/ca.pem --tlscert=$HOME/.iSulad/server-cert.pem --tlskey=$HOME/.iSulad/server-key.pem -H=0.0.0.0:2375" +if [ -f "/lib/systemd/system/lcrd.service" ]; then + echo " => Configuring /lib/systemd/system/lcrd.service" + SERVICE_BACKUP="/lib/systemd/system/lcrd.service.$(date +"%s")" + mv /lib/systemd/system/lcrd.service $SERVICE_BACKUP + sudo sh -c "echo '# The following line was added by iSulad TLS configuration script + OPTIONS=\"$OPTIONS\" + # A backup of the old file is at $SERVICE_BACKUP.' >> /etc/sysconfig/iSulad" + echo " => Backup file location: $SERVICE_BACKUP" +else + echo " => WARNING: No /etc/sysconfig/iSulad file found on your system." + echo " => You will need to configure your iSulad daemon with the following options:" + echo " => $OPTIONS" +fi + +export ISUALD_HOST=tcp://$publicip:2375 +export ISULAD_CERT_PATH=$HOME/.iSulad +export ISULAD_TLS_VERIFY=1 +echo " => Done! You just need to restart iSulad for the changes to take effect" + diff --git a/src/contrib/init/lcrd.init b/src/contrib/init/lcrd.init new file mode 100644 index 0000000..42b4dcc --- /dev/null +++ b/src/contrib/init/lcrd.init @@ -0,0 +1,133 @@ +####################################################################### +##- @Copyright (C) Huawei Technologies., Ltd. 2017-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +##- @Description: generate cetification +##- @Author: wujing +##- @Create: 2019-04-25 +#######################################################################*/ +#! /bin/sh + +# Provides: lcrd +# Short-Description: light-weighted container runtime daemon +# Description: lcrd is a light-weighted container runtime daemon + +set -e + +# /etc/init.d/lcrd: start and stop the lcrd daemon + +DAEMON=/usr/bin/lcrd +LCRD_ENABLE=true +LCRD_OPTS='-s test' +LCRD_DEFAULTS_FILE=/etc/default/lcrd +LCRD_CONFIG_FILE=/etc/isulad/daemon.json +LCRD_PID_FILE=/var/run/lcrd.pid + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +if [ -s $LCRD_DEFAULTS_FILE ]; then + . $LCRD_DEFAULTS_FILE + case "x$LCRD_ENABLE" in + xtrue|xfalse) ;; + xinetd) exit 0 + ;; + *) log_failure_msg "Value of LCRD_ENABLE in $LCRD_DEFAULTS_FILE must be either 'true' or 'false';" + log_failure_msg "not starting lcrd daemon." + exit 1 + ;; + esac +fi + +export PATH="${PATH:+$PATH:}/usr/sbin:/sbin:/usr/local/bin" + +lcrd_start() { + if [ ! -s "$LCRD_CONFIG_FILE" ]; then + log_failure_msg "missing or empty config file $LCRD_CONFIG_FILE" + log_end_msg 1 + exit 0 + fi + if /usr/bin/nohup $DARMON --pidfile $LCRD_PID_FILE $LCRD_OPTS & + then + rc=0 + sleep 1 + if ! kill -0 $(cat $LCRD_PID_FILE) >/dev/null 2>&1; then + log_failure_msg "lcrd daemon failed to start" + rc=1 + fi + else + rc=1 + fi + if [ $rc -eq 0 ]; then + log_end_msg 0 + else + log_end_msg 1 + rm -f $LCRD_PID_FILE + fi +} # lcrd_start + + +case "$1" in + start) + if "$LCRD_ENABLE"; then + log_daemon_msg "Starting lcrd daemon" "lcrd" + if [ -s $LCRD_PID_FILE ] && kill -0 $(cat $LCRD_PID_FILE) >/dev/null 2>&1; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + lcrd_start + else + if [ -s "$LCRD_CONFIG_FILE" ]; then + [ "$VERBOSE" != no ] && log_warning_msg "lcrd daemon not enabled in $LCRD_DEFAULTS_FILE, not starting..." + fi + fi + ;; + stop) + log_daemon_msg "Stopping lcrd daemon" "lcrd" + kill -INT $(cat $LCRD_PID_FILE) + log_end_msg $? + rm -f $LCRD_PID_FILE + ;; + + reload|force-reload) + log_warning_msg "Reloading lcrd daemon: not needed, as the daemon" + log_warning_msg "re-reads the config file whenever a client connects." + ;; + + restart) + set +e + if $LCRD_ENABLE; then + log_daemon_msg "Restarting lcrd daemon" "lcrd" + if [ -s $LCRD_PID_FILE ] && kill -0 $(cat $LCRD_PID_FILE) >/dev/null 2>&1; then + kill -INT $(cat $LCRD_PID_FILE) + sleep 1 + else + log_warning_msg "lcrd daemon not running, attempting to start." + rm -f $LCRD_PID_FILE + fi + lcrd_start + else + if [ -s "$LCRD_CONFIG_FILE" ]; then + [ "$VERBOSE" != no ] && log_warning_msg "lcrd daemon not enabled in $LCRD_DEFAULTS_FILE, not starting..." + fi + fi + ;; + + status) + status_of_proc -p $LCRD_PID_FILE "$DAEMON" lcrd + exit $? # notreached due to set -e + ;; + *) + echo "Usage: /etc/init.d/lcrd {start|stop|reload|force-reload|restart|status}" + exit 1 +esac + +exit 0 diff --git a/src/contrib/init/lcrd.service b/src/contrib/init/lcrd.service new file mode 100644 index 0000000..a0b3bf5 --- /dev/null +++ b/src/contrib/init/lcrd.service @@ -0,0 +1,23 @@ +[Unit] +Description=iSulad Application Container Engine +Documentation=http://code.huawei.com/containers/iSulad/tree/cri/documentation +After=network.target + +[Service] +Type=notify +EnvironmentFile=-/etc/sysconfig/iSulad +ExecStart=/usr/bin/lcrd $OPTIONS +ExecReload=/bin/kill -s HUP $MAINPID +LimitNOFILE=1048576 +LimitNPROC=infinity +LimitCORE=infinity +TimeoutStartSec=0 +Delegate=yes +KillMode=process +Restart=on-failure +StartLimitBurst=3 +StartLimitInterval=60s + +[Install] +WantedBy=multi-user.target + diff --git a/src/contrib/sysmonitor/isulad-check.sh b/src/contrib/sysmonitor/isulad-check.sh new file mode 100755 index 0000000..2bad76b --- /dev/null +++ b/src/contrib/sysmonitor/isulad-check.sh @@ -0,0 +1,258 @@ +####################################################################### +##- @Copyright (C) Huawei Technologies., Ltd. 2017-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +##- @Description: isulad check +##- @Author: maoweiyong +##- @Create: 2019-02-25 +#######################################################################*/ +#!/bin/sh +source /etc/sysconfig/iSulad +#backtrace定位日志存放目录 +log_dir="/var/lib/lcrd/isulad-monitor" +#backtrace定位日志占用总大小,单位为KB +default_size="10240" +#backtrace定位日志总个数 +default_num="4" +#健康检查文件 +healthcheck_file="/var/run/lcrd/isulad_healcheck_status" +createfile_time="/var/run/lcrd/isulad_healcheck_ctime" + +#健康检查超时时间 +declare -i timeout_time=900 +declare -i status_change_time=930 + +#status_check +#只进行基本的daemon状态检测,包括pid是否存在,进程状态是否异常,不进行其他的检查 +#正常返回0,异常返回1 +status_check(){ + pid=$1 + if [ $? -ne 0 ];then + echo "cat /var/run/lcrd.pid failed!" + return 1 + fi + + cat /proc/${pid}/status >> /dev/null 2>&1 + if [ $? -ne 0 ];then + echo "/var/run/lcrd.pid exitst while process not exists!" + return 1 + fi + + dstate=`cat /proc/${pid}/status | grep State | awk '{print $2}'` + if [[ ("${dstate}x" == "Zx") || ("${dstate}x" == "Tx") ]]; then + # Z状态、T状态 + echo " dstate="${dstate}", should restart!" + return 1 + fi + return 0 +} +#轮询三次进行status_check +#如果三次都失败,则失败,如果有一次超时6s,也失败 +#如果三次有一次成功,则成功 +basic_check() +{ + for((i=1;i<=5;i++)); + do + sleep 2 + date_start=$(date +%s) + + tmp_id=`cat /var/run/lcrd.pid` + status_check $tmp_id + check_daemon=$? + + date_end=$(date +%s) + if [ ${check_daemon} -eq 0 ];then + return 0 + elif [ $((date_end-date_start)) -gt 6 ];then + echo "check date is more than 6s!" + return 1 + fi + done + return 1 +} + +#检查backtrace日志文件个数是否大于5个 +checkFileNum(){ + num=`ls $log_dir | wc -l` + rm_num=$(($num-$default_num)) + k=2 + + if [ $rm_num -gt 0 ]; then + for ((i=1;i<=$rm_num;i++)) + do + rm -f $log_dir/$(ls -rt -l $log_dir | head -$k | tail -1 | awk '{print $9}') + if [ $? -ne 0 ];then + k=$((k+1)) + fi + done + fi + + return 0 +} + +#检查backtrace日志文件总大小是否大于10M +checkFileSize(){ + num=`ls $log_dir | wc -l` + k=2 + + #文件夹本身占用4KB + size=$((`du -sk $log_dir| awk '{print $1}'`-4)) + while [ $size -ge $default_size ] + do + if [ $num -eq 0 ];then + break; + fi + rm -f $log_dir/$(ls -rt -l $log_dir | head -$k | tail -1 | awk '{print $9}') + if [ $? -ne 0 ];then + k=$((k+1)) + num=$((num-1)) + fi + size=$((`du -sk $log_dir | awk '{print $1}'`-4)) + done + + return 0 +} + +#维护/var/lib/lcrd/isulad-monitor目录下的log文件 +#限制stack log的存储空间占用 +manage_monitor_log(){ + checkFileNum + if [ $? -ne 0 ];then + echo "Failed to checkfilenum" + return 1; + fi + + checkFileSize + if [ $? -ne 0 ];then + echo "Failed to checkfilesize" + return 1; + fi +} + +#获取健康检查状态文件的创建时间 +#如果文件running超过status_change_time,则代表健康检查进程故障,或者检查本身故障,或者状态文件被人恶意损坏或者更改 +#此时需要重新进行健康检查并删除原文件 +createtime_check(){ + if [ ! -f "${createfile_time}" ];then + return 1 + fi + date_create=$(cat ${createfile_time}) + date_now=$(date +%s) + if [ $((date_now-date_create)) -gt ${status_change_time} ]; then + return 1 + fi + return 0 +} + +clean_healthcheck(){ + rm -rf ${healthcheck_file} + rm -rf ${createfile_time} +} +#生成堆栈日志文件 +gen_stack_log(){ + pid=$(cat /var/run/lcrd.pid) + if [ $? -ne 0 ];then + echo "cat /var/run/lcrd.pid failed!" + return 1 + fi + kill -34 ${pid} +} + +#执行健康检查的后台线程,需要维护健康检查运行时 +health_check(){ + create_time=$(date +%s) + + touch ${createfile_time} + chmod 0640 ${createfile_time} + touch ${healthcheck_file} + chmod 0640 ${healthcheck_file} + + echo ${create_time} > ${createfile_time} + echo "running" > ${healthcheck_file} + + i=0 + ret=0 + + while [ $i -lt ${timeout_time} ] + do + timeout -s 9 1 lcrc version $SYSMONITOR_OPTIONS + ret=$? + if [ $ret -eq 0 ];then + echo "success">${healthcheck_file} + return + else + if [ $ret -ne 137];then + sleep 1 + fi + i=$(($i+1)) + fi + done + echo "failed">${healthcheck_file} + return +} + +if [ ! -d ${log_dir} ]; then + mkdir -m 600 ${log_dir} +fi + +#1、首先进行基本检查,最多耗时6s,如果错误,则直接返回错误;如果成功,则返回成功 +basic_check +if [ $? -ne 0 ];then + echo "basic check failed!" + exit 1 +fi + +#2、基本检查通过后,才需要进行健康检查状态的判断 +#如果健康状态文件不存在,则进行健康检查,直接返回 +if [ ! -f ${healthcheck_file} ];then + health_check & + exit 0 +fi + +#3、如果文件存在,则代表进行过检查,根据检查结果,做相应处理 +file_status=$(cat ${healthcheck_file} 2>/dev/null) +createtime_check +file_change_time=$? +case "$file_status" in + #上次的检查还在运行且没有超时 + running) + #或者超过规定时间还是running,代表进程可能异常或者状态位被恶意篡改,都需要重新拉起进程检查脚本 + #但是此时不代表服务已经异常,所以不重启服务 + if [ ${file_change_time} -ne 0 ];then + clean_healthcheck + health_check & + fi + exit 0 + ;; + #如果上次检查已经成功了,则直接重新拉新的进程并返回成功 + success) + clean_healthcheck + health_check & + exit 0 + ;; + ##上次的检查已经超时,代表失败,返回1,需要重新由sysmonitor restart服务 + failed) + clean_healthcheck + manage_monitor_log + sleep 5 + gen_stack_log + sleep 2 + exit 1 + ;; + #其他情况,可能是失败了,需要重新由sysmonitor restart服务 + *) + clean_healthcheck + manage_monitor_log + sleep 5 + gen_stack_log + sleep 2 + exit 1 + ;; +esac +exit 0 diff --git a/src/contrib/sysmonitor/isulad-monit b/src/contrib/sysmonitor/isulad-monit new file mode 100644 index 0000000..c13b0d1 --- /dev/null +++ b/src/contrib/sysmonitor/isulad-monit @@ -0,0 +1,4 @@ +NAME=lcrd +RECOVER_COMMAND=systemctl restart lcrd +MONITOR_COMMAND=/etc/default/lcrd/isulad-check.sh +STOP_COMMAND=systemctl stop lcrd diff --git a/src/cpputils/CMakeLists.txt b/src/cpputils/CMakeLists.txt new file mode 100644 index 0000000..b557143 --- /dev/null +++ b/src/cpputils/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_cpputils_srcs) + +set(CPPUTILS_SRCS + ${local_cpputils_srcs} + PARENT_SCOPE + ) diff --git a/src/cpputils/cxxutils.cc b/src/cpputils/cxxutils.cc new file mode 100644 index 0000000..d018097 --- /dev/null +++ b/src/cpputils/cxxutils.cc @@ -0,0 +1,41 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wujing + * Create: 2019-05-17 + * Description: provide c++ common utils functions + *******************************************************************************/ +#include "cxxutils.h" +#include +#include + +namespace CXXUtils { +std::vector Split(const std::string &str, char delimiter) +{ + std::vector ret_vec; + std::string tmpstr; + std::istringstream istream(str); + while (std::getline(istream, tmpstr, delimiter)) { + ret_vec.push_back(tmpstr); + } + return ret_vec; +} + +// Join concatenates the elements of a to create a single string. The separator string +// sep is placed between elements in the resulting string. +std::string StringsJoin(const std::vector &vec, const std::string &sep) +{ + auto func = [&sep](const std::string & a, const std::string & b) -> std::string { + return a + (a.length() > 0 ? sep : "") + b; + }; + return std::accumulate(vec.begin(), vec.end(), std::string(), func); +} + +} // namespace CXXUtils diff --git a/src/cpputils/cxxutils.h b/src/cpputils/cxxutils.h new file mode 100644 index 0000000..514615a --- /dev/null +++ b/src/cpputils/cxxutils.h @@ -0,0 +1,27 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: c++ common tools. + * Author: wujing + * Create: 2019-05-17 + ******************************************************************************/ +#ifndef __CXXUTILS_H_ +#define __CXXUTILS_H_ + +#include +#include +#include +#include + +namespace CXXUtils { +std::vector Split(const std::string &str, char delimiter); +std::string StringsJoin(const std::vector &vec, const std::string &sep); +}; +#endif /* __CXXUTILS_H_ */ diff --git a/src/cpputils/stoppable_thread.cc b/src/cpputils/stoppable_thread.cc new file mode 100644 index 0000000..d1df6b2 --- /dev/null +++ b/src/cpputils/stoppable_thread.cc @@ -0,0 +1,39 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wujing + * Create: 2019-4-19 + * Description: provide stoppable thread functions + *********************************************************************************/ + +#include "stoppable_thread.h" + +StoppableThread &StoppableThread::operator=(StoppableThread &&obj) +{ + m_exit_signal = std::move(obj.m_exit_signal); + m_future_obj = std::move(obj.m_future_obj); + return *this; +} + + +bool StoppableThread::stopRequested() +{ + if (m_future_obj.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout) { + return false; + } + return true; +} + +void StoppableThread::stop() +{ + m_exit_signal.set_value(); +} + + diff --git a/src/cpputils/stoppable_thread.h b/src/cpputils/stoppable_thread.h new file mode 100644 index 0000000..4b8a5e2 --- /dev/null +++ b/src/cpputils/stoppable_thread.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wujing + * Create: 2019-4-19 + * Description: provide stoppable thread definition + *********************************************************************************/ +#ifndef __STOPPABLE_THREAD_H_ +#define __STOPPABLE_THREAD_H_ + +#include +#include +#include +#include +#include +#include + +class StoppableThread { +public: + StoppableThread() : m_future_obj(m_exit_signal.get_future()) {} + + explicit StoppableThread(StoppableThread &&obj) : m_exit_signal(std::move(obj.m_exit_signal)), + m_future_obj(std::move(obj.m_future_obj)) {} + + StoppableThread &operator=(StoppableThread &&obj); + + virtual ~StoppableThread() = default; + + virtual void run() = 0; + + void operator()() + { + run(); + } + + bool stopRequested(); + + void stop(); + +private: + std::promise m_exit_signal; + std::future m_future_obj; +}; + +#endif /* __STOPPABLE_THREAD_H_ */ diff --git a/src/cpputils/url.cc b/src/cpputils/url.cc new file mode 100644 index 0000000..5e755ed --- /dev/null +++ b/src/cpputils/url.cc @@ -0,0 +1,929 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide url functions + *********************************************************************************/ +#include "url.h" +#include +#include "cxxutils.h" +#include "log.h" +namespace url { +bool IsHex(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +bool GetHexDigit(char c, char &d) +{ + if (!IsHex(c)) { + return false; + } + + if (c >= '0' && c <= '9') { + d = c - '0'; + } else if (c >= 'a' && c <= 'f') { + d = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + d = c - 'A' + 10; + } + return true; +} + +int SpecificCharacterCheck(char c, const EncodeMode &mode, bool &result) +{ + int ret = -1; + + if (std::string("-._~").find(c) != std::string::npos) { + result = false; + } else if (std::string("&,;?@$+=:/").find(c) != std::string::npos) { + switch (mode) { + case EncodeMode::ENCODE_PATH: + result = (c == '?'); + break; + case EncodeMode::ENCODE_PATH_SEGMENT: + result = (c == '/' || c == ';' || c == ',' || c == '?'); + break; + case EncodeMode::ENCODE_USER_PASSWORD: + result = (c == '@' || c == '/' || c == '?' || c == ':'); + break; + case EncodeMode::ENCODE_QUERY_COMPONENT: + result = true; + break; + case EncodeMode::ENCODE_FRAGMENT: + result = false; + break; + default: + ret = 0; + } + } else { + ret = 0; + } + + return ret; +} + +bool ShouldEscape(char c, const EncodeMode &mode) +{ + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { + return false; + } + if (mode == EncodeMode::ENCODE_HOST || mode == EncodeMode::ENCODE_ZONE) { + std::string subDelims = "!$&()'*+,;=:[]<>\""; + if (subDelims.find(c) != std::string::npos) { + return false; + } + } + + bool result = false; + int ret = SpecificCharacterCheck(c, mode, result); + if (ret != 0) { + return result; + } + + if (mode == EncodeMode::ENCODE_FRAGMENT) { + if (std::string("!()*").find(c) != std::string::npos) { + return false; + } + } + return true; +} + +std::string QueryUnescape(const std::string &s) +{ + return Unescape(s, EncodeMode::ENCODE_QUERY_COMPONENT); +} + +int UnescapeDealWithPercentSign(size_t &i, std::string &s, const EncodeMode &mode) +{ + if ((size_t)(i + 2) >= s.length() || !IsHex(s[i + 1]) || !IsHex(s[i + 2])) { + s.erase(s.begin(), s.begin() + (long)i); + if (s.length() > 3) { + s.erase(s.begin() + 3, s.end()); + } + ERROR("invalid URL escape %s", s.c_str()); // quoted + return -1; + } + char s1, s2; + if (!GetHexDigit(s[i + 1], s1) || !GetHexDigit(s[i + 2], s2)) { + return -1; + } + if (mode == EncodeMode::ENCODE_HOST && s1 < 8 && + std::string(s.begin() + (long)i, s.begin() + (long)i + 3) != "%25") { + ERROR("invalid URL escape %s", std::string(s.begin() + (long)i, s.begin() + (long)i + 3).c_str()); + return -1; + } + if (mode == EncodeMode::ENCODE_ZONE) { + char v = (char)(((unsigned char)s1 << 4) | (unsigned char)s2); + if (std::string(s.begin() + (long)i, s.begin() + (long)i + 3) != "%25" && + v != ' ' && ShouldEscape(v, EncodeMode::ENCODE_HOST)) { + ERROR("invalid URL escape %s", std::string(s.begin() + (long)i, s.begin() + (long)i + 3).c_str()); + return -1; + } + } + return 0; +} + +int CalculatePercentNum(std::string &s, const EncodeMode &mode, bool &hasPlus) +{ + int n = 0; + + for (size_t i = 0; i < s.length();) { + switch (s.at(i)) { + case '%': { + n++; + if (UnescapeDealWithPercentSign(i, s, mode)) { + return -1; + } + i += 3; + } + break; + case '+': { + hasPlus = (mode == EncodeMode::ENCODE_QUERY_COMPONENT); + i++; + } + break; + default: + if ((mode == EncodeMode::ENCODE_HOST || mode == EncodeMode::ENCODE_ZONE) && + s[i] < 0x80 && ShouldEscape(s[i], mode)) { + ERROR("invalid URL escape %s", std::string(s.begin() + (long)i, s.begin() + (long)i + 1).c_str()); + return -1; + } + i++; + } + } + return n; +} + +void DoUnescape(std::string &t, const std::string &s, const EncodeMode &mode) +{ + int j = 0; + for (size_t i = 0; i < s.length();) { + switch (s[i]) { + case '%': { + char s1, s2; + if (!GetHexDigit(s[i + 1], s1) || !GetHexDigit(s[i + 2], s2)) { + return; + } + t[j++] = (char)(((unsigned char)s1 << 4) | (unsigned char)s2); + i += 3; + } + break; + case '+': { + if (mode == EncodeMode::ENCODE_QUERY_COMPONENT) { + t[j++] = ' '; + } else { + t[j++] = '+'; + } + i++; + } + break; + default: + t[j++] = s[i++]; + break; + } + } +} + +std::string Unescape(std::string s, const EncodeMode &mode) +{ + bool hasPlus = false; + int n = CalculatePercentNum(s, mode, hasPlus); + if (n < 0) { + return ""; + } + + if (n == 0 && !hasPlus) { + return s; + } + + std::string t; + t.resize(s.length() - 2 * n, '0'); + DoUnescape(t, s, mode); + return t; +} + +std::string QueryEscape(const std::string &s) +{ + return Escape(s, EncodeMode::ENCODE_QUERY_COMPONENT); +} + +std::string Escape(const std::string &s, const EncodeMode &mode) +{ + size_t spaceCount = 0; + size_t hexCount = 0; + for (size_t i = 0; i < s.length(); i++) { + char c = s[i]; + if (ShouldEscape(c, mode)) { + if (c == ' ' && mode == EncodeMode::ENCODE_QUERY_COMPONENT) { + spaceCount++; + } else { + hexCount++; + } + } + } + + if (spaceCount == 0 && hexCount == 0) { + return s; + } + + std::string t; + t.resize(s.length() + 2 * hexCount, '0'); + int j = 0; + for (size_t i = 0; i < s.length(); ++i) { + char c = s[i]; + if (c == ' ' && mode == EncodeMode::ENCODE_QUERY_COMPONENT) { + t[j++] = '+'; + } else if (ShouldEscape(c, mode)) { + t[j] = '%'; + t[j + 1] = "0123456789ABCDEF"[(unsigned char)c >> 4]; + t[j + 2] = "0123456789ABCDEF"[c & 15]; + j += 3; + } else { + t[j++] = s[i]; + } + } + return t; +} + +UserInfo *User(const std::string &username) noexcept +{ + return new UserInfo { username, "", false }; +} + +UserInfo *UserPassword(const std::string &username, const std::string &password) noexcept +{ + return new UserInfo { username, password, true }; +} + +int Getscheme(const std::string &rawurl, std::string &scheme, std::string &path) +{ + for (size_t i = 0; i < rawurl.length(); ++i) { + char c = rawurl[i]; + if (isalpha(c)) { + continue; + } else if (isdigit(c) || c == '+' || c == '-' || c == '.') { + if (i == 0) { + scheme = ""; + path = rawurl; + return 0; + } + } else if (c == ':') { + if (i == 0) { + scheme = ""; + path = ""; + ERROR("missing protocol scheme"); + return -1; + } + scheme = std::string(rawurl.begin(), rawurl.begin() + (long)i); + path = std::string(rawurl.begin() + (long)i + 1, rawurl.end()); + return 0; + } else { + scheme = ""; + path = rawurl; + return 0; + } + } + scheme = ""; + path = rawurl; + return 0; +} + +void Split(const std::string &s, const std::string &c, bool cutc, std::string &t, std::string &u) +{ + size_t i = s.find(c); + if (i == std::string::npos) { + t = s; + u = ""; + return; + } + if (cutc) { + t = s.substr(0, i); + u = s.substr(i + c.length(), s.size()); + return; + } + t = s.substr(0, i); + u = s.substr(i, s.size()); +} + +URLDatum *Parse(const std::string &rawurl) +{ + std::string u, frag; + Split(rawurl, "#", true, u, frag); + auto url = Parse(u, false); + if (url == nullptr) { + return nullptr; + } + if (frag.empty()) { + return url; + } + url->SetFragment(Unescape(frag, EncodeMode::ENCODE_FRAGMENT)); + if (url->GetFragment().empty()) { + return nullptr; + } + return url; +} + +int SplitOffPossibleLeading(std::string &scheme, const std::string &rawurl, URLDatum *url, std::string &rest) +{ + if (Getscheme(rawurl, scheme, rest)) { + return -1; + } + std::transform(scheme.begin(), scheme.end(), scheme.begin(), ::tolower); + if (rest.at(rest.length() - 1) == '?' && + std::count(rest.begin(), rest.end(), '?') == 1) { + url->SetForceQuery(true); + rest = rest.substr(0, rest.length() - 1); + } else { + std::string rawQuery = url->GetRawQuery(); + Split(rest, "?", true, rest, rawQuery); + url->SetRawQuery(rawQuery); + } + return 0; +} + +URLDatum *HandleNonBackslashPrefix(URLDatum *url, const std::string &scheme, + const std::string &rest, bool viaRequest, bool &should_ret) +{ + if (rest.at(0) == '/') { + return nullptr; + } + if (!scheme.empty()) { + should_ret = true; + url->SetOpaque(rest); + return url; + } + if (viaRequest) { + should_ret = true; + ERROR("invalid URI for request"); + return nullptr; + } + size_t colon = rest.find(":"); + size_t slash = rest.find("/"); + if (colon != std::string::npos && (slash == std::string::npos || colon < slash)) { + should_ret = true; + ERROR("first path segment in URL cannot contain colon"); + return nullptr; + } + return nullptr; +} + +int SetURLDatumInfo(URLDatum *url, const std::string &scheme, bool viaRequest, std::string &rest) +{ + if ((!scheme.empty() || (!viaRequest && rest.substr(0, 3) == "///")) && rest.substr(0, 2) == "//") { + std::string authority; + Split(rest.substr(2, rest.size()), "/", false, authority, rest); + std::string host = url->GetHost(); + UserInfo *user = url->GetUser(); + if (ParseAuthority(authority, &user, host)) { + return -1; + } + url->SetHost(host); + url->SetUser(user); + } + if (url->SetPath(rest)) { + return -1; + } + url->SetScheme(scheme); + return 0; +} + +URLDatum *Parse(const std::string &rawurl, bool viaRequest) +{ + if (rawurl.empty() && viaRequest) { + ERROR("empty url!"); + return nullptr; + } + URLDatum *url = new (std::nothrow) URLDatum; + if (url == nullptr) { + ERROR("Out of memory"); + return nullptr; + } + if (rawurl == "*") { + url->SetPathWithoutEscape("*"); + return url; + } + std::string scheme = url->GetScheme(); + std::string rest; + if (SplitOffPossibleLeading(scheme, rawurl, url, rest)) { + return nullptr; + } + bool should_ret = false; + auto tmpret = HandleNonBackslashPrefix(url, scheme, rest, viaRequest, should_ret); + if (should_ret) { + return tmpret; + } + if (SetURLDatumInfo(url, scheme, viaRequest, rest)) { + return nullptr; + } + return url; +} + +int ParseAuthority(const std::string &authority, UserInfo **user, std::string &host) +{ + size_t i = authority.find("@"); + if (i == std::string::npos) { + if (ParseHost(authority, host)) { + *user = nullptr; + host = ""; + return -1; + } + } else { + if (ParseHost(authority.substr(i + 1, authority.size()), host)) { + *user = nullptr; + host = ""; + return -1; + } + } + if (i == std::string::npos) { + *user = nullptr; + return 0; + } + + std::string userinfo = authority.substr(0, i); + if (!ValidUserinfo(userinfo)) { + *user = nullptr; + host = ""; + ERROR("net/url: invalid userinfo"); + return -1; + } + if (userinfo.find(":") == std::string::npos) { + userinfo = Unescape(userinfo, EncodeMode::ENCODE_USER_PASSWORD); + if (userinfo.empty()) { + *user = nullptr; + host = ""; + return -1; + } + *user = User(userinfo); + } else { + std::string servername, serverword; + Split(userinfo, ":", true, servername, serverword); + servername = Unescape(servername, EncodeMode::ENCODE_USER_PASSWORD); + serverword = Unescape(serverword, EncodeMode::ENCODE_USER_PASSWORD); + if (servername.empty() || serverword.empty()) { + *user = nullptr; + host = ""; + return -1; + } + *user = UserPassword(servername, serverword); + } + return 0; +} + +int ParseHost(std::string host, std::string &out) +{ + if (host.at(0) == '[') { + size_t i = host.find_last_of("]"); + if (i == std::string::npos) { + ERROR("missing ']' in host"); + out = ""; + } + std::string colonPort = host.substr(i + 1, host.size()); + if (!ValidOptionalPort(colonPort)) { + out = ""; + ERROR("invalid port %s after host", colonPort.c_str()); + return -1; + } + size_t zone = host.substr(0, i).find("%25"); + if (zone != std::string::npos) { + std::string host1 = Unescape(host.substr(0, zone), EncodeMode::ENCODE_HOST); + if (host1.empty()) { + out = ""; + return -1; + } + std::string host2 = Unescape(host.substr(zone, i), EncodeMode::ENCODE_ZONE); + if (host2.empty()) { + out = ""; + return -1; + } + std::string host3 = Unescape(host.substr(i, host.size()), EncodeMode::ENCODE_HOST); + if (host3.empty()) { + out = ""; + return -1; + } + out = host1 + host2 + host3; + return 0; + } + } + host = Unescape(host, EncodeMode::ENCODE_HOST); + if (host.empty()) { + out = ""; + return -1; + } + out = host; + return 0; +} + +bool ValidEncodedPath(const std::string &s) +{ + std::string subDelims = R"(!$&'()*+,;=:@[]%)"; + for (size_t i = 0; i < s.length(); ++i) { + if (subDelims.find(s[i]) != std::string::npos) { + continue; + } + if (ShouldEscape(s[i], EncodeMode::ENCODE_PATH)) { + return false; + } + } + return true; +} + +bool ValidOptionalPort(const std::string &port) +{ + if (port.empty()) { + return true; + } + if (port.at(0) != ':') { + return false; + } + for (auto it = port.begin() + 1; it != port.end(); ++it) { + if (*it < '0' || *it > '9') { + return false; + } + } + return true; +} + +auto ParseQuery(const std::string &query) -> std::map> +{ + std::map> m; + ParseQuery(m, query); + return m; +} + +int ParseQuery(std::map> &m, std::string query) +{ + while (!query.empty()) { + std::string key = query; + size_t i = key.find("&"); + if (i == std::string::npos) { + i = key.find(";"); + if (i == std::string::npos) { + query = ""; + } + } + if (key.empty()) { + continue; + } + std::string value; + i = key.find("="); + value = key.substr(i + 1, key.size()); + key = key.substr(0, i); + key = QueryUnescape(key); + if (key.empty()) { + continue; + } + m[key].push_back(value); + } + return 0; +} + +std::string GetFullPreResolvePath(const std::string &base, const std::string &ref) +{ + if (ref.empty()) { + return base; + } else if (ref[0] != '/') { + size_t i = base.find_last_of("/"); + return base.substr(0, i + 1) + ref; + } + + return ref; +} + +void SplitFullPreResolvePath(const std::string &full, std::vector &dst) +{ + std::vector src = CXXUtils::Split(full, '/'); + for (auto elem : src) { + if (elem == ".") { + continue; + } else if (elem == "..") { + if (dst.size() > 0) { + dst.erase(dst.begin() + (long)(dst.size()), dst.end()); + } + } else { + dst.push_back(elem); + } + } + std::string last = src.at(src.size() - 1); + if (last == "." || last == "..") { + dst.push_back(""); + } +} + +std::string ResolvePath(const std::string &base, const std::string &ref) +{ + std::string full = GetFullPreResolvePath(base, ref); + if (full.empty()) { + return ""; + } + + std::vector dst; + SplitFullPreResolvePath(full, dst); + + std::string ret; + for (auto it = dst.begin(); it != dst.end(); ++it) { + ret += (*it + ((it + 1 != dst.end()) ? "/" : "")); + } + if (ret.at(0) == '/') { + ret.erase(ret.begin()); + } + return "/" + ret; +} + +std::string StripPort(const std::string &hostport) +{ + size_t colon = hostport.find(":"); + if (colon == std::string::npos) { + return hostport; + } + size_t found = hostport.find("]"); + if (found != std::string::npos) { + std::string ret = hostport.substr(0, found); + if (ret.at(0) == '[') { + ret.erase(ret.begin()); + } + return ret; + } + return hostport.substr(0, colon); +} + +std::string PortOnly(const std::string &hostport) +{ + size_t colon = hostport.find(":"); + if (colon == std::string::npos) { + return ""; + } + size_t found = hostport.find("]:"); + if (found != std::string::npos) { + return hostport.substr(found + 2, hostport.size()); + } + if (hostport.find("]") != std::string::npos) { + return ""; + } + return hostport.substr(colon + 1, hostport.size()); +} + +bool ValidUserinfo(const std::string &s) +{ + std::string subDelims = R"(-._:~!$&'()*+,;=%@)"; + for (const auto &r : s) { + if (('A' <= r && r <= 'Z') || ('a' <= r && r <= 'z') || + ('0' <= r && r <= '9') || (subDelims.find(r) != std::string::npos)) { + continue; + } + return false; + } + return true; +} + +std::string Values::Get(const std::string &key) +{ + if (v.size() == 0) { + return ""; + } + std::vector vs = v[key]; + if (vs.size() == 0) { + return ""; + } + return vs[0]; +} + +void Values::Set(const std::string &key, const std::string &value) +{ + v[key] = std::vector(); + v[key].push_back(value); +} + +void Values::Add(const std::string &key, const std::string &value) +{ + v[key].push_back(value); +} + +void Values::Del(const std::string &key) +{ + auto it = v.find(key); + if (it != v.end()) { + v.erase(it); + } +} + +std::string Values::Encode() +{ + if (v.size() == 0) { + return ""; + } + std::string buf; + std::vector keys; + keys.reserve(v.size()); + for (auto it = v.cbegin(); it != v.cend(); ++it) { + keys.push_back(it->first); + } + std::sort(keys.begin(), keys.end()); + for (auto k : keys) { + std::vector vs = v[k]; + std::string keyEscaped = QueryEscape(k); + for (auto elem : vs) { + if (buf.length() > 0) { + buf.append("&"); + } + buf.append(keyEscaped); + buf.append("="); + buf.append(QueryEscape(elem)); + } + } + return buf; +} + +std::string UserInfo::String() const +{ + std::string s; + if (!m_username.empty()) { + s = Escape(m_username, EncodeMode::ENCODE_USER_PASSWORD); + if (m_passwordSet) { + s += ":" + Escape(m_password, EncodeMode::ENCODE_USER_PASSWORD); + } + } + return s; +} +std::string UserInfo::Username() const +{ + return m_username; +} +std::string UserInfo::Password(bool &set) const +{ + set = m_passwordSet; + return m_password; +} + +URLDatum::~URLDatum() +{ + if (m_user != nullptr) { + delete m_user; + } + m_user = nullptr; +} +int URLDatum::SetPath(const std::string &p) +{ + std::string path = Unescape(p, EncodeMode::ENCODE_PATH); + if (path.empty()) { + return -1; + } + m_path = path; + std::string escp = Escape(path, EncodeMode::ENCODE_PATH); + m_rawPath = (p == escp ? "" : p); + return 0; +} + +std::string URLDatum::EscapedPath() +{ + if (!m_rawPath.empty() && ValidEncodedPath(m_rawPath)) { + std::string p = Unescape(m_rawPath, EncodeMode::ENCODE_PATH); + if (!p.empty() && p == m_path) { + return m_rawPath; + } + } + if (m_path == "*") { + return "*"; + } + return Escape(m_path, EncodeMode::ENCODE_PATH); +} + +void URLDatum::StringOpaqueEmptyRules(std::string &buf) +{ + if (!m_scheme.empty() || !m_host.empty() || m_user != nullptr) { + if (!m_host.empty() || !m_path.empty() || m_user != nullptr) { + buf.append("//"); + } + if (m_user != nullptr) { + buf.append(m_user->String()); + buf.append("@"); + } + if (!m_host.empty()) { + buf.append(Escape(m_host, EncodeMode::ENCODE_HOST)); + } + } + std::string path = EscapedPath(); + if (!m_path.empty() && m_path.at(0) != '/' && !m_host.empty()) { + buf.append("/"); + } + if (buf.length() == 0) { + auto i = m_path.find(":"); + if (i != std::string::npos && + path.substr(0, i).find("/") == std::string::npos) { + buf.append("./"); + } + } + buf.append(path); +} + +std::string URLDatum::String() +{ + std::string buf; + if (!m_scheme.empty()) { + buf.append(m_scheme); + buf.append(":"); + } + if (!m_opaque.empty()) { + buf.append(m_opaque); + } else { + StringOpaqueEmptyRules(buf); + } + if (m_forceQuery || !m_rawQuery.empty()) { + buf.append("?"); + buf.append(m_rawQuery); + } + if (!m_fragment.empty()) { + buf.append("#"); + buf.append(Escape(m_fragment, EncodeMode::ENCODE_FRAGMENT)); + } + return buf; +} + +bool URLDatum::IsAbs() const +{ + return (m_scheme != ""); +} + +std::unique_ptr URLDatum::UrlParse(const std::string &ref) +{ + auto refurl = Parse(ref); + if (refurl == nullptr) { + return nullptr; + } + return ResolveReference(refurl); +} + +std::unique_ptr URLDatum::ResolveReference(URLDatum *ref) +{ + std::unique_ptr url(new URLDatum(*ref)); + + if (url->m_scheme.empty()) { + url->m_scheme = m_scheme; + } + if (!ref->m_scheme.empty() || !ref->m_host.empty() || ref->m_user != nullptr) { + url->SetPath(ResolvePath(ref->EscapedPath(), "")); + return url; + } + if (!ref->m_opaque.empty()) { + url->m_user = nullptr; + url->m_host = ""; + url->m_path = ""; + return url; + } + if (ref->m_path.empty() && ref->m_rawQuery.empty()) { + url->m_rawQuery = m_rawQuery; + if (ref->m_fragment.empty()) { + url->m_fragment = m_fragment; + } + } + url->m_host = m_host; + url->m_user = m_user; + url->SetPath(ResolvePath(EscapedPath(), ref->EscapedPath())); + return url; +} + + +auto URLDatum::Query() ->std::map> +{ + return ParseQuery(m_rawQuery); +} + +std::string URLDatum::RequestURI() +{ + std::string result = m_opaque; + if (result.empty()) { + result = EscapedPath(); + if (result.empty()) { + result = "/"; + } + } else { + if (result.length() >= 2 && result.substr(0, 2) == "//") { + result = m_scheme + ":" + result; + } + } + if (m_forceQuery || !m_rawQuery.empty()) { + result += "?" + m_rawQuery; + } + return result; +} + +std::string URLDatum::Hostname() const +{ + return StripPort(m_host); +} + +std::string URLDatum::Port() const +{ + return PortOnly(m_host); +} +} // namespace url + + diff --git a/src/cpputils/url.h b/src/cpputils/url.h new file mode 100644 index 0000000..75068c5 --- /dev/null +++ b/src/cpputils/url.h @@ -0,0 +1,186 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: Package url parses URLs and implements query escaping. + * Author: wujing + * Create: 2019-01-02 + ******************************************************************************/ + +#ifndef __URL_H_ +#define __URL_H_ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace url { +enum class EncodeMode : int { + ENCODE_PATH = 1, + ENCODE_PATH_SEGMENT, + ENCODE_HOST, + ENCODE_ZONE, + ENCODE_USER_PASSWORD, + ENCODE_QUERY_COMPONENT, + ENCODE_FRAGMENT +}; + +class Values { +public: + std::string Get(const std::string &key); + void Set(const std::string &key, const std::string &value); + void Add(const std::string &key, const std::string &value); + void Del(const std::string &key); + std::string Encode(); +private: + std::map> v; +}; + +class UserInfo { +public: + UserInfo(const std::string &u, const std::string &p, bool b) : m_username(u), m_password(p), + m_passwordSet(b) {} + ~UserInfo() = default; + std::string String() const ; + std::string Username() const; + std::string Password(bool &set) const; + +private: + std::string m_username; + std::string m_password; + bool m_passwordSet; +}; + +class URLDatum { +public: + URLDatum() = default; + ~URLDatum(); + std::string EscapedPath(); + std::string String(); + bool IsAbs() const ; + std::unique_ptr UrlParse(const std::string &ref); + std::unique_ptr ResolveReference(URLDatum *ref); + auto Query()->std::map>; + std::string RequestURI(); + std::string Hostname() const; + std::string Port() const; + int SetPath(const std::string &p); + void SetScheme(const std::string &value) + { + m_scheme = value; + } + std::string GetScheme() const + { + return m_scheme; + } + void SetOpaque(const std::string &value) + { + m_opaque = value; + } + std::string GetOpaque() const + { + return m_opaque; + } + void SetUser(UserInfo *value) + { + m_user = value; + } + UserInfo *GetUser() const + { + return m_user; + } + void SetHost(const std::string &value) + { + m_host = value; + } + std::string GetHost() const + { + return m_host; + } + void SetPathWithoutEscape(const std::string &value) + { + m_path = value; + } + std::string GetPath() const + { + return m_path; + } + void SetForceQuery(bool value) + { + m_forceQuery = value; + } + bool GetForceQuery() const + { + return m_forceQuery; + } + void SetRawQuery(const std::string &value) + { + m_rawQuery = value; + } + std::string GetRawQuery() const + { + return m_rawQuery; + } + void SetFragment(const std::string &value) + { + m_fragment = value; + } + std::string GetFragment() const + { + return m_fragment; + } + +private: + void StringOpaqueEmptyRules(std::string &buf); + +private: + std::string m_scheme; + std::string m_opaque; + UserInfo *m_user{nullptr}; + std::string m_host; + std::string m_path; + std::string m_rawPath; + bool m_forceQuery{false}; + std::string m_rawQuery; + std::string m_fragment; +}; + +bool IsHex(char c); +bool GetHexDigit(char c, char &d); +bool ShouldEscape(char c, const EncodeMode &mode); +std::string QueryUnescape(const std::string &s); +std::string Unescape(std::string s, const EncodeMode &mode); +std::string QueryEscape(const std::string &s); +std::string Escape(const std::string &s, const EncodeMode &mode); +UserInfo *UserPassword(const std::string &username, const std::string &password) noexcept; +UserInfo *User(const std::string &username) noexcept; +int Getscheme(const std::string &rawurl, std::string &scheme, std::string &path); +void Split(const std::string &s, const std::string &c, bool cutc, std::string &t, std::string &u); +URLDatum *Parse(const std::string &rawurl); +URLDatum *Parse(const std::string &rawurl, bool viaRequest); +int ParseAuthority(const std::string &authority, UserInfo **user, std::string &host); +int ParseHost(std::string host, std::string &out); +bool ValidEncodedPath(const std::string &s); +bool ValidOptionalPort(const std::string &port); +auto ParseQuery(const std::string &query) +->std::map>; +int ParseQuery(std::map> &m, std::string query); +std::string ResolvePath(const std::string &base, const std::string &ref); +std::string StripPort(const std::string &hostport); +std::string PortOnly(const std::string &hostport); +bool ValidUserinfo(const std::string &s); +} // namespace url + +#endif + diff --git a/src/cutils/CMakeLists.txt b/src/cutils/CMakeLists.txt new file mode 100644 index 0000000..535e7e8 --- /dev/null +++ b/src/cutils/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_cutils_srcs) + +set(CUTILS_SRCS + ${local_cutils_srcs} + PARENT_SCOPE + ) diff --git a/src/cutils/util_atomic.c b/src/cutils/util_atomic.c new file mode 100644 index 0000000..4bdf763 --- /dev/null +++ b/src/cutils/util_atomic.c @@ -0,0 +1,18 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: weiwei + * Create: 2018-11-1 + * Description: provide atomic functions + ********************************************************************************/ +#include "util_atomic.h" + +pthread_mutex_t g_atomic_lock = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t g_atomic_image_lock = PTHREAD_MUTEX_INITIALIZER; diff --git a/src/cutils/util_atomic.h b/src/cutils/util_atomic.h new file mode 100644 index 0000000..19b5565 --- /dev/null +++ b/src/cutils/util_atomic.h @@ -0,0 +1,197 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide atomic function definition + ********************************************************************************/ +#ifndef __UTILS_ATOMIC_H +#define __UTILS_ATOMIC_H + +#include +#include +#include + +#include "log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern pthread_mutex_t g_atomic_lock; +extern pthread_mutex_t g_atomic_image_lock; + +/* atomic mutex lock */ +static inline void atomic_mutex_lock(pthread_mutex_t *mutex) +{ + if (pthread_mutex_lock(mutex)) { + ERROR("Failed to lock atomic mutex"); + } +} + +/* atomic mutex unlock */ +static inline void atomic_mutex_unlock(pthread_mutex_t *mutex) +{ + if (pthread_mutex_unlock(mutex)) { + ERROR("Failed to unlock atomic mutex"); + } +} + +/* atomic int get */ +static inline uint64_t atomic_int_get(const volatile uint64_t *atomic) +{ + uint64_t value; + + atomic_mutex_lock(&g_atomic_lock); + value = *atomic; + atomic_mutex_unlock(&g_atomic_lock); + + return value; +} + +/* atomic int set */ +static inline void atomic_int_set(volatile uint64_t *atomic, uint64_t value) +{ + atomic_mutex_lock(&g_atomic_lock); + *atomic = value; + atomic_mutex_unlock(&g_atomic_lock); +} + +/* atomic int set for image*/ +static inline void atomic_int_set_image(volatile uint64_t *atomic, uint64_t value) +{ + atomic_mutex_lock(&g_atomic_image_lock); + *atomic = value; + atomic_mutex_unlock(&g_atomic_image_lock); +} + + +/* atomic int inc */ +static inline uint64_t atomic_int_inc(volatile uint64_t *atomic) +{ + uint64_t value; + + atomic_mutex_lock(&g_atomic_lock); + value = ++(*atomic); + atomic_mutex_unlock(&g_atomic_lock); + + return value; +} + +/* atomic int inc for image */ +static inline uint64_t atomic_int_inc_image(volatile uint64_t *atomic) +{ + uint64_t value; + + atomic_mutex_lock(&g_atomic_image_lock); + value = ++(*atomic); + atomic_mutex_unlock(&g_atomic_image_lock); + + return value; +} + +/* atomic int dec test */ +static inline bool atomic_int_dec_test(volatile uint64_t *atomic) +{ + bool is_zero = false; + + atomic_mutex_lock(&g_atomic_lock); + is_zero = --(*atomic) == 0; + atomic_mutex_unlock(&g_atomic_lock); + + return is_zero; +} + +/* atomic int dec test for image*/ +static inline bool atomic_int_dec_test_image(volatile uint64_t *atomic) +{ + bool is_zero = false; + + atomic_mutex_lock(&g_atomic_image_lock); + is_zero = --(*atomic) == 0; + atomic_mutex_unlock(&g_atomic_image_lock); + + return is_zero; +} + + +/* atomic int compare exchange */ +static inline bool atomic_int_compare_exchange(volatile uint64_t *atomic, uint64_t oldval, uint64_t newval) +{ + bool success = false; + + atomic_mutex_lock(&g_atomic_lock); + + if ((success = (*atomic == oldval))) { + *atomic = newval; + } + + atomic_mutex_unlock(&g_atomic_lock); + + return success; +} + +/* atomic int add */ +static inline uint64_t atomic_int_add(volatile uint64_t *atomic, uint64_t val) +{ + uint64_t oldval; + + atomic_mutex_lock(&g_atomic_lock); + oldval = *atomic; + *atomic = oldval + val; + atomic_mutex_unlock(&g_atomic_lock); + + return oldval; +} + +/* atomic int and */ +static inline uint64_t atomic_int_and(volatile uint64_t *atomic, uint64_t val) +{ + uint64_t oldval; + + atomic_mutex_lock(&g_atomic_lock); + oldval = *atomic; + *atomic = oldval & val; + atomic_mutex_unlock(&g_atomic_lock); + + return oldval; +} + +/* atomic int or */ +static inline uint64_t atomic_int_or(volatile uint64_t *atomic, uint64_t val) +{ + uint64_t oldval; + + atomic_mutex_lock(&g_atomic_lock); + oldval = *atomic; + *atomic = oldval | val; + atomic_mutex_unlock(&g_atomic_lock); + + return oldval; +} + +/* atomic int xor */ +static inline uint64_t atomic_int_xor(volatile uint64_t *atomic, uint64_t val) +{ + uint64_t oldval; + + atomic_mutex_lock(&g_atomic_lock); + oldval = *atomic; + *atomic = oldval ^ val; + atomic_mutex_unlock(&g_atomic_lock); + + return oldval; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __UTILS_ATOMIC_H */ diff --git a/src/cutils/utils.c b/src/cutils/utils.c new file mode 100644 index 0000000..c95d0a0 --- /dev/null +++ b/src/cutils/utils.c @@ -0,0 +1,1399 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container utils functions + *******************************************************************************/ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "log.h" +#include "json_common.h" + +int mem_realloc(void **newptr, size_t newsize, void *oldptr, size_t oldsize) +{ + void *tmp = NULL; + int nret = 0; + + if (newptr == NULL || newsize == 0) { + goto err_out; + } + + tmp = util_common_calloc_s(newsize); + if (tmp == NULL) { + ERROR("Failed to malloc memory"); + goto err_out; + } + + if (oldptr != NULL) { + nret = memcpy_s(tmp, newsize, oldptr, (newsize < oldsize) ? newsize : oldsize); + if (nret != EOK) { + ERROR("Failed to memcpy memory"); + free(tmp); + goto err_out; + } + + if (memset_s(oldptr, oldsize, 0, oldsize) != EOK) { + ERROR("Failed to memset memory"); + free(tmp); + goto err_out; + } + + free(oldptr); + } + + *newptr = tmp; + return 0; + +err_out: + return -1; +} + +static int util_read_pipe(int pipe_fd, char **out_buf, size_t *out_buf_size, size_t *out_real_size) +{ + int ret = 0; + char *tmp = NULL; + char *buffer = *out_buf; + size_t old_size = *out_buf_size; + size_t real_size = *out_real_size; + size_t new_size = 0; + ssize_t read_size = 0; + + if (buffer == NULL) { + new_size = PIPE_BUF + 1; + buffer = util_common_calloc_s(new_size); + if (buffer == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + *out_buf_size = new_size; + *out_buf = buffer; + *out_real_size = 0; + } else { + if (old_size - real_size < PIPE_BUF + 1) { + if (old_size > (SIZE_MAX - PIPE_BUF) - 1) { + ERROR("Memory out"); + ret = -1; + goto out; + } + + new_size = old_size + PIPE_BUF + 1; + ret = mem_realloc((void *)(&tmp), new_size, (void *)buffer, old_size); + if (ret != 0) { + ERROR("Memory out"); + ret = -1; + goto out; + } + buffer = tmp; + *out_buf_size = new_size; + *out_buf = buffer; + } + } + + read_size = util_read_nointr(pipe_fd, buffer + real_size, PIPE_BUF); + if (read_size > 0) { + *out_real_size = real_size + (size_t)read_size; + ret = 0; + goto out; + } else if (read_size < 0 && errno == EAGAIN) { + ret = 0; + goto out; + } else { + ret = -1; + goto out; + } + +out: + return ret; +} + +#ifndef PR_SET_MM +#define PR_SET_MM 35 +#endif + +#ifndef PR_SET_MM_MAP +#define PR_SET_MM_MAP 14 +#endif + +static bool util_dir_skip_current(const struct dirent *pdirent) +{ + if (strcmp(pdirent->d_name, ".") == 0) { + return true; + } + + if (strcmp(pdirent->d_name, "..") == 0) { + return true; + } + return false; +} + +static bool util_is_std_fileno(int fd) +{ + return fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO; +} + +int util_check_inherited(bool closeall, int fd_to_ignore) +{ + struct dirent *pdirent = NULL; + int fd, fddir; + DIR *directory = NULL; + +restart: + directory = opendir("/proc/self/fd"); + if (directory == NULL) { + WARN("Failed to open directory: %m."); + return -1; + } + + fddir = dirfd(directory); + pdirent = readdir(directory); + for (; pdirent != NULL; pdirent = readdir(directory)) { + if (util_dir_skip_current(pdirent)) { + continue; + } + + if (util_safe_int(pdirent->d_name, &fd) < 0) { + continue; + } + + if (util_is_std_fileno(fd) || fd == fddir || fd == fd_to_ignore) { + continue; + } + + if (closeall) { + if (fd >= 0) { + close(fd); + fd = -1; + } + if (directory != NULL) { + closedir(directory); + directory = NULL; + } + goto restart; + } + } + + closedir(directory); + return 0; +} + +static int sig_num(const char *sig) +{ + int n; + + if (util_safe_int(sig, &n) < 0) { + return -1; + } + + return n; +} + +int util_sig_parse(const char *sig_name) +{ + size_t n; + const struct signame signames[] = SIGNAL_MAP_DEFAULT; + + if (sig_name == NULL) { + return -1; + } + + if (isdigit(*sig_name)) { + return sig_num(sig_name); + } else if (strncasecmp(sig_name, "sig", 3) == 0) { + sig_name += 3; + for (n = 0; n < sizeof(signames) / sizeof(signames[0]); n++) { + if (strcasecmp(signames[n].name, sig_name) == 0) { + return signames[n].num; + } + } + } else { + for (n = 0; n < sizeof(signames) / sizeof(signames[0]); n++) { + if (strcasecmp(signames[n].name, sig_name) == 0) { + return signames[n].num; + } + } + } + + return -1; +} + +bool util_check_signal_valid(int sig) +{ + size_t i; + const struct signame signames[] = SIGNAL_MAP_DEFAULT; + + for (i = 0; i < sizeof(signames) / sizeof(signames[0]); i++) { + if (signames[i].num == sig) { + return true; + } + } + + return false; +} + + +void *util_common_calloc_s(size_t size) +{ + if (size == 0 || size > SIZE_MAX) { + return NULL; + } + + return calloc((size_t)1, size); +} + +char *util_strdup_s(const char *src) +{ + char *dst = NULL; + + if (src == NULL) { + return NULL; + } + + dst = strdup(src); + if (dst == NULL) { + abort(); + } + + return dst; +} + +int wait_for_pid(pid_t pid) +{ + int st; + int nret = 0; + +rep: + nret = waitpid(pid, &st, 0); + if (nret == -1) { + if (errno == EINTR) { + goto rep; + } + return -1; + } + if (nret != pid) { + goto rep; + } + if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { + return -1; + } + return 0; +} + +int wait_for_pid_status(pid_t pid) +{ + int st; + int nret = 0; +rep: + nret = waitpid(pid, &st, 0); + if (nret == -1) { + if (errno == EINTR) { + goto rep; + } + return -1; + } + + if (nret != pid) { + goto rep; + } + return st; +} + +/* + * if errmsg contain with 'not found'/'no such' error, set exit_code 127 + * if errmsg contain with 'permission denied' error, set exit_code 126 + */ +void util_contain_errmsg(const char *errmsg, int *exit_code) +{ + if (errmsg == NULL || exit_code == NULL) { + return; + } + + if (strcasestr(errmsg, "executable file not found") || strcasestr(errmsg, "no such file or directory") || + strcasestr(errmsg, "system cannot find the file specified")) { + *exit_code = 127; + } else if (strcasestr(errmsg, "permission denied")) { + *exit_code = 126; + } else if (strcasestr(errmsg, "not a directory")) { + *exit_code = 127; + } + + return; +} + +char *util_short_digest(const char *digest) +{ +#define SHORT_DIGEST_LEN 12 + char short_digest[SHORT_DIGEST_LEN + 1] = { 0 }; + size_t start_pos = 0; + + if (digest == NULL) { + return NULL; + } + if (!util_valid_digest(digest)) { + ERROR("invalid digest %s", digest); + return NULL; + } + + if (strstr(digest, SHA256_PREFIX) == digest) { + start_pos = strlen(SHA256_PREFIX); + } + + if (memcpy_s(short_digest, sizeof(short_digest), digest + start_pos, SHORT_DIGEST_LEN) != EOK) { + ERROR("Failed to memcpy memory"); + return NULL; + } + + short_digest[SHORT_DIGEST_LEN] = 0; + + return util_strdup_s(short_digest); +} + +char *util_full_digest(const char *digest) +{ + int nret = 0; + char full_digest[PATH_MAX] = { 0 }; + + if (digest == NULL) { + ERROR("invalid NULL digest"); + return NULL; + } + + nret = sprintf_s(full_digest, sizeof(full_digest), "%s%s", SHA256_PREFIX, digest); + if (nret < 0) { + ERROR("digest too long failed"); + return NULL; + } + + return util_strdup_s(full_digest); +} + +/* util_stat2proc() makes sure it can handle arbitrary executable file basenames + * for `cmd', i.e. those with embedded whitespace or embedded ')'s. + * Such names confuse %s (see scanf(3)), so the string is split and %39c + * is used instead. (except for embedded ')' "(%[^)]c)" would work. + */ +proc_t *util_stat2proc(const char *s, size_t len) +{ + int num; + proc_t *p = NULL; + char *tmp = NULL; + + if (s == NULL) { + return NULL; + } + if (len == 0) { + return NULL; + } + + tmp = strrchr(s, ')'); /* split into "PID (cmd" and "" */ + if (tmp == NULL) { + return NULL; + } + *tmp = '\0'; /* replace trailing ')' with NUL */ + + p = util_common_calloc_s(sizeof(proc_t)); + if (p == NULL) { + return NULL; + } + + /* parse these two strings separately, skipping the leading "(". */ + /* comm[16] in kernel */ + num = sscanf_s(s, "%d (%15c", &p->pid, p->cmd, 16); + if (num != 2) { + ERROR("Call sscanf error: %s", errno ? strerror(errno) : ""); + free(p); + return NULL; + } + num = sscanf_s(tmp + 2, /* skip space after ')' too */ + "%c " + "%d %d %d %d %d " + "%lu %lu %lu %lu %lu " + "%Lu %Lu %Lu %Lu " /* utime stime cutime cstime */ + "%ld %ld %ld %ld " + "%Lu ", /* start_time */ + &p->state, 1, &p->ppid, &p->pgrp, &p->session, &p->tty, &p->tpgid, &p->flags, &p->min_flt, + &p->cmin_flt, &p->maj_flt, &p->cmaj_flt, &p->utime, &p->stime, &p->cutime, &p->cstime, &p->priority, + &p->nice, &p->timeout, &p->it_real_value, &p->start_time); + if (num != 20) { // max arg to read + ERROR("Call sscanf error: %s", errno ? strerror(errno) : ""); + free(p); + return NULL; + } + + if (p->tty == 0) { + p->tty = -1; /* the old notty val, update elsewhere bef. moving to 0 */ + } + return p; +} + +bool util_process_alive(pid_t pid, unsigned long long start_time) +{ + int sret = 0; + bool alive = true; + proc_t *pid_info = NULL; + char filename[PATH_MAX] = { 0 }; + char sbuf[1024] = { 0 }; /* bufs for stat */ + + if (pid == 0) { + return false; + } + + sret = kill(pid, 0); + if (sret < 0 && errno == ESRCH) { + return false; + } + + sret = sprintf_s(filename, sizeof(filename), "/proc/%d/stat", pid); + if (sret < 0 || (unsigned int)sret >= sizeof(filename)) { + ERROR("Failed to sprintf filename"); + goto out; + } + + if ((util_file2str(filename, sbuf, sizeof(sbuf))) == -1) { + ERROR("Failed to read pidfile %s", filename); + alive = false; + goto out; + } + + pid_info = util_stat2proc(sbuf, sizeof(sbuf)); + if (pid_info == NULL) { + ERROR("Failed to get proc stat info"); + alive = false; + goto out; + } + + if (start_time != pid_info->start_time) { + alive = false; + } +out: + free(pid_info); + return alive; +} + +static void set_stderr_buf(char **stderr_buf, const char *format, ...) +{ + int ret = 0; + char errbuf[BUFSIZ + 1] = { 0 }; + char *jerr = NULL; + + UTIL_FREE_AND_SET_NULL(*stderr_buf); + + va_list argp; + va_start(argp, format); + + ret = vsprintf_s(errbuf, BUFSIZ, format, argp); + va_end(argp); + if (ret < 0) { + return; + } + + *stderr_buf = json_marshal_string(errbuf, strlen(errbuf), NULL, &jerr); + if (*stderr_buf == NULL) { + *stderr_buf = util_strdup_s(errbuf); + } + free(jerr); +} + +static int open_devnull(void) +{ + int fd = util_open("/dev/null", O_RDWR, 0); + if (fd < 0) { + ERROR("Can't open /dev/null"); + } + + return fd; +} + +static int null_stdin(void) +{ + int ret = -1; + int fd = -1; + + fd = open_devnull(); + if (fd >= 0) { + ret = dup2(fd, STDIN_FILENO); + close(fd); + if (ret < 0) { + return -1; + } + } + + return ret; +} + +static inline bool deal_with_result_of_waitpid_nomsg(char **stderr_msg, size_t errmsg_len) +{ + if (*stderr_msg == NULL || strlen(*stderr_msg) == 0 || errmsg_len == 0) { + return true; + } + return false; +} + +static bool deal_with_result_of_waitpid(int status, char **stderr_msg, size_t errmsg_len) +{ + int signal; + bool nomsg = false; + + if (stderr_msg == NULL) { + ERROR("Invalid arguments"); + return false; + } + + nomsg = deal_with_result_of_waitpid_nomsg(stderr_msg, errmsg_len); + + if (status < 0) { + if (nomsg) { + set_stderr_buf(stderr_msg, "Failed to wait exec cmd process"); + } + return false; + } + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 0) { + return true; + } + if (nomsg) { + set_stderr_buf(stderr_msg, "Command exit with status: %d", WEXITSTATUS(status)); + } + } else if (WIFSIGNALED((unsigned int)status)) { + signal = WTERMSIG(status); + if (nomsg) { + set_stderr_buf(stderr_msg, "Command exit with signal: %d", signal); + } + } else if (WIFSTOPPED(status)) { + signal = WSTOPSIG(status); + if (nomsg) { + set_stderr_buf(stderr_msg, "Command stop with signal: %d", signal); + } + } else { + if (nomsg) { + set_stderr_buf(stderr_msg, "Command exit with unknown status: %d", status); + } + } + + return false; +} + +static void marshal_stderr_msg(char **buffer, size_t *real_size) +{ + char *jerr = NULL; + char *tmp_err = NULL; + char *stderr_buffer = *buffer; + size_t stderr_real_size = *real_size; + + if (stderr_buffer != NULL && strlen(stderr_buffer) > 0 && stderr_real_size > 0) { + tmp_err = json_marshal_string(stderr_buffer, strlen(stderr_buffer), NULL, &jerr); + if (tmp_err != NULL) { + free(stderr_buffer); + *buffer = tmp_err; + *real_size = strlen(tmp_err); + } + free(jerr); + } +} + +bool util_exec_top_cmd(exec_top_func_t cb_func, char **args, const char *pid_args, size_t args_len, char **stdout_msg, + char **stderr_msg) +{ + bool ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + size_t stdout_buf_size = 0; + size_t stderr_buf_size = 0; + size_t stdout_real_size = 0; + size_t stderr_real_size = 0; + int stdout_close_flag = 0; + int stderr_close_flag = 0; + int err_fd[2] = { -1, -1 }; + int out_fd[2] = { -1, -1 }; + pid_t pid = 0; + int status = 0; + + if (pipe2(err_fd, O_CLOEXEC | O_NONBLOCK) != 0) { + ERROR("Failed to create pipe"); + set_stderr_buf(&stderr_buffer, "Failed to create pipe"); + goto out; + } + if (pipe2(out_fd, O_CLOEXEC | O_NONBLOCK) != 0) { + ERROR("Failed to create pipe"); + set_stderr_buf(&stderr_buffer, "Failed to create pipe"); + close(err_fd[0]); + close(err_fd[1]); + goto out; + } + + pid = fork(); + if (pid == (pid_t) - 1) { + ERROR("Failed to fork()"); + set_stderr_buf(&stderr_buffer, "Failed to fork()"); + close(err_fd[0]); + close(err_fd[1]); + close(out_fd[0]); + close(out_fd[1]); + goto out; + } + + if (pid == (pid_t)0) { + int nret = 0; + nret = null_stdin(); + if (nret < 0) { + WARN("Failed to set stdin to /dev/null"); + } + + // child process, dup2 out_fd[1] to stdout + close(out_fd[0]); + dup2(out_fd[1], STDOUT_FILENO); + + // child process, dup2 err_fd[1] to stderr + close(err_fd[0]); + dup2(err_fd[1], STDERR_FILENO); + + /* become session leader */ + nret = setsid(); + if (nret < 0) { + COMMAND_ERROR("Failed to set process %d as group leader", getpid()); + } + + cb_func(args, pid_args, args_len); + } + + close(err_fd[1]); + close(out_fd[1]); + + for (;;) { + if (stdout_close_flag == 0) { + stdout_close_flag = util_read_pipe(out_fd[0], &stdout_buffer, &stdout_buf_size, &stdout_real_size); + } + if (stderr_close_flag == 0) { + stderr_close_flag = util_read_pipe(err_fd[0], &stderr_buffer, &stderr_buf_size, &stderr_real_size); + } + if (stdout_close_flag != 0 && stderr_close_flag != 0) { + break; + } + usleep_nointerupt(1000); + } + + marshal_stderr_msg(&stderr_buffer, &stderr_real_size); + + status = wait_for_pid_status(pid); + + ret = deal_with_result_of_waitpid(status, &stderr_buffer, stderr_real_size); + + close(err_fd[0]); + close(out_fd[0]); +out: + *stdout_msg = stdout_buffer; + *stderr_msg = stderr_buffer; + return ret; +} + +static void close_pipes_fd(int *pipes, size_t pipe_size) +{ + size_t i = 0; + + for (i = 0; i < pipe_size; i++) { + if (pipes[i] >= 0) { + close(pipes[i]); + pipes[i] = -1; + } + } +} + +bool util_exec_cmd(exec_func_t cb_func, void *args, const char *stdin_msg, char **stdout_msg, char **stderr_msg) +{ + bool ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + size_t stdout_buf_size = 0; + size_t stderr_buf_size = 0; + size_t stdout_real_size = 0; + size_t stderr_real_size = 0; + int stdout_close_flag = 0; + int stderr_close_flag = 0; + int err_fd[2] = { -1, -1 }; + int out_fd[2] = { -1, -1 }; + int in_fd[2] = {-1, -1}; + pid_t pid = 0; + int status = 0; + + if (pipe2(in_fd, O_CLOEXEC | O_NONBLOCK) != 0) { + ERROR("Failed to create stdin pipe"); + set_stderr_buf(&stderr_buffer, "Failed to create stdin pipe"); + goto out; + } + + if (pipe2(err_fd, O_CLOEXEC | O_NONBLOCK) != 0) { + ERROR("Failed to create pipe"); + set_stderr_buf(&stderr_buffer, "Failed to create pipe"); + close_pipes_fd(in_fd, 2); + goto out; + } + if (pipe2(out_fd, O_CLOEXEC | O_NONBLOCK) != 0) { + ERROR("Failed to create pipe"); + set_stderr_buf(&stderr_buffer, "Failed to create pipe"); + close_pipes_fd(in_fd, 2); + close_pipes_fd(err_fd, 2); + goto out; + } + + pid = fork(); + if (pid == (pid_t) - 1) { + ERROR("Failed to fork()"); + set_stderr_buf(&stderr_buffer, "Failed to fork()"); + close_pipes_fd(in_fd, 2); + close_pipes_fd(err_fd, 2); + close_pipes_fd(out_fd, 2); + goto out; + } + + if (pid == (pid_t)0) { + int nret = 0; + + close(in_fd[1]); + if (in_fd[0] != STDIN_FILENO) { + dup2(in_fd[0], STDIN_FILENO); + } else { + if (fcntl(in_fd[0], F_SETFD, 0) != 0) { + fprintf(stderr, "Failed to remove FD_CLOEXEC from fd."); + exit(127); + } + } + close(in_fd[0]); + + // child process, dup2 out_fd[1] to stdout + close(out_fd[0]); + dup2(out_fd[1], STDOUT_FILENO); + + // child process, dup2 err_fd[1] to stderr + close(err_fd[0]); + dup2(err_fd[1], STDERR_FILENO); + + /* become session leader */ + nret = setsid(); + if (nret < 0) { + COMMAND_ERROR("Failed to set process %d as group leader", getpid()); + } + + cb_func(args); + } + + /* parent */ + close(err_fd[1]); + err_fd[1] = -1; + close(out_fd[1]); + out_fd[1] = -1; + + close(in_fd[0]); + in_fd[0] = -1; + if (stdin_msg != NULL) { + size_t len = strlen(stdin_msg); + if (util_write_nointr(in_fd[1], stdin_msg, len) != len) { + WARN("Write instr: %s failed", stdin_msg); + } + } + close(in_fd[1]); + in_fd[1] = -1; + + for (;;) { + if (stdout_close_flag == 0) { + stdout_close_flag = util_read_pipe(out_fd[0], &stdout_buffer, &stdout_buf_size, &stdout_real_size); + } + if (stderr_close_flag == 0) { + stderr_close_flag = util_read_pipe(err_fd[0], &stderr_buffer, &stderr_buf_size, &stderr_real_size); + } + if (stdout_close_flag != 0 && stderr_close_flag != 0) { + break; + } + usleep_nointerupt(1000); + } + + marshal_stderr_msg(&stderr_buffer, &stderr_real_size); + + status = wait_for_pid_status(pid); + + ret = deal_with_result_of_waitpid(status, &stderr_buffer, stderr_real_size); + + close(err_fd[0]); + close(out_fd[0]); +out: + *stdout_msg = stdout_buffer; + *stderr_msg = stderr_buffer; + return ret; +} + +char **get_backtrace(void) +{ +#define BACKTRACE_SIZE 16 + int addr_cnts; + void *buffer[BACKTRACE_SIZE]; + char **syms = NULL; + + addr_cnts = backtrace(buffer, BACKTRACE_SIZE); + if (addr_cnts <= 0) { + return NULL; + } + + syms = backtrace_symbols(buffer, addr_cnts); + if (syms == NULL) { + return NULL; + } + + return syms; +} + +static long long get_time_unit(int unit) +{ + long long u[255] = { 0 }; + + u['M'] = Time_Milli; + u['s'] = Time_Second; + u['m'] = Time_Minute; + u['h'] = Time_Hour; + + return u[unit]; +} + +static int get_time_ns(long long *pns, long long unit) +{ + if (unit == 0) { + return -1; + } + + if (INT64_MAX / *pns >= unit) { + *pns *= unit; + return 0; + } + + return -1; +} + +int util_parse_time_str_to_nanoseconds(const char *value, int64_t *nanoseconds) +{ + int ret = 0; + long long tmp = 0; + char unit = 0; + size_t len = 0; + char *num_str = NULL; + + if (value == NULL || nanoseconds == NULL) { + return -1; + } + + if (util_reg_match("^([0-9]+)+(ms|s|m|h)$", value) != 0) { + return -1; + } + num_str = util_strdup_s(value); + len = strlen(value); + + if (strstr(value, "ms") == NULL) { + unit = *(value + len - 1); + *(num_str + len - 1) = '\0'; + } else { + unit = 'M'; + *(num_str + len - 2) = '\0'; + } + ret = util_safe_llong(num_str, &tmp); + if (ret < 0) { + ERROR("Illegal unsigned integer: %s", num_str); + ret = -1; + goto out; + } + if (tmp == 0) { + goto out; + } + + ret = get_time_ns(&tmp, get_time_unit(unit)); + if (ret != 0) { + ERROR("failed get nano seconds for %s", num_str); + } + *nanoseconds = (int64_t)tmp; + +out: + free(num_str); + return ret; +} + +/* isulad: get starttime of process pid */ +proc_t *util_get_process_proc_info(pid_t pid) +{ + int sret = 0; + proc_t *pid_info = NULL; + char filename[PATH_MAX] = { 0 }; + char sbuf[1024] = { 0 }; /* bufs for stat */ + + sret = sprintf_s(filename, sizeof(filename), "/proc/%d/stat", pid); + if (sret < 0 || (unsigned int)sret >= sizeof(filename)) { + ERROR("Failed to sprintf filename"); + goto out; + } + + if ((util_file2str(filename, sbuf, sizeof(sbuf))) == -1) { + ERROR("Failed to read pidfile %s", filename); + goto out; + } + + pid_info = util_stat2proc(sbuf, sizeof(sbuf)); + if (pid_info == NULL) { + ERROR("Failed to get proc stat info"); + goto out; + } + +out: + return pid_info; +} + +int util_env_set_val(char ***penv, const size_t *penv_len, const char *key, size_t key_len, const char *newkv) +{ + size_t i = 0; + char **env = NULL; + size_t env_len = 0; + + if (penv == NULL || penv_len == NULL || key == NULL || newkv == NULL) { + return -1; + } + + env = *penv; + env_len = *penv_len; + + for (i = 0; i < env_len; i++) { + size_t elen = strlen(env[i]); + if (key_len < elen && (strncmp(key, env[i], key_len) == 0) && (env[i][key_len] == '=')) { + free(env[i]); + env[i] = util_strdup_s(newkv); + if (env[i] == NULL) { + ERROR("out of memory"); + return -1; + } + return 0; + } + } + + /* can not find key env, return error. */ + return -1; +} + +int util_env_insert(char ***penv, size_t *penv_len, const char *key, size_t key_len, const char *newkv) +{ + char **env = NULL; + size_t env_len = 0; + char **temp = NULL; + int ret = 0; + + if (penv == NULL || penv_len == NULL || key == NULL || newkv == NULL) { + return -1; + } + + if (util_env_set_val(penv, penv_len, key, key_len, newkv) == 0) { + return 0; + } + + env = *penv; + env_len = *penv_len; + + if (env_len > (SIZE_MAX / sizeof(char *)) - 1) { + ERROR("Failed to realloc memory for envionment variables"); + return -1; + } + + ret = mem_realloc((void **)(&temp), (env_len + 1) * sizeof(char *), env, env_len * sizeof(char *)); + if (ret != 0) { + ERROR("Failed to realloc memory for envionment variables"); + return -1; + } + + env = temp; + env[env_len] = util_strdup_s(newkv); + env_len++; + + *penv = env; + *penv_len = env_len; + return 0; +} + +char *util_env_get_val(char **env, size_t env_len, const char *key, size_t key_len) +{ + size_t i = 0; + + if (key == NULL || env == NULL) { + return NULL; + } + + for (i = 0; i < env_len; i++) { + size_t elen = strlen(env[i]); + if (key_len < elen && !strncmp(key, env[i], key_len) && env[i][key_len] == '=') { + return util_strdup_s(env[i] + key_len + 1); + } + } + + return NULL; +} + +int util_parse_user_remap(const char *user_remap, unsigned int *host_uid, unsigned int *host_gid, + unsigned int *size) +{ + int ret = 0; + size_t args_len = 0; + char **items = NULL; + + if (user_remap == NULL || host_uid == NULL || host_gid == NULL || size == NULL) { + COMMAND_ERROR("Out of memory"); + return -1; + } + items = util_string_split(user_remap, ':'); + if (items == NULL) { + COMMAND_ERROR("split user remap '%s' failed", user_remap); + ret = -1; + goto out; + } + args_len = util_array_len(items); + + switch (args_len) { + case 3: + ret = util_safe_uint(items[0], host_uid); + if (ret) { + COMMAND_ERROR("Invalid host uid for '%s', uid must be unsigned int", user_remap); + break; + } + ret = util_safe_uint(items[1], host_gid); + if (ret) { + COMMAND_ERROR("Invalid host gid for '%s', gid must be unsigned int", user_remap); + break; + } + ret = util_safe_uint(items[2], size); + if (ret) { + COMMAND_ERROR("Invalid id offset for '%s', offset must be unsigned int", user_remap); + break; + } + if (*size > MAX_ID_OFFSET || *size == 0) { + COMMAND_ERROR("Invalid id offset for '%s', offset must be greater than 0 and less than %d", user_remap, + MAX_ID_OFFSET); + ret = -1; + break; + } + break; + default: + COMMAND_ERROR("Invalid user remap specification '%s'. unsupported format", user_remap); + ret = -1; + break; + } + +out: + util_free_array(items); + return ret; +} + +char *util_str_token(char **input, const char *delimiter) +{ + char *str = NULL; + char *delimiter_found = NULL; + char *tok = NULL; + size_t tok_length = 0; + + if (input == NULL || delimiter == NULL) { + return NULL; + } + + str = *input; + + if (str == NULL) { + return NULL; + } + delimiter_found = strstr(str, delimiter); + if (delimiter_found != NULL) { + tok_length = delimiter_found - str; + } else { + tok_length = strlen(str); + } + tok = strndup(str, tok_length); + if (tok == NULL) { + ERROR("strndup failed"); + return NULL; + } + *input = delimiter_found != NULL ? delimiter_found + strlen(delimiter) : NULL; + return tok; +} + +bool pid_max_kernel_namespaced() +{ + bool ret = false; + FILE *fp = NULL; + char *pline = NULL; + size_t length = 0; + + fp = util_fopen("/proc/kallsyms", "r"); + if (fp == NULL) { + SYSERROR("Failed to open /proc/kallsyms"); + return ret; + } + while (getline(&pline, &length, fp) != -1) { + if (strstr(pline, "proc_dointvec_pidmax") != NULL) { + ret = true; + goto out; + } + } +out: + fclose(fp); + free(pline); + return ret; +} + +bool check_sysctl_valid(const char *sysctl_key) +{ + size_t i = 0; + size_t full_keys_len = 0; + size_t key_prefixes_len = 0; + const char *sysctl_full_keys[] = { + "kernel.msgmax", "kernel.msgmnb", "kernel.msgmni", "kernel.sem", + "kernel.shmall", "kernel.shmmax", "kernel.shmmni", "kernel.shm_rmid_forced" + }; + const char *sysctl_key_prefixes[] = { "net.", "fs.mqueue." }; + + if (sysctl_key == NULL) { + return false; + } + + full_keys_len = sizeof(sysctl_full_keys) / sizeof(char *); + key_prefixes_len = sizeof(sysctl_key_prefixes) / sizeof(char *); + + for (i = 0; i < full_keys_len; i++) { + if (strcmp(sysctl_full_keys[i], sysctl_key) == 0) { + return true; + } + } + for (i = 0; i < key_prefixes_len; i++) { + if (strncmp(sysctl_key_prefixes[i], sysctl_key, strlen(sysctl_key_prefixes[i])) == 0) { + return true; + } + } + return false; +} + +void free_sensitive_string(char *str) +{ + if (!util_valid_str(str)) { + goto out; + } + + if (memset_s(str, strlen(str), 0, strlen(str)) != EOK) { + ERROR("Failed to memset sensitive string memory"); + } + +out: + free(str); +} + +void memset_sensitive_string(char *str) +{ + if (!util_valid_str(str)) { + return; + } + + if (memset_s(str, strlen(str), 0, strlen(str)) != EOK) { + ERROR("Failed to memset sensitive string memory"); + } +} + +static char *get_mtpoint(const char *line) +{ + int i; + const char *tmp = NULL; + char *pend = NULL; + char *sret = NULL; + size_t len; + + if (line == NULL) { + goto err_out; + } + + tmp = line; + + for (i = 0; i < 4; i++) { + tmp = strchr(tmp, ' '); + if (tmp == NULL) { + goto err_out; + } + tmp++; + } + pend = strchr(tmp, ' '); + if ((pend == NULL) || pend == tmp) { + goto err_out; + } + + /* stuck a \0 after the mountpoint */ + len = (size_t)(pend - tmp); + sret = util_common_calloc_s(len + 1); + if (sret == NULL) { + goto err_out; + } + if (memcpy_s(sret, len + 1, tmp, len) != EOK) { + free(sret); + sret = NULL; + goto err_out; + } + sret[len] = '\0'; + +err_out: + return sret; +} + +bool detect_mount(const char *path) +{ + FILE *fp = NULL; + char *line = NULL; + char *mountpoint = NULL; + size_t length = 0; + bool bret = false; + + fp = util_fopen("/proc/self/mountinfo", "r"); + if (fp == NULL) { + ERROR("Failed opening /proc/self/mountinfo"); + return false; + } + + while (getline(&line, &length, fp) != -1) { + mountpoint = get_mtpoint(line); + if (mountpoint == NULL) { + INFO("Error reading mountinfo: bad line '%s'", line); + continue; + } + if (strcmp(mountpoint, path) == 0) { + free(mountpoint); + bret = true; + goto out; + } + free(mountpoint); + } +out: + fclose(fp); + free(line); + return bret; +} + +static int set_echo_back(bool echo_back) +{ + struct termios old, new; + + if (tcgetattr(STDIN_FILENO, &old)) { + ERROR("get tc attribute failed: %s\n", strerror(errno)); + return -1; + } + + new = old; + + if (echo_back) { + new.c_lflag |= ECHO | ICANON; + } else { + new.c_lflag &= ~(ECHO | ICANON); + } + + if (tcsetattr(STDIN_FILENO, TCSANOW, &new)) { + ERROR("set tc attribute failed: %s\n", strerror(errno)); + return -1; + } + + return 0; +} + +int util_input_notty(char *buf, size_t maxlen) +{ + size_t i = 0; + int ret = 0; + + for (i = 0; i < maxlen; i++) { + int c = getchar(); + if (c == EOF || c == '\n') { + break; + } + if (c < 0) { + ret = -1; + break; + } + buf[i] = (char) c; + } + + return ret ? ret : (int)i; +} + +static int util_input(char *buf, size_t maxlen, bool echo_back) +{ + int ret = 0; + + if (set_echo_back(echo_back)) { + return -1; + } + + ret = util_input_notty(buf, maxlen); + + if (set_echo_back(true)) { + return -1; + } + + return ret; +} + +// Get input from stdin, echo back if get any charactor. +int util_input_echo(char *buf, size_t maxlen) +{ + return util_input(buf, maxlen, true); +} + +// Get input from stdin, no echo back. +int util_input_noecho(char *buf, size_t maxlen) +{ + return util_input(buf, maxlen, false); +} + +void usleep_nointerupt(unsigned long usec) +{ +#define SECOND_TO_USECOND_MUTIPLE 1000000 + int ret = 0; + struct timespec request = { 0 }; + struct timespec remain = { 0 }; + if (usec == 0) { + return; + } + + request.tv_sec = (time_t)(usec / SECOND_TO_USECOND_MUTIPLE); + request.tv_nsec = (long)((usec % SECOND_TO_USECOND_MUTIPLE) * 1000); + + do { + ret = nanosleep(&request, &remain); + request = remain; + } while (ret == -1 && errno == EINTR); +} + diff --git a/src/cutils/utils.h b/src/cutils/utils.h new file mode 100644 index 0000000..1376672 --- /dev/null +++ b/src/cutils/utils.h @@ -0,0 +1,414 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container sha256 functions + ********************************************************************************/ + +#ifndef __UTILS_H +#define __UTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils_string.h" +#include "utils_array.h" +#include "utils_file.h" +#include "utils_convert.h" +#include "utils_verify.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MS_REC +#define MS_REC 16384 +#endif + +#define MAX_ID_OFFSET 65535 +#define HOST_CHANNLE_ARGS 4 +#define HOST_CHANNLE_MIN_SIZE (4 * SIZE_KB) +#define MOUNT_PROPERTIES_SIZE 128 +#define HOST_PATH_MODE 0700 +#define DEFAULT_SHM_SIZE (64 * SIZE_MB) + +#define ECOMMON 1 + +/* image error start */ +#define EIMAGEBUSY 2 +#define ENAMECONFLICT 3 +#define EIMAGENOTFOUND 20 +/* image error end. reserved from 2 to 20 */ +#define EINVALIDARGS 125 +#define ESERVERERROR 125 +#define ERRORACCESS 126 +#define ECMDNOTFOUND 127 + +#define MAX_PATH_DEPTH 1024 + +#define MAX_BUFFER_SIZE 4096 +#define LCRD_NUMSTRLEN64 21 +#define MAXLINE 4096 +#define MAX_HOST_NAME_LEN 64 +#define MAX_IMAGE_NAME_LEN 255 +#define MAX_IMAGE_REF_LEN 384 +#define MAX_CONTAINER_NAME_LEN 1024 +#define MAX_RUNTIME_NAME_LEN 32 + +#define LOGIN_USERNAME_LEN 255 +#define LOGIN_PASSWORD_LEN 255 + +#define MAX_SHA256_IDENTIFIER 64 +#define SHA256_PREFIX "sha256:" + +#define UINT_LEN 10 + +/*container id max length*/ +#define CONTAINER_ID_MAX_LEN 64 + +#define LIST_SIZE_MAX 1000LL +#define LIST_DEVICE_SIZE_MAX 10000LL +#define LIST_ENV_SIZE_MAX 200000LL + +#define UNIX_SOCKET_PREFIX "unix://" + +#define SIZE_KB 1024LL +#define SIZE_MB (1024LL * SIZE_KB) +#define SIZE_GB (1024LL * SIZE_MB) +#define SIZE_TB (1024LL * SIZE_GB) +#define SIZE_PB (1024LL * SIZE_TB) + +#define Time_Nano 1LL +#define Time_Micro (1000LL * Time_Nano) +#define Time_Milli (1000LL * Time_Micro) +#define Time_Second (1000LL * Time_Milli) +#define Time_Minute (60LL * Time_Second) +#define Time_Hour (60LL * Time_Minute) + +/*Max regular file size for LCRC\LCRD to open as same as docker*/ +#define REGULAR_FILE_SIZE (10 * SIZE_MB) + +#define rFC339Local "2006-01-02T15:04:05" +#define rFC339NanoLocal "2006-01-02T15:04:05.999999999" +#define dateLocal "2006-01-02" +#define defaultContainerTime "0001-01-01T00:00:00Z" +#define TIME_STR_SIZE 512 + +#define HOST_NAME_REGEXP \ + "^(([[:alnum:]]|[[:alnum:]][[:alnum:]\\-]{0,63}[[:alnum:]])\\.){0,63}" \ + "([[:alnum:]]|[[:alnum:]][[:alnum:]\\-]{0,63}[[:alnum:]])$" +#define __TagPattern "^:([A-Za-z_0-9][A-Za-z_0-9.-]{0,127})$" +#define __NamePattern \ + "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])" \ + "((\\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(:[0-9]+)?/)?[a-z0-9]" \ + "+((([._]|__|[-]*)[a-z0-9]+)+)?((/[a-z0-9]+((([._]|__|[-]*)[a-z0-9]+)+)?)+)?$" + +// native umask value +#define ANNOTATION_UMAKE_KEY "native.umask" +#define UMASK_NORMAL "normal" +#define UMASK_SECURE "secure" + +// proxy value +#define HTTP_PROXY "http_proxy" +#define HTTPS_PROXY "https_proxy" +#define NO_PROXY "no_proxy" + +#ifndef SIGTRAP +#define SIGTRAP 5 +#endif + +#ifndef SIGIOT +#define SIGIOT 6 +#endif + +#ifndef SIGEMT +#define SIGEMT 7 +#endif + +#ifndef SIGBUS +#define SIGBUS 7 +#endif + +#ifndef SIGSTKFLT +#define SIGSTKFLT 16 +#endif + +#ifndef SIGCLD +#define SIGCLD 17 +#endif + +#ifndef SIGURG +#define SIGURG 23 +#endif + +#ifndef SIGXCPU +#define SIGXCPU 24 +#endif + +#ifndef SIGXFSZ +#define SIGXFSZ 25 +#endif + +#ifndef SIGVTALRM +#define SIGVTALRM 26 +#endif + +#ifndef SIGPROF +#define SIGPROF 27 +#endif + +#ifndef SIGWINCH +#define SIGWINCH 28 +#endif + +#ifndef SIGIO +#define SIGIO 29 +#endif + +#ifndef SIGPOLL +#define SIGPOLL 29 +#endif + +#ifndef SIGINFO +#define SIGINFO 29 +#endif + +#ifndef SIGLOST +#define SIGLOST 37 +#endif + +#ifndef SIGPWR +#define SIGPWR 30 +#endif + +#ifndef SIGUNUSED +#define SIGUNUSED 31 +#endif + +#ifndef SIGSYS +#define SIGSYS 31 +#endif + +#ifndef SIGRTMIN1 +#define SIGRTMIN1 34 +#endif + +#ifndef SIGRTMAX +#define SIGRTMAX 64 +#endif + +#define SIGNAL_MAP_DEFAULT \ + { \ + { SIGHUP, "HUP" }, \ + { SIGINT, "INT" }, \ + { SIGQUIT, "QUIT" }, \ + { SIGILL, "ILL" }, \ + { SIGABRT, "ABRT" }, \ + { SIGFPE, "FPE" }, \ + { SIGKILL, "KILL" }, \ + { SIGSEGV, "SEGV" }, \ + { SIGPIPE, "PIPE" }, \ + { SIGALRM, "ALRM" }, \ + { SIGTERM, "TERM" }, \ + { SIGUSR1, "USR1" }, \ + { SIGUSR2, "USR2" }, \ + { SIGCHLD, "CHLD" }, \ + { SIGCONT, "CONT" }, \ + { SIGSTOP, "STOP" }, \ + { SIGTSTP, "TSTP" }, \ + { SIGTTIN, "TTIN" }, \ + { SIGTTOU, "TTOU" }, \ + { SIGTRAP, "TRAP" }, \ + { SIGIOT, "IOT" }, \ + { SIGEMT, "EMT" }, \ + { SIGBUS, "BUS" }, \ + { SIGSTKFLT, "STKFLT" }, \ + { SIGCLD, "CLD" }, \ + { SIGURG, "URG" }, \ + { SIGXCPU, "XCPU" }, \ + { SIGXFSZ, "XFSZ" }, \ + { SIGVTALRM, "VTALRM" }, \ + { SIGPROF, "PROF" }, \ + { SIGWINCH, "WINCH" }, \ + { SIGIO, "IO" }, \ + { SIGPOLL, "POLL" }, \ + { SIGINFO, "INFO" }, \ + { SIGLOST, "LOST" }, \ + { SIGPWR, "PWR" }, \ + { SIGUNUSED, "UNUSED" }, \ + { SIGSYS, "SYS" }, \ + { SIGRTMIN, "RTMIN" }, \ + { SIGRTMIN + 1, "RTMIN+1" }, \ + { SIGRTMIN + 2, "RTMIN+2" }, \ + { SIGRTMIN + 3, "RTMIN+3" }, \ + { SIGRTMIN + 4, "RTMIN+4" }, \ + { SIGRTMIN + 5, "RTMIN+5" }, \ + { SIGRTMIN + 6, "RTMIN+6" }, \ + { SIGRTMIN + 7, "RTMIN+7" }, \ + { SIGRTMIN + 8, "RTMIN+8" }, \ + { SIGRTMIN + 9, "RTMIN+9" }, \ + { SIGRTMIN + 10, "RTMIN+10" }, \ + { SIGRTMIN + 11, "RTMIN+11" }, \ + { SIGRTMIN + 12, "RTMIN+12" }, \ + { SIGRTMIN + 13, "RTMIN+13" }, \ + { SIGRTMIN + 14, "RTMIN+14" }, \ + { SIGRTMIN + 15, "RTMIN+15" }, \ + { SIGRTMAX - 14, "RTMAX-14" }, \ + { SIGRTMAX - 13, "RTMAX-13" }, \ + { SIGRTMAX - 12, "RTMAX-12" }, \ + { SIGRTMAX - 11, "RTMAX-11" }, \ + { SIGRTMAX - 10, "RTMAX-10" }, \ + { SIGRTMAX - 9, "RTMAX-9" }, \ + { SIGRTMAX - 8, "RTMAX-8" }, \ + { SIGRTMAX - 7, "RTMAX-7" }, \ + { SIGRTMAX - 6, "RTMAX-6" }, \ + { SIGRTMAX - 5, "RTMAX-5" }, \ + { SIGRTMAX - 4, "RTMAX-4" }, \ + { SIGRTMAX - 3, "RTMAX-3" }, \ + { SIGRTMAX - 2, "RTMAX-2" }, \ + { SIGRTMAX - 1, "RTMAX-1" }, \ + { SIGRTMAX, "RTMAX" } \ + } + +/* Basic data structure which holds all information we can get about a process. + * (unless otherwise specified, fields are read from /proc/#/stat) + * + * Most of it comes from task_struct in linux/sched.h + */ +typedef struct _proc_t { + // 1st 16 bytes + int pid; /* process id */ + int ppid; /* pid of parent process */ + + char state; /* single-char code for process state (S=sleeping) */ + + unsigned long long utime, /* user-mode CPU time accumulated by process */ + stime, /* kernel-mode CPU time accumulated by process */ + // and so on... + cutime, /* cumulative utime of process and reaped children */ + cstime, /* cumulative stime of process and reaped children */ + start_time; /* start time of process -- seconds since 1-1-70 */ + + long priority, /* kernel scheduling priority */ + timeout, /* ? */ + nice, /* standard unix nice level of process */ + rss, /* resident set size from /proc/#/stat (pages) */ + it_real_value; /* ? */ + unsigned long rtprio, /* real-time priority */ + sched, /* scheduling class */ + vsize, /* number of pages of virtual memory ... */ + rss_rlim, /* resident set size limit? */ + flags, /* kernel flags for the process */ + min_flt, /* number of minor page faults since process start */ + maj_flt, /* number of major page faults since process start */ + cmin_flt, /* cumulative min_flt of process and child processes */ + cmaj_flt, /* cumulative maj_flt of process and child processes */ + nswap, /* ? */ + cnswap, /* cumulative nswap ? */ + start_code, /* address of beginning of code segment */ + end_code, /* address of end of code segment */ + start_stack, /* address of the bottom of stack for the process */ + kstk_esp, /* kernel stack pointer */ + kstk_eip, /* kernel instruction pointer */ + wchan; /* address of kernel wait channel proc is sleeping in */ + + char cmd[16]; /* basename of executable file in call to exec(2) */ + int pgrp, /* process group id */ + session, /* session id */ + tty, /* full device number of controlling terminal */ + tpgid, /* terminal process group id */ + exit_signal, /* might not be SIGCHLD */ + processor; /* current (or most recent?) CPU */ +} proc_t; + +struct signame { + int num; + const char *name; +}; + +#define UTIL_FREE_AND_SET_NULL(p) \ + do { \ + if ((p) != NULL) { \ + free((void *)(p)); \ + (p) = NULL; \ + } \ + } while (0) + +int mem_realloc(void **newptr, size_t newsize, void *oldptr, size_t oldsize); + +int util_check_inherited(bool closeall, int fd_to_ignore); + +int util_sig_parse(const char *sig_name); + +void *util_common_calloc_s(size_t size); + +char *util_strdup_s(const char *src); + +int wait_for_pid(pid_t pid); + +void util_contain_errmsg(const char *errmsg, int *exit_code); + +char *util_short_digest(const char *digest); + +char *util_full_digest(const char *digest); + +proc_t *util_stat2proc(const char *s, size_t len); + +bool util_process_alive(pid_t pid, unsigned long long start_time); + +int wait_for_pid_status(pid_t pid); + +typedef void (*exec_func_t)(void *args); +bool util_exec_cmd(exec_func_t cb_func, void *args, const char *stdin_msg, char **stdout_msg, char **stderr_msg); + +typedef void (*exec_top_func_t)(char **args, const char *pid_args, size_t args_len); +bool util_exec_top_cmd(exec_top_func_t cb_func, char **args, const char *pid_args, size_t args_len, char **stdout_msg, + char **stderr_msg); + +char **get_backtrace(void); + +int util_parse_time_str_to_nanoseconds(const char *value, int64_t *nanoseconds); + +proc_t *util_get_process_proc_info(pid_t pid); + +int util_parse_user_remap(const char *user_remap, unsigned int *host_uid, unsigned int *host_gid, unsigned int *size); + +int util_env_insert(char ***penv, size_t *penv_len, const char *key, size_t key_len, const char *newkv); +int util_env_set_val(char ***penv, const size_t *penv_len, const char *key, size_t key_len, const char *newkv); +char *util_env_get_val(char **env, size_t env_len, const char *key, size_t key_len); + +char *util_str_token(char **input, const char *delimiter); +bool check_sysctl_valid(const char *sysctl_key); +bool pid_max_kernel_namespaced(); +void free_sensitive_string(char *str); +void memset_sensitive_string(char *str); +bool detect_mount(const char *path); + +int util_input_notty(char *buf, size_t maxlen); +int util_input_echo(char *buf, size_t maxlen); +int util_input_noecho(char *buf, size_t maxlen); + +bool util_check_signal_valid(int sig); + +void usleep_nointerupt(unsigned long usec); + +#ifdef __cplusplus +} +#endif + +#endif /* __UTILS_H */ + diff --git a/src/cutils/utils_array.c b/src/cutils/utils_array.c new file mode 100644 index 0000000..d6aefe2 --- /dev/null +++ b/src/cutils/utils_array.c @@ -0,0 +1,125 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container utils functions + *******************************************************************************/ + +#define _GNU_SOURCE +#include "utils_array.h" + +#include "securec.h" + +#include "log.h" +#include "utils.h" + +size_t util_array_len(char **array) +{ + char **pos; + size_t len = 0; + + for (pos = array; pos != NULL && *pos != NULL; pos++) { + len++; + } + + return len; +} + +void util_free_array(char **array) +{ + char **p; + + for (p = array; p != NULL && *p != NULL; p++) { + UTIL_FREE_AND_SET_NULL(*p); + } + free(array); +} + +int util_array_append(char ***array, const char *element) +{ + size_t len; + char **new_array = NULL; + + if (array == NULL || element == NULL) { + return -1; + } + + // let new len to len + 2 for element and null + len = util_array_len(*array); + + if (len > SIZE_MAX / sizeof(char *) - 2) { + ERROR("Too many array elements!"); + return -1; + } + new_array = util_common_calloc_s((len + 2) * sizeof(char *)); + if (new_array == NULL) { + ERROR("Out of memory"); + return -1; + } + if (*array != NULL) { + if (memcpy_s(new_array, (len + 2) * sizeof(char *), + *array, len * sizeof(char *)) != EOK) { + ERROR("Failed to memcpy memory"); + free(new_array); + return -1; + } + UTIL_FREE_AND_SET_NULL(*array); + } + *array = new_array; + + new_array[len] = util_strdup_s(element); + + return 0; +} + +int util_grow_array(char ***orig_array, size_t *orig_capacity, size_t size, + size_t increment) +{ + size_t add_capacity; + char **add_array = NULL; + + if (orig_array == NULL || orig_capacity == NULL || increment == 0) { + return -1; + } + + if (((*orig_array) == NULL) || ((*orig_capacity) == 0)) { + UTIL_FREE_AND_SET_NULL(*orig_array); + *orig_capacity = 0; + } + + add_capacity = *orig_capacity; + while (size + 1 > add_capacity) { + add_capacity += increment; + } + if (add_capacity != *orig_capacity) { + if (add_capacity > SIZE_MAX / sizeof(void *)) { + return -1; + } + add_array = util_common_calloc_s(add_capacity * sizeof(void *)); + if (add_array == NULL) { + return -1; + } + if (*orig_array) { + if (memcpy_s(add_array, add_capacity * sizeof(void *), + *orig_array, *orig_capacity * sizeof(void *)) != EOK) { + ERROR("Failed to memcpy memory"); + free(add_array); + return -1; + } + UTIL_FREE_AND_SET_NULL(*orig_array); + } + + *orig_array = add_array; + *orig_capacity = add_capacity; + } + + return 0; +} diff --git a/src/cutils/utils_array.h b/src/cutils/utils_array.h new file mode 100644 index 0000000..7e667a5 --- /dev/null +++ b/src/cutils/utils_array.h @@ -0,0 +1,40 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container sha256 functions + ********************************************************************************/ + +#ifndef __UTILS_ARRAY_H +#define __UTILS_ARRAY_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +size_t util_array_len(char **array); + +void util_free_array(char **array); + +int util_grow_array(char ***orig_array, size_t *orig_capacity, size_t size, + size_t increment); + +int util_array_append(char ***array, const char *element); + +#ifdef __cplusplus +} +#endif + +#endif /* __UTILS_H */ diff --git a/src/cutils/utils_convert.c b/src/cutils/utils_convert.c new file mode 100644 index 0000000..e190c19 --- /dev/null +++ b/src/cutils/utils_convert.c @@ -0,0 +1,183 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container utils functions + *******************************************************************************/ + +#define _GNU_SOURCE +#include "utils_convert.h" + +#include +#include +#include +#include +#include +#include + +static inline bool is_invalid_error_str(const char *err_str, const char *numstr) +{ + return err_str == NULL || err_str == numstr || *err_str != '\0'; +} + +int util_safe_u16(const char *numstr, uint16_t *converted) +{ + char *err_str = NULL; + unsigned long int ui; + + if (numstr == NULL || converted == NULL) { + return -EINVAL; + } + + errno = 0; + ui = strtoul(numstr, &err_str, 0); + if (errno > 0) { + return -errno; + } + + if (is_invalid_error_str(err_str, numstr)) { + return -EINVAL; + } + + if (ui > 0xFFFF) { + return -ERANGE; + } + + *converted = (uint16_t)ui; + return 0; +} + +int util_safe_int(const char *num_str, int *converted) +{ + char *err_str = NULL; + signed long int li; + + if (num_str == NULL || converted == NULL) { + return -EINVAL; + } + + errno = 0; + li = strtol(num_str, &err_str, 0); + if (errno > 0) { + return -errno; + } + + if (is_invalid_error_str(err_str, num_str)) { + return -EINVAL; + } + + if (li > INT_MAX || li < INT_MIN) { + return -ERANGE; + } + + *converted = (int)li; + return 0; +} + +int util_safe_uint(const char *numstr, unsigned int *converted) +{ + char *err_str = NULL; + unsigned long long ull; + + if (numstr == NULL || converted == NULL) { + return -EINVAL; + } + + errno = 0; + ull = strtoull(numstr, &err_str, 0); + if (errno > 0) { + return -errno; + } + + if (is_invalid_error_str(err_str, numstr)) { + return -EINVAL; + } + + if (ull > UINT_MAX) { + return -ERANGE; + } + + *converted = (unsigned int)ull; + return 0; +} + +int util_safe_llong(const char *numstr, long long *converted) +{ + char *err_str = NULL; + long long ll; + + if (numstr == NULL || converted == NULL) { + return -EINVAL; + } + + errno = 0; + ll = strtoll(numstr, &err_str, 0); + if (errno > 0) { + return -errno; + } + + if (is_invalid_error_str(err_str, numstr)) { + return -EINVAL; + } + + *converted = (long long)ll; + return 0; +} + +int util_safe_strtod(const char *numstr, double *converted) +{ + char *err_str = NULL; + double ld; + + if (numstr == NULL || converted == NULL) { + return -EINVAL; + } + + errno = 0; + ld = strtod(numstr, &err_str); + if (errno > 0) { + return -errno; + } + + if (is_invalid_error_str(err_str, numstr)) { + return -EINVAL; + } + + *converted = ld; + return 0; +} + +static inline bool is_valid_str_bool_true(const char *str) +{ + return strcmp(str, "1") == 0 || strcmp(str, "t") == 0 || strcmp(str, "T") == 0 || strcmp(str, "true") == 0 || + strcmp(str, "TRUE") == 0 || strcmp(str, "True") == 0; +} + +static inline bool is_valid_str_bool_false(const char *str) +{ + return strcmp(str, "0") == 0 || strcmp(str, "f") == 0 || strcmp(str, "F") == 0 || strcmp(str, "false") == 0 || + strcmp(str, "FALSE") == 0 || strcmp(str, "False") == 0; +} + +int util_str_to_bool(const char *boolstr, bool *converted) +{ + if (boolstr == NULL || boolstr[0] == '\0') { + return -EINVAL; + } + if (is_valid_str_bool_true(boolstr)) { + *converted = true; + } else if (is_valid_str_bool_false(boolstr)) { + *converted = false; + } else { + return -EINVAL; + } + return 0; +} diff --git a/src/cutils/utils_convert.h b/src/cutils/utils_convert.h new file mode 100644 index 0000000..1183503 --- /dev/null +++ b/src/cutils/utils_convert.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container sha256 functions + ********************************************************************************/ + +#ifndef __UTILS_CONVERT_H +#define __UTILS_CONVERT_H +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int util_safe_u16(const char *numstr, uint16_t *converted); +int util_safe_int(const char *num_str, int *converted); +int util_safe_uint(const char *numstr, unsigned int *converted); +int util_safe_llong(const char *numstr, long long *converted); +int util_safe_strtod(const char *numstr, double *converted); +int util_str_to_bool(const char *boolstr, bool *converted); + +#ifdef __cplusplus +} +#endif + +#endif /* __UTILS_CONVERT_H */ diff --git a/src/cutils/utils_file.c b/src/cutils/utils_file.c new file mode 100644 index 0000000..22304cd --- /dev/null +++ b/src/cutils/utils_file.c @@ -0,0 +1,886 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container utils functions + *******************************************************************************/ + +#define _GNU_SOURCE +#include "utils_file.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "securec.h" + +#include "constants.h" +#include "log.h" +#include "utils.h" +#include "sha256.h" +#include "path.h" + +bool util_dir_exists(const char *path) +{ + struct stat s; + int nret; + + if (path == NULL) { + return false; + } + + nret = stat(path, &s); + if (nret < 0) { + return false; + } + + return S_ISDIR(s.st_mode); +} + +bool util_file_exists(const char *f) +{ + struct stat buf; + int nret; + + if (f == NULL) { + return false; + } + + nret = stat(f, &buf); + if (nret < 0) { + return false; + } + return true; +} + +// Remove removes the named file or directory. +int util_path_remove(const char *path) +{ + int saved_errno; + + if (path == NULL) { + return -1; + } + + if (unlink(path) == 0) { + return 0; + } + saved_errno = errno; + if (rmdir(path) == 0) { + return 0; + } + if (errno == ENOTDIR) { + errno = saved_errno; + } + return -1; +} + +ssize_t util_write_nointr(int fd, const void *buf, size_t count) +{ + ssize_t nret; + + if (buf == NULL) { + return -1; + } + + for (;;) { + nret = write(fd, buf, count); + if (nret < 0 && errno == EINTR) { + continue; + } else { + break; + } + } + return nret; +} + +ssize_t util_read_nointr(int fd, void *buf, size_t count) +{ + ssize_t nret; + + if (buf == NULL) { + return -1; + } + + for (;;) { + nret = read(fd, buf, count); + if (nret < 0 && errno == EINTR) { + continue; + } else { + break; + } + } + + return nret; +} + +int util_mkdir_p(const char *dir, mode_t mode) +{ + const char *tmp_pos = NULL; + const char *base = NULL; + char *cur_dir = NULL; + int len = 0; + + if (dir == NULL || strlen(dir) > PATH_MAX) { + goto err_out; + } + + tmp_pos = dir; + base = dir; + + do { + dir = tmp_pos + strspn(tmp_pos, "/"); + tmp_pos = dir + strcspn(dir, "/"); + len = (int)(dir - base); + if (len <= 0) { + break; + } + cur_dir = strndup(base, (size_t)len); + if (cur_dir == NULL) { + ERROR("strndup failed"); + goto err_out; + } + if (*cur_dir) { + if (mkdir(cur_dir, mode) && (errno != EEXIST || !util_dir_exists(cur_dir))) { + ERROR("failed to create directory '%s': %s", cur_dir, strerror(errno)); + goto err_out; + } + } + UTIL_FREE_AND_SET_NULL(cur_dir); + } while (tmp_pos != dir); + + return 0; +err_out: + free(cur_dir); + return -1; +} + +static bool check_dir_valid(const char *dirpath, int recursive_depth, int *failure) +{ + if ((recursive_depth + 1) > MAX_PATH_DEPTH) { + ERROR("Reach max path depth: %s", dirpath); + *failure = 1; + return false; + } + + if (!util_dir_exists(dirpath)) { + return false; + } + + return true; +} + +static int recursive_rmdir_next_depth(struct stat fstat, const char *fname, int recursive_depth, int *saved_errno, + int failure) +{ + if (S_ISDIR(fstat.st_mode)) { + if (util_recursive_rmdir(fname, (recursive_depth + 1)) < 0) { + failure = 1; + } + } else { + if (unlink(fname) < 0) { + ERROR("Failed to delete %s: %s", fname, strerror(errno)); + if (*saved_errno == 0) { + *saved_errno = errno; + } + failure = 1; + } + } + + return failure; +} + +static int recursive_rmdir_helper(const char *dirpath, int recursive_depth, int *saved_errno) +{ + int nret = 0; + struct dirent *pdirent = NULL; + DIR *directory = NULL; + int failure = 0; + char fname[MAXPATHLEN]; + + directory = opendir(dirpath); + if (directory == NULL) { + ERROR("Failed to open %s", dirpath); + return 1; + } + pdirent = readdir(directory); + for (; pdirent != NULL; pdirent = readdir(directory)) { + struct stat fstat; + int pathname; + + bool ret = !strcmp(pdirent->d_name, ".") || !strcmp(pdirent->d_name, ".."); + + if (ret) { + continue; + } + + nret = memset_s(fname, sizeof(fname), 0, sizeof(fname)); + if (nret != EOK) { + ERROR("Failed to memset memory"); + failure = 1; + continue; + } + pathname = sprintf_s(fname, MAXPATHLEN, "%s/%s", dirpath, pdirent->d_name); + + ret = pathname < 0 || pathname >= MAXPATHLEN; + if (ret) { + ERROR("Pathname too long"); + failure = 1; + continue; + } + + nret = lstat(fname, &fstat); + if (nret) { + ERROR("Failed to stat %s", fname); + failure = 1; + continue; + } + + failure = recursive_rmdir_next_depth(fstat, fname, recursive_depth, saved_errno, failure); + } + + if (rmdir(dirpath) < 0 && errno != ENOENT) { + if (*saved_errno == 0) { + *saved_errno = errno; + } + ERROR("Failed to delete %s", dirpath); + failure = 1; + } + + nret = closedir(directory); + if (nret) { + ERROR("Failed to close directory %s", dirpath); + failure = 1; + } + + return failure; +} + +int util_recursive_rmdir(const char *dirpath, int recursive_depth) +{ + int failure = 0; + int saved_errno = 0; + + if (dirpath == NULL) { + return -1; + } + + if (!check_dir_valid(dirpath, recursive_depth, &failure)) { + goto err_out; + } + + failure = recursive_rmdir_helper(dirpath, recursive_depth, &saved_errno); + +err_out: + errno = saved_errno; + return failure ? -1 : 0; +} + +char *util_path_join(const char *dir, const char *file) +{ + int nret = 0; + char path[PATH_MAX] = { 0 }; + char cleaned[PATH_MAX] = { 0 }; + + if (dir == NULL || file == NULL) { + ERROR("NULL dir or file failed"); + return NULL; + } + + nret = sprintf_s(path, sizeof(path), "%s/%s", dir, file); + if (nret < 0) { + ERROR("dir or file too long failed"); + return NULL; + } + + if (cleanpath(path, cleaned, sizeof(cleaned)) == NULL) { + ERROR("Failed to clean path: %s", path); + return NULL; + } + + return util_strdup_s(cleaned); +} + +/* + * if path do not exist, this function will create it. + */ +int util_ensure_path(char **confpath, const char *path) +{ + int err = -1; + int fd; + char real_path[PATH_MAX + 1] = { 0 }; + + if (confpath == NULL || path == NULL) { + return -1; + } + + fd = util_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, DEFAULT_SECURE_FILE_MODE); + if (fd < 0 && errno != EEXIST) { + ERROR("failed to open '%s'", path); + goto err; + } + if (fd >= 0) { + close(fd); + } + + if (strlen(path) > PATH_MAX || NULL == realpath(path, real_path)) { + ERROR("Failed to get real path: %s", path); + goto err; + } + + *confpath = util_strdup_s(real_path); + + err = EXIT_SUCCESS; + +err: + return err; +} + +static void set_char_to_terminator(char *p) +{ + *p = '\0'; +} + +/* + * @name is absolute path of this file. + * make all directory in this absolute path. + */ +int util_build_dir(const char *name) +{ + char *n = NULL; + char *p = NULL; + char *e = NULL; + int nret; + + if (name == NULL) { + return -1; + } + + n = util_strdup_s(name); + + if (n == NULL) { + ERROR("Out of memory while creating directory '%s'.", name); + return -1; + } + + e = &n[strlen(n)]; + for (p = n + 1; p < e; p++) { + if (*p != '/') { + continue; + } + set_char_to_terminator(p); + nret = mkdir(n, DEFAULT_SECURE_DIRECTORY_MODE); + if (nret && (errno != EEXIST || !util_dir_exists(n))) { + ERROR("failed to create directory '%s'.", n); + free(n); + return -1; + } + *p = '/'; + } + free(n); + return 0; +} + +char *util_human_size(uint64_t val) +{ + int index = 0; + int ret = 0; + size_t len = 0; + uint64_t ui = 0; + char *out = NULL; + char *uf[] = { "B", "KB", "MB", "GB" }; + + ui = val; + + for (;;) { + if (ui < 1024 || index >= 3) { + break; + } + + ui = (uint64_t)(ui / 1024); + index++; + } + + len = LCRD_NUMSTRLEN64 + 2 + 1; + out = util_common_calloc_s(len); + if (out == NULL) { + ERROR("Memory out"); + return NULL; + } + + ret = sprintf_s(out, len, "%llu%s", (unsigned long long)ui, uf[index]); + if (ret < 0) { + ERROR("Failed to print string"); + free(out); + return NULL; + } + + return out; +} + +char *util_human_size_decimal(int64_t val) +{ + int nret = 0; + int kb = 1024; + int mb = kb * 1024; + int gb = mb * 1024; + char out[16] = { 0 }; /* 16 is enough, format like: 123.456 MB */ + + if (val >= gb) { + nret = sprintf_s(out, sizeof(out), "%.3lf GB", ((double)val / gb)); + } else if (val >= mb) { + nret = sprintf_s(out, sizeof(out), "%.3lf MB", ((double)val / mb)); + } else if (val >= kb) { + nret = sprintf_s(out, sizeof(out), "%.3lf KB", ((double)val / kb)); + } else { + nret = sprintf_s(out, sizeof(out), "%lld B", (long long int)val); + } + if (nret < 0) { + ERROR("Failed to print string"); + return NULL; + } + + return util_strdup_s(out); +} + +int util_open(const char *filename, int flags, mode_t mode) +{ + char rpath[PATH_MAX] = { 0x00 }; + + if (cleanpath(filename, rpath, sizeof(rpath)) == NULL) { + return -1; + } + if (mode) { + return open(rpath, flags | O_CLOEXEC, mode); + } else { + return open(rpath, flags | O_CLOEXEC); + } +} + +FILE *util_fopen(const char *filename, const char *mode) +{ + unsigned int fdmode = 0; + int f_fd = -1; + int tmperrno; + FILE *fp = NULL; + char rpath[PATH_MAX] = { 0x00 }; + + if (mode == NULL) { + return NULL; + } + + if (cleanpath(filename, rpath, sizeof(rpath)) == NULL) { + ERROR("cleanpath failed"); + return NULL; + } + if (strncmp(mode, "a+", 2) == 0) { + fdmode = O_RDWR | O_CREAT | O_APPEND; + } else if (strncmp(mode, "a", 1) == 0) { + fdmode = O_WRONLY | O_CREAT | O_APPEND; + } else if (strncmp(mode, "w+", 2) == 0) { + fdmode = O_RDWR | O_TRUNC | O_CREAT; + } else if (strncmp(mode, "w", 1) == 0) { + fdmode = O_WRONLY | O_TRUNC | O_CREAT; + } else if (strncmp(mode, "r+", 2) == 0) { + fdmode = O_RDWR; + } else if (strncmp(mode, "r", 1) == 0) { + fdmode = O_RDONLY; + } + + fdmode |= O_CLOEXEC; + + f_fd = open(rpath, (int)fdmode, 0666); + if (f_fd < 0) { + return NULL; + } + + fp = fdopen(f_fd, mode); + tmperrno = errno; + if (fp == NULL) { + close(f_fd); + } + errno = tmperrno; + return fp; +} + +static char *util_file_digest(const char *filename) +{ + FILE *fp = NULL; + char *digest = NULL; + + if (filename == NULL) { + ERROR("invalid NULL param"); + return NULL; + } + + fp = util_fopen(filename, "r"); + if (fp == NULL) { + ERROR("failed to open file %s: %s", filename, strerror(errno)); + return NULL; + } + + digest = sha256_digest(fp, false); + if (digest == NULL) { + ERROR("calc digest for file %s failed: %s", filename, strerror(errno)); + goto err_out; + } + +err_out: + fclose(fp); + + return digest; +} + +char *util_full_file_digest(const char *filename) +{ + char *digest = NULL; + char *full_digest = NULL; + + if (filename == NULL) { + ERROR("invalid NULL param"); + return NULL; + } + + digest = util_file_digest(filename); + full_digest = util_full_digest(digest); + free(digest); + + return full_digest; +} + +static char *util_path_dir(const char *path) +{ + char *dir = NULL; + int len = 0; + int i = 0; + + if (path == NULL) { + ERROR("invalid NULL param"); + return NULL; + } + + len = (int)strlen(path); + if (len == 0) { + return util_strdup_s("."); + } + + dir = util_strdup_s(path); + + for (i = len - 1; i > 0; i--) { + if (dir[i] == '/') { + dir[i] = 0; + break; + } + } + + return dir; +} + +char *util_add_path(const char *path, const char *name) +{ + char *tmp_dir = NULL; + char *new_path = NULL; + + if (path == NULL || name == NULL) { + ERROR("invalid NULL param"); + return NULL; + } + + tmp_dir = util_path_dir(path); + new_path = util_path_join(tmp_dir, name); + free(tmp_dir); + + return new_path; +} + +/* note: This function can only read small text file. */ +char *util_read_text_file(const char *path) +{ + char *buf = NULL; + long len = 0; + size_t readlen = 0; + FILE *filp = NULL; + const long max_size = 10 * 1024 * 1024; /* 10M */ + + if (path == NULL) { + ERROR("invalid NULL param"); + return NULL; + } + + filp = util_fopen(path, "r"); + if (filp == NULL) { + ERROR("open file %s failed", path); + goto err_out; + } + + if (fseek(filp, 0, SEEK_END)) { + ERROR("Seek end failed"); + goto err_out; + } + + len = ftell(filp); + if (len > max_size) { + ERROR("File to large!"); + goto err_out; + } + + if (fseek(filp, 0, SEEK_SET)) { + ERROR("Seek set failed"); + goto err_out; + } + + buf = util_common_calloc_s((size_t)(len + 1)); + if (buf == NULL) { + ERROR("out of memroy"); + goto err_out; + } + + readlen = fread(buf, 1, (size_t)len, filp); + if (((readlen < (size_t)len) && (!feof(filp))) || (readlen > (size_t)len)) { + ERROR("Failed to read file %s, error: %s\n", path, strerror(errno)); + UTIL_FREE_AND_SET_NULL(buf); + goto err_out; + } + + buf[(size_t)len] = 0; + +err_out: + + if (filp != NULL) { + fclose(filp); + } + + return buf; +} + +int64_t util_file_size(const char *filename) +{ + struct stat st; + + if (filename == NULL) { + ERROR("invalid NULL param"); + return -1; + } + + if (stat(filename, &st)) { + ERROR("stat file %s failed: %s", filename, strerror(errno)); + return -1; + } + + return (int64_t)st.st_size; +} + +int util_list_all_subdir(const char *directory, char ***out) +{ + DIR *dir = NULL; + struct dirent *direntp = NULL; + char **names_array = NULL; + char tmpdir[PATH_MAX] = { 0 }; + int nret; + + if (directory == NULL || out == NULL) { + return -1; + } + + dir = opendir(directory); + if (dir == NULL) { + ERROR("Failed to open directory: %s error:%s", directory, strerror(errno)); + return -1; + } + direntp = readdir(dir); + for (; direntp != NULL; direntp = readdir(dir)) { + if (strncmp(direntp->d_name, ".", 1) == 0) { + continue; + } + + nret = sprintf_s(tmpdir, PATH_MAX, "%s/%s", directory, direntp->d_name); + if (nret < 0) { + ERROR("Sprintf: %s failed", direntp->d_name); + goto error_out; + } + if (!util_dir_exists(tmpdir)) { + DEBUG("%s is not directory", direntp->d_name); + continue; + } + if (util_array_append(&names_array, direntp->d_name)) { + ERROR("Failed to append subdirectory array"); + goto error_out; + } + } + + closedir(dir); + *out = names_array; + return 0; + +error_out: + closedir(dir); + util_free_array(names_array); + names_array = NULL; + return -1; +} + +int util_file2str(const char *filename, char *buf, size_t len) +{ + int fd = -1; + int num_read; + + if (filename == NULL || buf == NULL) { + return -1; + } + + fd = util_open(filename, O_RDONLY, 0); + if (fd == -1) { + return -1; + } + num_read = (int)read(fd, buf, len - 1); + if (num_read <= 0) { + num_read = -1; + } else { + buf[num_read] = 0; + } + close(fd); + + return num_read; +} + +static int find_executable(const char *file) +{ + struct stat buf; + int nret; + + nret = stat(file, &buf); + if (nret < 0) { + return errno; + } + if (!S_ISDIR(buf.st_mode) && (buf.st_mode & 0111) != 0) { + return 0; + } + + return EPERM; +} + +char *look_path(const char *file, char **err) +{ + char *path_env = NULL; + char *tmp = NULL; + char *full_path = NULL; + char *ret = NULL; + char **paths = NULL; + char **work = NULL; + + if (file == NULL || err == NULL) { + return NULL; + } + + /* if slash in file, directly use file and do not try PATH. */ + if (strings_contains_any(file, "/")) { + if (find_executable(file) == 0) { + return util_strdup_s(file); + } + if (asprintf(err, "find exec %s : %s", file, strerror(errno)) < 0) { + *err = util_strdup_s("Out of memory"); + } + return NULL; + } + + path_env = getenv("PATH"); + if (path_env == NULL) { + *err = util_strdup_s("Not found PATH env"); + return NULL; + } + + paths = util_string_split(path_env, ':'); + if (paths == NULL) { + *err = util_strdup_s("Split PATH failed"); + goto free_out; + } + for (work = paths; work && *work; work++) { + tmp = *work; + if (strcmp("", tmp) == 0) { + tmp = "."; + } + + full_path = util_path_join(tmp, file); + if (full_path == NULL) { + *err = util_strdup_s("Out of memory"); + goto free_out; + } + if (find_executable(full_path) == 0) { + ret = full_path; + goto free_out; + } + UTIL_FREE_AND_SET_NULL(full_path); + } + +free_out: + util_free_array(paths); + return ret; +} + +int util_write_file(const char *fname, const char *content, size_t content_len) +{ + int ret = 0; + int dst_fd = -1; + ssize_t len = 0; + + if (fname == NULL) { + return -1; + } + if (content == NULL || content_len == 0) { + return 0; + } + dst_fd = util_open(fname, O_WRONLY | O_CREAT | O_TRUNC, DEFAULT_SECURE_FILE_MODE); + if (dst_fd < 0) { + ERROR("Creat file: %s, failed: %s", fname, strerror(errno)); + ret = -1; + goto free_out; + } + len = util_write_nointr(dst_fd, content, content_len); + if (len < 0 || ((size_t)len) != content_len) { + ret = -1; + ERROR("Write file failed: %s", strerror(errno)); + goto free_out; + } +free_out: + if (dst_fd >= 0) { + close(dst_fd); + } + return ret; +} + +char *verify_file_and_get_real_path(const char *file) +{ +#define MAX_FILE_SIZE (10 * SIZE_MB) + char resolved_path[PATH_MAX] = { 0 }; + + if (file == NULL) { + return NULL; + } + if (realpath(file, resolved_path) == NULL) { + ERROR("Failed to get realpath: %s , %s", resolved_path, strerror(errno)); + return NULL; + } + + if (!util_file_exists(resolved_path)) { + ERROR("%s not exist!", resolved_path); + return NULL; + } + if (util_file_size(resolved_path) > MAX_FILE_SIZE) { + ERROR("%s too large!", resolved_path); + return NULL; + } + return util_strdup_s(resolved_path); +} + diff --git a/src/cutils/utils_file.h b/src/cutils/utils_file.h new file mode 100644 index 0000000..44b460b --- /dev/null +++ b/src/cutils/utils_file.h @@ -0,0 +1,78 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container sha256 functions + ********************************************************************************/ + +#ifndef __UTILS_FILE_H +#define __UTILS_FILE_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool util_dir_exists(const char *path); + +bool util_file_exists(const char *f); + +int util_path_remove(const char *path); + +ssize_t util_write_nointr(int fd, const void *buf, size_t count); + +ssize_t util_read_nointr(int fd, void *buf, size_t count); + +int util_mkdir_p(const char *dir, mode_t mode); + +int util_recursive_rmdir(const char *dirpath, int recursive_depth); + +char *util_path_join(const char *dir, const char *file); + +int util_ensure_path(char **confpath, const char *path); + +int util_build_dir(const char *name); + +char *util_human_size(uint64_t val); + +char *util_human_size_decimal(int64_t val); + +int util_open(const char *filename, int flags, mode_t mode); + +FILE *util_fopen(const char *filename, const char *mode); + +char *util_full_file_digest(const char *filename); + +char *util_add_path(const char *path, const char *name); + +char *util_read_text_file(const char *path); + +int64_t util_file_size(const char *filename); + +int util_list_all_subdir(const char *directory, char ***out); + +int util_file2str(const char *filename, char *buf, size_t len); + +char *look_path(const char *file, char **err); + +int util_write_file(const char *fname, const char *content, size_t content_len); + +char *verify_file_and_get_real_path(const char *file); + +#ifdef __cplusplus +} +#endif + +#endif /* __UTILS_H */ diff --git a/src/cutils/utils_string.c b/src/cutils/utils_string.c new file mode 100644 index 0000000..1171a77 --- /dev/null +++ b/src/cutils/utils_string.c @@ -0,0 +1,685 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container utils functions + *******************************************************************************/ + +#define _GNU_SOURCE +#include "utils_string.h" + +#include +#include "securec.h" + +#include "utils.h" +#include "log.h" + +struct unit_map_def { + int64_t mltpl; + char *name; +}; + +static struct unit_map_def const g_unit_map[] = { + {.mltpl = 1, .name = "I"}, + {.mltpl = 1, .name = "B"}, + {.mltpl = 1, .name = "IB"}, + {.mltpl = SIZE_KB, .name = "K"}, + {.mltpl = SIZE_KB, .name = "KI"}, + {.mltpl = SIZE_KB, .name = "KB"}, + {.mltpl = SIZE_KB, .name = "KIB"}, + {.mltpl = SIZE_MB, .name = "M"}, + {.mltpl = SIZE_MB, .name = "MI"}, + {.mltpl = SIZE_MB, .name = "MB"}, + {.mltpl = SIZE_MB, .name = "MIB"}, + {.mltpl = SIZE_GB, .name = "G"}, + {.mltpl = SIZE_GB, .name = "GI"}, + {.mltpl = SIZE_GB, .name = "GB"}, + {.mltpl = SIZE_GB, .name = "GIB"}, + {.mltpl = SIZE_TB, .name = "T"}, + {.mltpl = SIZE_TB, .name = "TI"}, + {.mltpl = SIZE_TB, .name = "TB"}, + {.mltpl = SIZE_TB, .name = "TIB"}, + {.mltpl = SIZE_PB, .name = "P"}, + {.mltpl = SIZE_PB, .name = "PI"}, + {.mltpl = SIZE_PB, .name = "PB"}, + {.mltpl = SIZE_PB, .name = "PIB"} +}; + +static size_t const g_unit_map_len = sizeof(g_unit_map) / sizeof(g_unit_map[0]); + +bool strings_contains_any(const char *str, const char *substr) +{ + size_t i = 0; + size_t j; + size_t len_str = 0; + size_t len_substr = 0; + + if (str == NULL || substr == NULL) { + return false; + } + + len_str = strlen(str); + len_substr = strlen(substr); + + for (i = 0; i < len_str; i++) { + for (j = 0; j < len_substr; j++) { + if (str[i] == substr[j]) { + return true; + } + } + } + return false; +} + +int strings_count(const char *str, unsigned char c) +{ + size_t i = 0; + int res = 0; + size_t len = 0; + + if (str == NULL) { + return 0; + } + + len = strlen(str); + for (i = 0; i < len; i++) { + if (str[i] == c) { + res++; + } + } + return res; +} + +// strings_in_slice tests whether a string is contained in array of strings or not. +// Comparison is case insensitive +bool strings_in_slice(const char **strarray, size_t alen, const char *str) +{ + size_t i; + + if (strarray == NULL || alen == 0 || str == NULL) { + return false; + } + + for (i = 0; i < alen; i++) { + if (strarray[i] != NULL && strcasecmp(strarray[i], str) == 0) { + return true; + } + } + + return false; +} + +// Returns a string that is generated after converting +// all uppercase characters in the str to lowercase. +char *strings_to_lower(const char *str) +{ + char *newstr = NULL; + char *pos = NULL; + + if (str == NULL) { + return NULL; + } + + newstr = util_strdup_s(str); + if (newstr == NULL) { + return NULL; + } + + for (pos = newstr; *pos; ++pos) { + *pos = (char)tolower((int)(*pos)); + } + return newstr; +} + +// Returns a string that is generated after converting +// all lowercase characters in the str to uppercase. +char *strings_to_upper(const char *str) +{ + char *newstr = NULL; + char *pos = NULL; + + if (str == NULL) { + return NULL; + } + + newstr = util_strdup_s(str); + if (newstr == NULL) { + return NULL; + } + + for (pos = newstr; *pos; ++pos) { + *pos = (char)toupper((int)(*pos)); + } + return newstr; +} + +static int parse_unit_multiple(const char *unit, int64_t *mltpl) +{ + size_t i; + if (unit[0] == '\0') { + *mltpl = 1; + return 0; + } + + for (i = 0; i < g_unit_map_len; i++) { + if (strcasecmp(unit, g_unit_map[i].name) == 0) { + *mltpl = g_unit_map[i].mltpl; + return 0; + } + } + return -EINVAL; +} + + +static int util_parse_size_int_and_float(const char *numstr, int64_t mlt, int64_t *converted) +{ + long long int_size = 0; + double float_size = 0; + long long int_real = 0; + long long float_real = 0; + char *dot = NULL; + int nret; + + dot = strchr(numstr, '.'); + if (dot != NULL) { + char tmp; + // interger.float + if (dot == numstr || *(dot + 1) == '\0') { + return -EINVAL; + } + // replace 123.456 to 120.456 + tmp = *(dot - 1); + *(dot - 1) = '0'; + // parsing 0.456 + nret = util_safe_strtod(dot - 1, &float_size); + // recover 120.456 to 123.456 + *(dot - 1) = tmp; + if (nret < 0) { + return nret; + } + float_real = (int64_t)float_size; + if (mlt > 0) { + if (INT64_MAX / mlt < (int64_t)float_size) { + return -ERANGE; + } + float_real = (int64_t)(float_size * mlt); + } + *dot = '\0'; + } + nret = util_safe_llong(numstr, &int_size); + if (nret < 0) { + return nret; + } + int_real = int_size; + if (mlt > 0) { + if (INT64_MAX / mlt < int_size) { + return -ERANGE; + } + int_real = int_size * mlt; + } + if (INT64_MAX - int_real < float_real) { + return -ERANGE; + } + + *converted = int_real + float_real; + return 0; +} + +int util_parse_byte_size_string(const char *s, int64_t *converted) +{ + int ret; + int64_t mltpl = 0; + char *dup = NULL; + char *pmlt = NULL; + + if (s == NULL || converted == NULL || s[0] == '\0' || !isdigit(s[0])) { + return -EINVAL; + } + + dup = util_strdup_s(s); + if (dup == NULL) { + return -ENOMEM; + } + + pmlt = dup; + while (*pmlt != '\0' && (isdigit(*pmlt) || *pmlt == '.')) { + pmlt++; + } + + ret = parse_unit_multiple(pmlt, &mltpl); + if (ret) { + free(dup); + return ret; + } + + // replace the first multiple arg to '\0' + *pmlt = '\0'; + ret = util_parse_size_int_and_float(dup, mltpl, converted); + free(dup); + return ret; +} + +static char **util_shrink_array(char **orig_array, size_t new_size) +{ + char **new_array = NULL; + size_t i = 0; + + if (new_size == 0) { + return orig_array; + } + if (new_size > SIZE_MAX / sizeof(char *)) { + ERROR("Invalid arguments"); + return orig_array; + } + new_array = util_common_calloc_s(new_size * sizeof(char *)); + if (new_array == NULL) { + return orig_array; + } + + for (i = 0; i < new_size; i++) { + new_array[i] = orig_array[i]; + } + free(orig_array); + return new_array; +} + +static char **make_empty_array() +{ + char **res_array = NULL; + + res_array = calloc(2, sizeof(char *)); + if (res_array == NULL) { + return NULL; + } + res_array[0] = util_strdup_s(""); + return res_array; +} + +char **util_string_split_multi(const char *src_str, char delim) +{ + int ret, tmp_errno; + char *token = NULL; + char *cur = NULL; + char **res_array = NULL; + char deli[2] = { delim, '\0' }; + size_t count = 0; + size_t capacity = 0; + char *tmpstr = NULL; + + if (src_str == NULL) { + return NULL; + } + + if (src_str[0] == '\0') { + return make_empty_array(); + } + + tmpstr = util_strdup_s(src_str); + cur = tmpstr; + token = strsep(&cur, deli); + while (token != NULL) { + ret = util_grow_array(&res_array, &capacity, count + 1, 16); + if (ret < 0) { + goto err_out; + } + res_array[count] = util_strdup_s(token); + count++; + token = strsep(&cur, deli); + } + free(tmpstr); + return util_shrink_array(res_array, count + 1); + +err_out: + tmp_errno = errno; + free(tmpstr); + util_free_array(res_array); + errno = tmp_errno; + return NULL; +} + +char **util_string_split(const char *src_str, char _sep) +{ + char *token = NULL; + char *str = NULL; + char *tmpstr = NULL; + char *reserve_ptr = NULL; + char deli[2] = { _sep, '\0' }; + char **res_array = NULL; + size_t capacity = 0; + size_t count = 0; + int ret, tmp_errno; + + if (src_str == NULL) { + return NULL; + } + if (src_str[0] == '\0') { + return make_empty_array(); + } + + tmpstr = util_strdup_s(src_str); + + str = tmpstr; + for (; (token = strtok_r(str, deli, &reserve_ptr)); str = NULL) { + ret = util_grow_array(&res_array, &capacity, count + 1, 16); + if (ret < 0) { + goto err_out; + } + res_array[count] = util_strdup_s(token); + count++; + } + if (res_array == NULL) { + free(tmpstr); + return make_empty_array(); + } + free(tmpstr); + return util_shrink_array(res_array, count + 1); + +err_out: + tmp_errno = errno; + free(tmpstr); + util_free_array(res_array); + errno = tmp_errno; + return NULL; +} + +const char *str_skip_str(const char *str, const char *skip) +{ + if (str == NULL || skip == NULL) { + return NULL; + } + + for (; ; str++, skip++) { + if (*skip == 0) { + return str; + } else if (*str != *skip) { + return NULL; + } + } +} + + +static char *util_string_delchar_inplace(char *s, unsigned char c) +{ + size_t i = 0; + size_t j = 0; + size_t slen = 0; + + if (s == NULL) { + return NULL; + } + + slen = strlen(s); + + while (i < slen) { + if (j == slen) { + s[i] = '\0'; + break; + } + + s[i] = s[j]; + + if (s[i] != c) { + i++; + } + j++; + } + + return s; +} + +char *util_string_delchar(const char *ss, unsigned char c) +{ + char *s = NULL; + + if (ss == NULL) { + return NULL; + } + + s = util_strdup_s(ss); + + return util_string_delchar_inplace(s, c); +} + +void util_trim_newline(char *s) +{ + size_t len; + + if (s == NULL) { + return; + } + len = strlen(s); + while ((len >= 1) && (s[len - 1] == '\n')) { + s[--len] = '\0'; + } +} + +static char *util_left_trim_space(char *str) +{ + char *begin = str; + char *tmp = str; + while (isspace(*begin)) { + begin++; + } + while ((*tmp++ = *begin++)) {} + return str; +} + +static char *util_right_trim_space(char *str) +{ + char *end = NULL; + size_t len = strlen(str); + if (len == 0) { + return str; + } + end = str + len - 1; + while (isspace(*end)) { + end--; + } + *(end + 1) = '\0'; + + return str; +} + +char *util_trim_space(char *str) +{ + if (str == NULL) { + return NULL; + } + str = util_left_trim_space(str); + str = util_right_trim_space(str); + return str; +} + +static char *util_left_trim_quotation(char *str) +{ + char *begin = str; + char *tmp = str; + + if (*str == '\0') { + return str; + } + + while ((*begin) == '\"') { + begin++; + } + while ((*tmp++ = *begin++)) {} + return str; +} + +static char *util_right_trim_quotation(char *str) +{ + char *end = NULL; + size_t len = strlen(str); + if (len == 0) { + return str; + } + + end = str + len - 1; + while (end >= str && ((*end) == '\0' || (*end) == '\n' || (*end) == '\"')) { + end--; + } + *(end + 1) = '\0'; + + return str; +} + +char *util_trim_quotation(char *str) +{ + if (str == NULL) { + return NULL; + } + str = util_left_trim_quotation(str); + str = util_right_trim_quotation(str); + return str; +} + +char **str_array_dup(const char **src, size_t len) +{ + size_t i; + char **dest = NULL; + + if (len == 0 || src == NULL) { + return NULL; + } + if (len > SIZE_MAX / sizeof(char *) - 1) { + return NULL; + } + dest = (char **)util_common_calloc_s(sizeof(char *) * (len + 1)); + if (dest == NULL) { + return NULL; + } + + for (i = 0; i < len; ++i) { + if (src[i] != NULL) { + dest[i] = util_strdup_s(src[i]); + } + } + return dest; +} + +static char *do_string_join(const char *sep, const char **parts, size_t parts_len, size_t result_len) +{ + char *res_string = NULL; + size_t iter; + + res_string = calloc(result_len + 1, 1); + if (res_string == NULL) { + return NULL; + } + + for (iter = 0; iter < parts_len - 1; iter++) { + if (strcat_s(res_string, result_len + 1, parts[iter]) != EOK) { + free(res_string); + return NULL; + } + if (strcat_s(res_string, result_len + 1, sep) != EOK) { + free(res_string); + return NULL; + } + } + if (strcat_s(res_string, result_len + 1, parts[parts_len - 1]) != EOK) { + free(res_string); + return NULL; + } + return res_string; +} + +char *util_string_join(const char *sep, const char **parts, size_t len) +{ + size_t sep_len; + size_t result_len; + size_t iter; + + if (len == 0 || parts == NULL || sep == NULL) { + return NULL; + } + + sep_len = strlen(sep); + + if ((sep_len != 0) && (sep_len != 1) && (len > SIZE_MAX / sep_len + 1)) { + return NULL; + } + result_len = (len - 1) * sep_len; + for (iter = 0; iter < len; iter++) { + if (parts[iter] == NULL || result_len >= SIZE_MAX - strlen(parts[iter])) { + return NULL; + } + result_len += strlen(parts[iter]); + } + + return do_string_join(sep, parts, len, result_len); +} + +char *util_string_append(const char *post, const char *pre) +{ + char *res_string = NULL; + size_t length = 0; + + if (post == NULL && pre == NULL) { + return NULL; + } + if (pre == NULL) { + return util_strdup_s(post); + } + if (post == NULL) { + return util_strdup_s(pre); + } + if (strlen(post) > ((SIZE_MAX - strlen(pre)) - 1)) { + ERROR("String is too long to be appended"); + return NULL; + } + length = strlen(post) + strlen(pre) + 1; + res_string = util_common_calloc_s(length); + if (res_string == NULL) { + return NULL; + } + if (strcat_s(res_string, length, pre) != EOK) { + free(res_string); + return NULL; + } + if (strcat_s(res_string, length, post) != EOK) { + free(res_string); + return NULL; + } + + return res_string; +} + +int dup_array_of_strings(const char **src, size_t src_len, char ***dst, size_t *dst_len) +{ + size_t i; + + if (src == NULL || src_len == 0) { + return 0; + } + + if (dst == NULL || dst_len == NULL) { + return -1; + } + + *dst = NULL; + *dst_len = 0; + if (src_len > SIZE_MAX / sizeof(char *)) { + ERROR("Src elements is too much!"); + return -1; + } + *dst = (char **)util_common_calloc_s(src_len * sizeof(char *)); + if (*dst == NULL) { + ERROR("Out of memory"); + return -1; + } + for (i = 0; i < src_len; i++) { + (*dst)[*dst_len] = (src[i] != NULL) ? util_strdup_s(src[i]) : NULL; + (*dst_len)++; + } + return 0; +} + diff --git a/src/cutils/utils_string.h b/src/cutils/utils_string.h new file mode 100644 index 0000000..03ab20d --- /dev/null +++ b/src/cutils/utils_string.h @@ -0,0 +1,68 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container sha256 functions + ********************************************************************************/ + +#ifndef __UTILS_STRING_H +#define __UTILS_STRING_H +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool strings_contains_any(const char *str, const char *substr); + +int strings_count(const char *str, unsigned char c); + +bool strings_in_slice(const char **strarray, size_t alen, const char *str); + +char *strings_to_lower(const char *str); + +char *strings_to_upper(const char *str); + +int util_parse_byte_size_string(const char *s, int64_t *converted); + +// Breaks src_str into an array of string according to _sep, +// note that two or more contiguous delimiter bytes is considered to be a single delimiter +char **util_string_split(const char *src_str, char _sep); + +// Breaks src_str into an array of string according to _sep, +// note that every delimiter bytes is considered to be a single delimiter +char **util_string_split_multi(const char *src_str, char delim); + +const char *str_skip_str(const char *str, const char *skip); + +char *util_string_delchar(const char *ss, unsigned char c); + +void util_trim_newline(char *s); + +char *util_trim_space(char *str); + +char *util_trim_quotation(char *str); + +char **str_array_dup(const char **src, size_t len); + +char *util_string_join(const char *sep, const char **parts, size_t len); + +char *util_string_append(const char *post, const char *pre); + +int dup_array_of_strings(const char **src, size_t src_len, char ***dst, size_t *dst_len); + +#ifdef __cplusplus +} +#endif + +#endif /* __UTILS_STRING_H */ diff --git a/src/cutils/utils_verify.c b/src/cutils/utils_verify.c new file mode 100644 index 0000000..e358082 --- /dev/null +++ b/src/cutils/utils_verify.c @@ -0,0 +1,666 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container utils functions + *******************************************************************************/ + +#define _GNU_SOURCE +#include "utils_verify.h" + +#include +#include +#ifdef HAVE_LIBCAP_H +#include +#endif + +#include "securec.h" + +#include "log.h" +#include "utils.h" + +const char *g_all_caps[] = { + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_DAC_READ_SEARCH", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETPCAP", + "CAP_LINUX_IMMUTABLE", + "CAP_NET_BIND_SERVICE", + "CAP_NET_BROADCAST", + "CAP_NET_ADMIN", + "CAP_NET_RAW", + "CAP_IPC_LOCK", + "CAP_IPC_OWNER", + "CAP_SYS_MODULE", + "CAP_SYS_RAWIO", + "CAP_SYS_CHROOT", + "CAP_SYS_PTRACE", + "CAP_SYS_PACCT", + "CAP_SYS_ADMIN", + "CAP_SYS_BOOT", + "CAP_SYS_NICE", + "CAP_SYS_RESOURCE", + "CAP_SYS_TIME", + "CAP_SYS_TTY_CONFIG", + "CAP_MKNOD", + "CAP_LEASE", +#ifdef CAP_AUDIT_WRITE + "CAP_AUDIT_WRITE", +#endif +#ifdef CAP_AUDIT_CONTROL + "CAP_AUDIT_CONTROL", +#endif + "CAP_SETFCAP", + "CAP_MAC_OVERRIDE", + "CAP_MAC_ADMIN", +#ifdef CAP_SYSLOG + "CAP_SYSLOG", +#endif +#ifdef CAP_WAKE_ALARM + "CAP_WAKE_ALARM", +#endif +#ifdef CAP_BLOCK_SUSPEND + "CAP_BLOCK_SUSPEND", +#endif +#ifdef CAP_AUDIT_READ + "CAP_AUDIT_READ" +#endif +}; + +/* + * return value: + * -1 failed + * 0 match + * 1 no match + */ +int util_reg_match(const char *patten, const char *str) +{ + int nret = 0; + regex_t reg; + regmatch_t regmatch = { 0 }; + + if (patten == NULL || str == NULL) { + ERROR("invalid NULL param"); + return -1; + } + + nret = regcomp(®, patten, REG_EXTENDED | REG_NOSUB); + if (nret) { + return -1; + } + + nret = regexec(®, str, 1, ®match, 0); + if (nret == 0) { + nret = 0; + goto free_out; + } else if (nret == REG_NOMATCH) { + nret = 1; + goto free_out; + } else { + nret = -1; + ERROR("reg match failed"); + goto free_out; + } + +free_out: + regfree(®); + + return nret; +} + +bool util_valid_cmd_arg(const char *arg) +{ + return (arg != NULL) && (strchr(arg, '|') == NULL) && (strchr(arg, '`') == NULL) && (strchr(arg, '&')) == NULL && + (strchr(arg, ';') == NULL); +} + +bool util_valid_signal(int sig) +{ + size_t n = 0; + const struct signame signames[] = SIGNAL_MAP_DEFAULT; + + for (n = 0; n < sizeof(signames) / sizeof(signames[0]); n++) { + if (signames[n].num == sig) { + return true; + } + } + + return false; +} + +int util_validate_absolute_path(const char *path) +{ + int nret = 0; + regex_t preg; + int status = 0; + regmatch_t regmatch; + + if (path == NULL) { + return -1; + } + + if (memset_s(®match, sizeof(regmatch_t), 0, sizeof(regmatch_t)) != EOK) { + WARN("Failed to set memory!"); + return -1; + } + + if (regcomp(&preg, "^(/[^/ ]*)+/?$", REG_NOSUB | REG_EXTENDED)) { + ERROR("Failed to compile the regex"); + nret = -1; + goto err_out; + } + + status = regexec(&preg, path, 1, ®match, 0); + regfree(&preg); + if (status != 0) { + nret = -1; + goto err_out; + } +err_out: + return nret; +} + +static int util_vaildate_tcp_socket(const char *socket) +{ + if (socket == NULL) { + return -1; + } + return util_reg_match("^(tcp://(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]).){3}" + "(25[0-5]|2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])|localhost):" + "((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})" + "|([1-5][0-9]{4})|([1-9][0-9]{0,3})|0))$", + socket); +} +static int util_validate_unix_socket(const char *socket) +{ + int nret = 0; + const char *name = NULL; + + if (socket == NULL) { + return -1; + } + + if (strncmp("unix://", socket, strlen("unix://"))) { + nret = -1; + goto err_out; + } + + name = socket + strlen("unix://"); + + if (name[0] == '\0') { + nret = -1; + goto err_out; + } + + nret = util_validate_absolute_path(name); + if (nret != 0) { + nret = -1; + goto err_out; + } +err_out: + return nret; +} + +bool util_validate_socket(const char *socket) +{ + return util_validate_unix_socket(socket) == 0 || util_vaildate_tcp_socket(socket) == 0; +} + +bool util_valid_device_mode(const char *mode) +{ + size_t i = 0; + + if (mode == NULL || !strcmp(mode, "")) { + return false; + } + + for (i = 0; i < strlen(mode); i++) { + if (mode[i] != 'r' && mode[i] != 'w' && mode[i] != 'm') { + return false; + } + } + + return true; +} + +bool util_valid_str(const char *str) +{ + return (str != NULL && str[0] != '\0') ? true : false; +} + +size_t util_get_all_caps_len() +{ + return sizeof(g_all_caps) / sizeof(char *); +} + +bool util_valid_cap(const char *cap) +{ + bool cret = true; + int nret = 0; + char tmpcap[32] = { 0 }; + size_t all_caps_len = util_get_all_caps_len(); + + if (cap == NULL) { + return false; + } + + nret = sprintf_s(tmpcap, sizeof(tmpcap), "CAP_%s", cap); + if (nret < 0) { + ERROR("Failed to print string"); + cret = false; + goto err_out; + } + if (!strings_in_slice(g_all_caps, all_caps_len, tmpcap)) { + cret = false; + goto err_out; + } + +err_out: + return cret; +} + +bool util_valid_container_id(const char *id) +{ + char *patten = "^[a-f0-9]{1,64}$"; + + if (id == NULL) { + ERROR("invalid NULL param"); + return false; + } + + return util_reg_match(patten, id) == 0; +} + +bool util_valid_container_name(const char *name) +{ + char *patten = "^/?[a-zA-Z0-9][a-zA-Z0-9_.-]+$"; + + if (name == NULL) { + ERROR("Invalid NULL param"); + return false; + } + + if (strnlen(name, MAX_CONTAINER_NAME_LEN + 1) > MAX_CONTAINER_NAME_LEN) { + ERROR("Container name '%s' too long, max length:%d", name, MAX_CONTAINER_NAME_LEN); + return false; + } + + return util_reg_match(patten, name) == 0; +} + +bool util_valid_container_id_or_name(const char *id_or_name) +{ + if (util_valid_container_id(id_or_name)) { + return true; + } + + return util_valid_container_name(id_or_name); +} + +bool util_valid_runtime_name(const char *name) +{ + if (name == NULL) { + ERROR("Invalid NULL param"); + return false; + } + + return strcasecmp(name, "lcr") == 0; +} + +bool util_valid_host_name(const char *name) +{ + if (name == NULL) { + ERROR("invalid NULL param"); + return false; + } + + if (strnlen(name, MAX_HOST_NAME_LEN + 1) > MAX_HOST_NAME_LEN) { + ERROR("Host name '%s' too long, max length:%d", name, MAX_HOST_NAME_LEN); + return false; + } + + return util_reg_match(HOST_NAME_REGEXP, name) == 0; +} + +char *util_tag_pos(const char *ref) +{ + char *tag_pos = NULL; + + if (ref == NULL) { + return NULL; + } + + /* Tag can not contain "/", so if "/" is found after last ":", + * it means this reference do not have a tag */ + tag_pos = strrchr(ref, ':'); + if (tag_pos != NULL) { + if (strchr(tag_pos, '/') == NULL) { + return tag_pos; + } + } + + return NULL; +} + +bool util_valid_embedded_image_name(const char *name) +{ + char *copy_name = NULL; + char *tag_pos = NULL; + bool bret = false; + + if (name == NULL) { + ERROR("invalid NULL param"); + return false; + } + + if (strnlen(name, MAX_IMAGE_NAME_LEN + 1) > MAX_IMAGE_NAME_LEN) { + return false; + } + + copy_name = util_strdup_s(name); + tag_pos = util_tag_pos(copy_name); + if (tag_pos == NULL) { + goto cleanup; + } + + if (util_reg_match(__TagPattern, tag_pos)) { + goto cleanup; + } + + *tag_pos = '\0'; + + if (util_reg_match(__NamePattern, copy_name)) { + goto cleanup; + } + + bret = true; +cleanup: + free(copy_name); + return bret; +} + +bool util_valid_image_name(const char *name) +{ + char *copy = NULL; + char *tag_pos = NULL; + bool bret = false; + + if (name == NULL) { + ERROR("invalid NULL param"); + return false; + } + + if (strnlen(name, MAX_IMAGE_NAME_LEN + 1) > MAX_IMAGE_NAME_LEN) { + return false; + } + + copy = util_strdup_s(name); + tag_pos = util_tag_pos(copy); + if (tag_pos != NULL) { + if (util_reg_match(__TagPattern, tag_pos)) { + goto cleanup; + } + + *tag_pos = '\0'; + } + + if (util_reg_match(__NamePattern, copy)) { + goto cleanup; + } + + bret = true; +cleanup: + free(copy); + return bret; +} + +bool util_valid_time_tz(const char *time) +{ + char *patten = "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{2,9}Z$"; + + if (time == NULL) { + ERROR("invalid NULL param"); + return false; + } + + return util_reg_match(patten, time) == 0; +} + +bool util_valid_digest(const char *digest) +{ + char *patten = "^sha256:([a-f0-9]{64})$"; + + if (digest == NULL) { + ERROR("invalid NULL param"); + return false; + } + + return util_reg_match(patten, digest) == 0; +} + +bool util_valid_file(const char *path, uint32_t fmod) +{ + struct stat s; + int nret; + + if (path == NULL) { + ERROR("invalid NULL param"); + return false; + } + + nret = stat(path, &s); + if (nret < 0) { + ERROR("stat failed, error: %s", strerror(errno)); + return false; + } + + return (s.st_mode & S_IFMT) == fmod; +} + +bool util_valid_digest_file(const char *path, const char *digest) +{ + char *file_digest = NULL; + + if (path == NULL || digest == NULL) { + ERROR("invalid NULL param"); + return false; + } + + file_digest = util_full_file_digest(path); + if (file_digest == NULL) { + ERROR("calc digest of file %s failed", path); + return false; + } + + if (strcmp(file_digest, digest)) { + ERROR("file %s digest %s not match %s", path, file_digest, digest); + free(file_digest); + return false; + } + + free(file_digest); + + return true; +} + +bool util_valid_key_type(const char *key) +{ + if (key == NULL) { + return false; + } + + return !strcmp(key, "type"); +} + +bool util_valid_key_src(const char *key) +{ + if (key == NULL) { + return false; + } + + return !strcmp(key, "src") || !strcmp(key, "source"); +} + +bool util_valid_key_dst(const char *key) +{ + if (key == NULL) { + return false; + } + + return !strcmp(key, "dst") || !strcmp(key, "destination"); +} + +bool util_valid_key_ro(const char *key) +{ + if (key == NULL) { + return false; + } + + return !strcmp(key, "ro") || !strcmp(key, "readonly"); +} + +bool util_valid_key_propagation(const char *key) +{ + if (key == NULL) { + return false; + } + + return !strcmp(key, "bind-propagation"); +} + +bool util_valid_key_selinux(const char *key) +{ + if (key == NULL) { + return false; + } + + return !strcmp(key, "bind-selinux-opts"); +} + +bool util_valid_value_true(const char *value) +{ + if (value == NULL) { + return false; + } + + return !strcmp(value, "1") || !strcmp(value, "true"); +} + +bool util_valid_value_false(const char *value) +{ + if (value == NULL) { + return false; + } + + return !strcmp(value, "0") || !strcmp(value, "false"); +} + +bool util_valid_rw_mode(const char *mode) +{ + return !strcmp(mode, "rw") || !strcmp(mode, "ro"); +} + +bool util_valid_label_mode(const char *mode) +{ + return !strcmp(mode, "z") || !strcmp(mode, "Z"); +} + +bool util_valid_copy_mode(const char *mode) +{ + return !strcmp(mode, "nocopy"); +} + +bool util_valid_propagation_mode(const char *mode) +{ + if (mode == NULL) { + return false; + } + return !strcmp(mode, "private") || !strcmp(mode, "rprivate") || !strcmp(mode, "slave") || !strcmp(mode, "rslave") || + !strcmp(mode, "shared") || !strcmp(mode, "rshared"); +} + +bool util_valid_mount_mode(const char *mode) +{ + int rw_mode_cnt = 0; + int pro_mode_cnt = 0; + int label_mode_cnt = 0; + int copy_mode_cnt = 0; + size_t i, mlen; + char **modes = NULL; + bool nret = false; + + modes = util_string_split(mode, ','); + if (modes == NULL) { + ERROR("Out of memory"); + return false; + } + mlen = util_array_len(modes); + + for (i = 0; i < mlen; i++) { + if (util_valid_rw_mode(modes[i])) { + rw_mode_cnt++; + } else if (util_valid_propagation_mode(modes[i])) { + pro_mode_cnt++; + } else if (util_valid_label_mode(modes[i])) { + label_mode_cnt++; + } else if (util_valid_copy_mode(modes[i])) { + copy_mode_cnt++; + } else { + goto err_out; + } + } + + if (rw_mode_cnt > 1 || pro_mode_cnt > 1 || label_mode_cnt > 1 || copy_mode_cnt > 1) { + goto err_out; + } + + nret = true; +err_out: + util_free_array(modes); + return nret; +} + +/* ShortIdentifierRegexp is the format used to represent a prefix + * of an identifier. A prefix may be used to match a sha256 identifier + * within a list of trusted identifiers. + * minimumTruncatedIDLength = 3 maxTruncatedIDLength = 64 + */ +bool util_valid_short_sha256_id(const char *id) +{ +#define __ShortIdentifierRegexp "^[a-f0-9]{3,64}$" + char *copy = NULL; + bool bret = false; + + if (id == NULL) { + ERROR("invalid NULL param"); + return false; + } + + if (strnlen(id, MAX_SHA256_IDENTIFIER + 1) > MAX_SHA256_IDENTIFIER) { + return false; + } + + copy = util_strdup_s(id); + + if (util_reg_match(__ShortIdentifierRegexp, copy) != 0) { + goto cleanup; + } + + bret = true; +cleanup: + free(copy); + return bret; +} diff --git a/src/cutils/utils_verify.h b/src/cutils/utils_verify.h new file mode 100644 index 0000000..26b8610 --- /dev/null +++ b/src/cutils/utils_verify.h @@ -0,0 +1,104 @@ +/****************************************************************************** +#include "buffer.h" + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container sha256 functions + ********************************************************************************/ + +#ifndef __UTILS_VERIFY_H +#define __UTILS_VERIFY_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern const char *g_all_caps[]; + +int util_reg_match(const char *patten, const char *str); + +bool util_valid_cmd_arg(const char *arg); + +bool util_valid_signal(int sig); + +int util_validate_absolute_path(const char *path); + +bool util_validate_socket(const char *socket); + +bool util_valid_device_mode(const char *mode); + +bool util_valid_str(const char *str); + +size_t util_get_all_caps_len(); + +bool util_valid_cap(const char *cap); + +bool util_valid_time_tz(const char *time); + +bool util_valid_embedded_image_name(const char *name); + +bool util_valid_image_name(const char *name); + +char *util_tag_pos(const char *ref); + +bool util_valid_file(const char *path, uint32_t fmod); + +bool util_valid_digest(const char *digest); + +bool util_valid_digest_file(const char *path, const char *digest); + +bool util_valid_key_type(const char *key); + +bool util_valid_key_src(const char *key); + +bool util_valid_key_dst(const char *key); + +bool util_valid_key_ro(const char *key); + +bool util_valid_key_propagation(const char *key); + +bool util_valid_key_selinux(const char *key); + +bool util_valid_value_true(const char *value); + +bool util_valid_value_false(const char *value); + +bool util_valid_rw_mode(const char *mode); + +bool util_valid_label_mode(const char *mode); + +bool util_valid_copy_mode(const char *mode); + +bool util_valid_propagation_mode(const char *mode); + +bool util_valid_mount_mode(const char *mode); + +bool util_valid_container_id(const char *id); + +bool util_valid_container_name(const char *name); + +bool util_valid_container_id_or_name(const char *id_or_name); + +bool util_valid_host_name(const char *name); + +bool util_valid_runtime_name(const char *name); + +bool util_valid_short_sha256_id(const char *id); + +#ifdef __cplusplus +} +#endif + +#endif /* __UTILS_H */ diff --git a/src/engines/CMakeLists.txt b/src/engines/CMakeLists.txt new file mode 100644 index 0000000..dc1297d --- /dev/null +++ b/src/engines/CMakeLists.txt @@ -0,0 +1,14 @@ +# get current directory sources files + +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} engines_top_srcs) +add_subdirectory(lcr) +set(ENGINES_SRCS + ${engines_top_srcs} + ${ENGINES_LCR_SRCS} + PARENT_SCOPE + ) +set(ENGINES_INCS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/lcr + PARENT_SCOPE + ) diff --git a/src/engines/engine.c b/src/engines/engine.c new file mode 100644 index 0000000..429d502 --- /dev/null +++ b/src/engines/engine.c @@ -0,0 +1,336 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container engine functions + ******************************************************************************/ +#include "engine.h" + +#include +#include +#include +#include +#include + +#include "constants.h" +#include "linked_list.h" +#include "lcrd_config.h" +#include "securec.h" +#include "log.h" +#include "utils.h" +#include "lcr_engine.h" +#include "liblcrd.h" + +struct lcrd_engine_operation_lists { + pthread_rwlock_t lcrd_engines_op_rwlock; + struct linked_list lcrd_engines_op_list; +}; + +static struct lcrd_engine_operation_lists g_lcrd_engines_lists; + +typedef int (*engine_init_func_t)(struct engine_operation *ops); + +/* engine global init */ +int engines_global_init() +{ + int ret = 0; + errno_t mret = EOK; + mret = memset_s(&g_lcrd_engines_lists, sizeof(struct lcrd_engine_operation_lists), 0x00, + sizeof(struct lcrd_engine_operation_lists)); + if (mret != EOK) { + ERROR("Failed to set memory"); + ret = -1; + goto out; + } + + /* init lcrd_engines_op_rwlock */ + + ret = pthread_rwlock_init(&g_lcrd_engines_lists.lcrd_engines_op_rwlock, NULL); + if (ret != 0) { + ERROR("Failed to init lcrd conf rwlock"); + ret = -1; + goto out; + } + + linked_list_init(&g_lcrd_engines_lists.lcrd_engines_op_list); + +out: + return ret; +} + +/* engine routine log init */ +static int engine_routine_log_init(const struct engine_operation *eop) +{ + int ret = 0; + char *engine_log_path = NULL; + struct service_arguments *args = NULL; + + if (eop == NULL || eop->engine_log_init_op == NULL) { + ERROR("Failed to get engine log init operations"); + ret = -1; + goto out; + } + + engine_log_path = conf_get_engine_log_file(); + if (lcrd_server_conf_rdlock()) { + ret = -1; + goto out; + } + + args = conf_get_server_conf(); + if (args == NULL) { + ERROR("Failed to get lcrd server config"); + ret = -1; + goto unlock_out; + } + + if (engine_log_path == NULL) { + ERROR("Log fifo path is NULL"); + ret = -1; + goto unlock_out; + } + ret = eop->engine_log_init_op(args->progname, engine_log_path, args->json_confs->log_level, eop->engine_type, + args->quiet, NULL); + if (ret != 0) { + ret = -1; + goto unlock_out; + } + +unlock_out: + if (lcrd_server_conf_unlock()) { + ret = -1; + goto out; + } +out: + free(engine_log_path); + return ret; +} + +/* engine operation free */ +void engine_operation_free(struct engine_operation *eop) +{ + if (eop->engine_type != NULL) { + free(eop->engine_type); + eop->engine_type = NULL; + } +} + +/* create engine root path */ +static int create_engine_root_path(const char *path) +{ + int ret = -1; + + if (path == NULL) { + return ret; + } + + if (util_dir_exists(path)) { + ret = 0; + goto out; + } + ret = util_mkdir_p(path, CONFIG_DIRECTORY_MODE); + if (ret != 0) { + ERROR("Unable to create engine root path: %s", path); + } + +out: + return ret; +} + +static struct engine_operation *query_engine_locked(const char *name) +{ + struct engine_operation *engine_op = NULL; + struct linked_list *it = NULL; + struct linked_list *next = NULL; + + linked_list_for_each_safe(it, &g_lcrd_engines_lists.lcrd_engines_op_list, next) { + engine_op = (struct engine_operation *)it->elem; + if (engine_op == NULL) { + DEBUG("Invalid engine list elem"); + linked_list_del(it); + continue; + } + + if (strcasecmp(name, engine_op->engine_type) == 0) { + break; + } + engine_op = NULL; + } + + return engine_op; +} + +static struct engine_operation *new_engine_locked(const char *name) +{ + struct engine_operation *engine_op = NULL; + char *rootpath = NULL; + + /* now we just support lcr engine */ + if (strcasecmp(name, "lcr") == 0) { + engine_op = lcr_engine_init(); + } + + if (engine_op == NULL) { + ERROR("Failed to initialize engine or runtime: %s", name); + lcrd_set_error_message("Failed to initialize engine or runtime: %s", name); + return NULL; + } + + /* First init engine log */ + if (engine_routine_log_init(engine_op) != 0) { + ERROR("Init engine: %s log failed", name); + goto out; + } + + rootpath = conf_get_routine_rootdir(name); + if (rootpath == NULL) { + ERROR("Root path is NULL"); + goto out; + } + + if (create_engine_root_path(rootpath)) { + ERROR("Create engine path failed"); + free(rootpath); + goto out; + } + + free(rootpath); + return engine_op; + +out: + engine_operation_free(engine_op); + free(engine_op); + + return NULL; +} + +/* engines discovery */ +int engines_discovery(const char *name) +{ + int ret = 0; + struct engine_operation *engine_op = NULL; + struct linked_list *newnode = NULL; + + if (name == NULL) { + return -1; + } + + if (pthread_rwlock_wrlock(&g_lcrd_engines_lists.lcrd_engines_op_rwlock)) { + ERROR("Failed to acquire lcrd engines list write lock"); + return -1; + } + + engine_op = query_engine_locked(name); + if (engine_op != NULL) { + goto unlock_out; + } + + engine_op = new_engine_locked(name); + if (engine_op == NULL) { + ret = -1; + goto unlock_out; + } + + newnode = util_common_calloc_s(sizeof(struct linked_list)); + if (newnode == NULL) { + CRIT("Memory allocation error."); + ret = -1; + + engine_operation_free(engine_op); + free(engine_op); + goto unlock_out; + } + + linked_list_add_elem(newnode, engine_op); + linked_list_add_tail(&g_lcrd_engines_lists.lcrd_engines_op_list, newnode); + +unlock_out: + if (pthread_rwlock_unlock(&g_lcrd_engines_lists.lcrd_engines_op_rwlock)) { + ERROR("Failed to release lcrd engines list write lock"); + ret = -1; + } + + return ret; +} + +/* engines check handler exist */ +static struct engine_operation *engines_check_handler_exist(const char *name) +{ + struct engine_operation *engine_op = NULL; + struct linked_list *it = NULL; + struct linked_list *next = NULL; + + if (name == NULL) { + goto out; + } + + if (pthread_rwlock_rdlock(&g_lcrd_engines_lists.lcrd_engines_op_rwlock)) { + ERROR("Failed to acquire lcrd engines list read lock"); + engine_op = NULL; + goto out; + } + + linked_list_for_each_safe(it, &g_lcrd_engines_lists.lcrd_engines_op_list, next) { + engine_op = (struct engine_operation *)it->elem; + if (engine_op == NULL) { + DEBUG("Invalid engine list elem"); + linked_list_del(it); + continue; + } + if (strcasecmp(name, engine_op->engine_type) == 0) { + /* find the matched handle */ + break; + } + engine_op = NULL; + } + + if (pthread_rwlock_unlock(&g_lcrd_engines_lists.lcrd_engines_op_rwlock)) { + CRIT("Failed to release lcrd engines list read lock"); + engine_op = NULL; + goto out; + } + +out: + return engine_op; +} + +/* + * get the engine operation by engine name, + * if not exist in the list, try to discovery it, and then get it again + */ +struct engine_operation *engines_get_handler(const char *name) +{ + struct engine_operation *engine_op = NULL; + + if (name == NULL) { + ERROR("Runtime is NULL"); + engine_op = NULL; + goto out; + } + + engine_op = engines_check_handler_exist(name); + if (engine_op != NULL) { + goto out; + } + + if (engines_discovery(name)) { + engine_op = NULL; + goto out; + } + + engine_op = engines_check_handler_exist(name); + if (engine_op != NULL) { + goto out; + } + +out: + return engine_op; +} diff --git a/src/engines/engine.h b/src/engines/engine.h new file mode 100644 index 0000000..3020254 --- /dev/null +++ b/src/engines/engine.h @@ -0,0 +1,219 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container engine definition + ******************************************************************************/ +#ifndef __LCRD_ENGINE_H +#define __LCRD_ENGINE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct engine_console_config { + char *log_path; + unsigned int log_rotate; + char *log_file_size; +}; + +struct engine_cgroup_resources { + uint64_t blkio_weight; + uint64_t cpu_shares; + uint64_t cpu_period; + uint64_t cpu_quota; + char *cpuset_cpus; + char *cpuset_mems; + uint64_t memory_limit; + uint64_t memory_swap; + uint64_t memory_reservation; + uint64_t kernel_memory_limit; +}; + +typedef enum { + ENGINE_CONTAINER_STATUS_UNKNOWN = 0, + ENGINE_CONTAINER_STATUS_CREATED = 1, + ENGINE_CONTAINER_STATUS_STARTING = 2, + ENGINE_CONTAINER_STATUS_RUNNING = 3, + ENGINE_CONTAINER_STATUS_STOPPED = 4, + ENGINE_CONTAINER_STATUS_PAUSED = 5, + ENGINE_CONTAINER_STATUS_RESTARTING = 6, + ENGINE_CONTAINER_STATUS_MAX_STATE = 7 +} Engine_Container_Status; + +struct engine_container_summary_info { + char *id; + uint32_t has_pid; + uint32_t pid; + Engine_Container_Status status; + char *image; + char *command; + uint32_t exit_code; + uint32_t restart_count; + char *startat; + char *finishat; +}; + +struct engine_container_info { + char *id; + bool has_pid; + uint32_t pid; + Engine_Container_Status status; + uint64_t pids_current; + /* CPU usage */ + uint64_t cpu_use_nanos; + uint64_t cpu_system_use; + /* BlkIO usage */ + uint64_t blkio_read; + uint64_t blkio_write; + /* Memory usage */ + uint64_t mem_used; + uint64_t mem_limit; + /* Kernel Memory usage */ + uint64_t kmem_used; + uint64_t kmem_limit; +}; + +typedef struct _container_pid_t { + int pid; /* process id */ + int ppid; /* pid of parent process */ + unsigned long long start_time, /* start time of process -- seconds since 1-1-70 */ + pstart_time; /* start time of parent process -- seconds since 1-1-70 */ +} container_pid_t; + +typedef struct _engine_start_request_t { + const char *name; + const char *lcrpath; + + const char *logpath; + const char *loglevel; + + bool daemonize; + bool tty; + bool open_stdin; + const char *pidfile; + const char **console_fifos; + const char *console_logpath; + uint32_t start_timeout; + const char *container_pidfile; + const char *exit_fifo; + + uid_t uid; + gid_t gid; + gid_t *additional_gids; + size_t additional_gids_len; + + const char **share_ns; +} engine_start_request_t; + +typedef bool (*engine_create_t)(const char *, const char *, const char *, const char *, void *); + +typedef bool (*engine_start_t)(const engine_start_request_t *request); + +typedef bool (*engine_clean_t)(const char *name, const char *lcrpath, const char *logpath, const char *loglevel, + pid_t pid); + +typedef bool (*engine_kill_t)(const char *name, const char *enginepath, uint32_t signal); + +typedef int (*engine_kill_monitor_t)(const char *name, const char *enginepath); + +typedef bool (*engine_delete_t)(const char *name, const char *enginepath); + +typedef bool (*engine_pause_t)(const char *name, const char *enginepath); + +typedef bool (*engine_resume_t)(const char *name, const char *enginepath); + +typedef bool (*engine_reset_t)(const char *name, const char *enginepath); + +typedef bool (*engine_update_t)(const char *name, const char *enginepath, const struct engine_cgroup_resources *cr); + +typedef bool (*engine_exec_t)(const char *name, const char *enginepath, const char *logpath, const char *loglevel, + const char *console_fifos[], char * const argv[], char * const env[], int64_t timeout, + pid_t *pid, int *exit_code); + +typedef int (*engine_get_all_containers_info_t)(const char *enginepath, struct engine_container_summary_info **cons); + +typedef struct engine_container_summary_info *(*engine_get_container_info_t)(const char *name, const char *enginepath); + +typedef void (*engine_free_container_info_t)(struct engine_container_summary_info *info); + +typedef void (*engine_free_all_containers_info_t)(struct engine_container_summary_info *info, int num); + +typedef int (*engine_get_container_status_t)(const char *name, const char *enginepath, + struct engine_container_info *status); + +typedef void (*engine_free_container_status_t)(struct engine_container_info *container); + +typedef bool (*engine_get_container_pids_t)(const char *name, const char *rootpath, pid_t **pids, size_t *pids_len); + +typedef int (*engine_monitord_spawn_t)(const char *enginepath); + +typedef bool (*engine_console_t)(const char *name, const char *enginepath, char *in_fifo, char *out_fifo, + char *err_fifo); + +typedef bool (*engine_get_console_config_t)(const char *name, const char *enginepath, + struct engine_console_config *config); + +typedef void (*engine_free_console_config_t)(struct engine_console_config *config); + +typedef int (*engine_log_init_t)(const char *name, const char *file, const char *priority, const char *prefix, + int quiet, const char *enginepath); + +typedef uint32_t (*engine_get_errno_t)(); + +typedef const char *(*engine_get_errmsg_t)(); + +typedef void (*engine_clear_errmsg_t)(); + +struct engine_operation { + char *engine_type; + engine_create_t engine_create_op; + engine_start_t engine_start_op; + engine_kill_t engine_kill_op; + engine_kill_monitor_t engine_kill_monitor_op; + engine_delete_t engine_delete_op; + engine_pause_t engine_pause_op; + engine_resume_t engine_resume_op; + engine_reset_t engine_reset_op; + engine_exec_t engine_exec_op; + engine_console_t engine_console_op; + engine_get_container_status_t engine_get_container_status_op; + engine_free_container_status_t engine_free_container_status_op; + engine_get_all_containers_info_t engine_get_all_containers_info_op; + engine_free_all_containers_info_t engine_free_all_containers_info_op; + engine_get_container_pids_t engine_get_container_pids_op; + engine_log_init_t engine_log_init_op; + engine_update_t engine_update_op; + engine_get_console_config_t engine_get_console_config_op; + engine_free_console_config_t engine_free_console_config_op; + engine_get_errmsg_t engine_get_errmsg_op; + engine_clear_errmsg_t engine_clear_errmsg_op; + engine_clean_t engine_clean_op; +}; + +extern int engines_global_init(); + +extern void engine_operation_free(struct engine_operation *eop); + +extern int engines_discovery(const char *name); + +extern struct engine_operation *engines_get_handler(const char *name); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/engines/lcr/CMakeLists.txt b/src/engines/lcr/CMakeLists.txt new file mode 100644 index 0000000..5b848c2 --- /dev/null +++ b/src/engines/lcr/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_lcr_srcs) + +set(ENGINES_LCR_SRCS + ${local_lcr_srcs} + PARENT_SCOPE + ) diff --git a/src/engines/lcr/lcr_engine.c b/src/engines/lcr/lcr_engine.c new file mode 100644 index 0000000..4da846d --- /dev/null +++ b/src/engines/lcr/lcr_engine.c @@ -0,0 +1,503 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container lcr engine functions + ******************************************************************************/ +#include "lcr_engine.h" + +#include +#include +#include +#include +#include + +#include + +#include "error.h" +#include "securec.h" +#include "engine.h" +#include "log.h" +#include "lcrd_config.h" + +typedef int(*lcr_monitor_open_t)(const char *lcrpath); +typedef int(*lcr_monitor_read_t)(int fd, struct lcr_msg *msg); +typedef struct lcr_container_info *(*lcr_container_info_get_op_t)(const char *name, const char *lcrpath); +typedef int(*lcr_list_all_containers_t)(const char *lcrpath, struct lcr_container_info **info_arr); +typedef void(*lcr_containers_info_free_t)(struct lcr_container_info **info_arr, size_t size); +typedef int(*lcr_stats_all_containers_t)(const char *lcrpath, struct lcr_container_state **state_arr); +typedef bool(*lcr_state_op_t)(const char *name, const char *lcrpath, struct lcr_container_state *lcs); +typedef void(*lcr_container_state_free_t)(struct lcr_container_state *lcs); +typedef bool(*lcr_update_op_t)(const char *name, const char *lcrpath, struct lcr_cgroup_resources *cr); +typedef bool(*lcr_get_console_config_op_t)(const char *name, const char *lcrpath, struct lcr_console_config *config); +typedef void(*lcr_free_console_config_op_t)(struct lcr_console_config *config); +typedef int(*lcr_monitord_spawn_t)(const char *lcrpath); +typedef bool(*lcr_start_op_t)(struct lcr_start_request *request); + +static lcr_list_all_containers_t g_lcr_list_all_containers_op = NULL; +static lcr_containers_info_free_t g_lcr_containers_info_free_op = NULL; +static lcr_state_op_t g_lcr_state_op = NULL; +static lcr_container_state_free_t g_lcr_container_state_free_op = NULL; +static lcr_update_op_t g_lcr_update_op = NULL; +static lcr_get_console_config_op_t g_lcr_get_console_config_op = NULL; +static lcr_free_console_config_op_t g_lcr_free_console_config_op = NULL; +static lcr_start_op_t g_lcr_start_op = NULL; + +/* + * Trans the lcr_state_t to Status + */ +static Engine_Container_Status lcrsta2sta(const char *state) +{ + Engine_Container_Status status = ENGINE_CONTAINER_STATUS_UNKNOWN; + + if (state == NULL) { + WARN("Empty string of state"); + return status; + } + + if (strcmp("STOPPED", state) == 0) { + status = ENGINE_CONTAINER_STATUS_STOPPED; + } else if ((strcmp("STARTING", state) == 0) || (strcmp("STOPPING", state) == 0)) { + status = ENGINE_CONTAINER_STATUS_CREATED; + } else if (strcmp("RUNNING", state) == 0) { + status = ENGINE_CONTAINER_STATUS_RUNNING; + } else if ((strcmp("ABORTING", state) == 0) || (strcmp("FREEZING", state) == 0) || + (strcmp("FROZEN", state) == 0) || (strcmp("THAWED", state) == 0)) { + status = ENGINE_CONTAINER_STATUS_PAUSED; + } else { + DEBUG("invalid state '%s'", state); + status = ENGINE_CONTAINER_STATUS_UNKNOWN; + } + + return status; +} + +/* lcr update container */ +static bool lcr_update_container(const char *name, const char *lcrpath, const struct engine_cgroup_resources *cr) +{ + struct lcr_cgroup_resources lcr_cr; + errno_t ret = EOK; + + if (g_lcr_update_op == NULL) { + ERROR("Not supported update operation"); + return false; + } + + if (cr == NULL) { + ERROR("Empty configs for update"); + return false; + } + + ret = memset_s(&lcr_cr, sizeof(struct lcr_cgroup_resources), 0x00, sizeof(struct lcr_cgroup_resources)); + if (ret != EOK) { + ERROR("Failed to set memory"); + return false; + } + + lcr_cr.blkio_weight = cr->blkio_weight; + lcr_cr.cpu_shares = cr->cpu_shares; + lcr_cr.cpu_period = cr->cpu_period; + lcr_cr.cpu_quota = cr->cpu_quota; + lcr_cr.cpuset_cpus = cr->cpuset_cpus; + lcr_cr.cpuset_mems = cr->cpuset_mems; + lcr_cr.memory_limit = cr->memory_limit; + lcr_cr.memory_swap = cr->memory_swap; + lcr_cr.memory_reservation = cr->memory_reservation; + lcr_cr.kernel_memory_limit = cr->kernel_memory_limit; + + return g_lcr_update_op(name, lcrpath, &lcr_cr); +} + +static bool lcr_start_container(const engine_start_request_t *request) +{ + struct lcr_start_request *lcr_request = (struct lcr_start_request *)request; + + return g_lcr_start_op(lcr_request); +} + +/* free console config */ +void free_console_config(struct engine_console_config *config) +{ + if (config == NULL) { + return; + } + free(config->log_path); + config->log_path = NULL; + + free(config->log_file_size); + config->log_file_size = NULL; + + config->log_rotate = 0; +} + +/* get console config */ +bool get_console_config(const char *name, const char *lcrpath, struct engine_console_config *config) +{ + struct lcr_console_config lcr_config; + bool ret = false; + errno_t mret = EOK; + + if (name == NULL || config == NULL) { + ERROR("Invalid arguments"); + return ret; + } + + mret = memset_s(&lcr_config, sizeof(struct lcr_console_config), 0x00, sizeof(struct lcr_console_config)); + if (mret != EOK) { + ERROR("Failed to set memory"); + return ret; + } + + if (g_lcr_get_console_config_op != NULL) { + ret = g_lcr_get_console_config_op(name, lcrpath, &lcr_config); + } + + if (ret) { + if (lcr_config.log_path) { + config->log_path = util_strdup_s(lcr_config.log_path); + } else { + config->log_path = NULL; + } + config->log_rotate = lcr_config.log_rotate; + if (lcr_config.log_file_size) { + config->log_file_size = util_strdup_s(lcr_config.log_file_size); + } else { + config->log_file_size = NULL; + } + + if (g_lcr_free_console_config_op != NULL) { + g_lcr_free_console_config_op(&lcr_config); + } + } + + return ret; +} + +/* + * Get the containers info by liblcr + */ +static void get_containers_info(int num, const struct lcr_container_info *info_arr, + struct engine_container_summary_info *info) +{ + int i = 0; + const struct lcr_container_info *in = NULL; + char *name = NULL; + + for (i = 0, in = info_arr; i < num; i++, in++) { + name = in->name; + if (name == NULL) { + continue; + } + + info[i].id = util_strdup_s(name); + info[i].has_pid = (-1 == in->init) ? false : true; + info[i].pid = (uint32_t)in->init; + info[i].status = lcrsta2sta(in->state); + } +} + +/* + * Get the state of container from 'lcr_container_state' + */ +static void copy_container_status(const struct lcr_container_state *lcs, struct engine_container_info *status) +{ + const char *defvalue = "-"; + const char *name = NULL; + + if (memset_s(status, sizeof(struct engine_container_info), 0, sizeof(struct engine_container_info)) != EOK) { + WARN("Can not set memory"); + } + + name = lcs->name ? lcs->name : defvalue; + status->id = util_strdup_s(name); + + status->has_pid = (-1 == lcs->init) ? false : true; + status->pid = (uint32_t)lcs->init; + + status->status = lcrsta2sta(lcs->state); + + status->pids_current = lcs->pids_current; + + status->cpu_use_nanos = lcs->cpu_use_nanos; + + status->blkio_read = lcs->io_service_bytes.read; + status->blkio_write = lcs->io_service_bytes.write; + + status->mem_used = lcs->mem_used; + status->mem_limit = lcs->mem_limit; + status->kmem_used = lcs->kmem_used; + status->kmem_limit = lcs->kmem_limit; +} + +/* + * Alloc Memory for containerArray and container + */ +static int service_list_alloc(int num, struct engine_container_summary_info **cons) +{ + if (num <= 0 || cons == NULL) { + return -1; + } + + if ((size_t)num > SIZE_MAX / sizeof(struct engine_container_summary_info)) { + ERROR("Too many engine container summaries!"); + return -1; + } + *cons = util_common_calloc_s((size_t)num * sizeof(struct engine_container_summary_info)); + if ((*cons) == NULL) { + ERROR("Out of memory"); + return -1; + } + + return 0; +} + +/* + * Free the container** containerArray + */ +static void free_all_containers_info(struct engine_container_summary_info *info, int num) +{ + int i = 0; + + if (num <= 0 || info == NULL) { + return; + } + for (i = 0; i < num; i++) { + free(info[i].id); + info[i].id = NULL; + free(info[i].command); + info[i].command = NULL; + free(info[i].image); + info[i].image = NULL; + free(info[i].finishat); + info[i].finishat = NULL; + free(info[i].startat); + info[i].startat = NULL; + } + free(info); +} + +/* get all containers info */ +static int get_all_containers_info(const char *enginepath, struct engine_container_summary_info **cons) +{ + struct lcr_container_info *info_arr = NULL; + int num = 0; + + if (cons == NULL) { + ERROR("Invalid argument"); + return -1; + } + + if (g_lcr_list_all_containers_op == NULL || g_lcr_containers_info_free_op == NULL) { + ERROR("Not supported op"); + num = -1; + goto free_out; + } + + num = g_lcr_list_all_containers_op(enginepath, &info_arr); + if (num <= 0) { + num = 0; /* set to 0 if non were found */ + goto free_out; + } + + if (service_list_alloc(num, cons)) { + g_lcr_containers_info_free_op(&info_arr, (size_t)num); + ERROR("service list alloc failed"); + num = -1; + goto free_out; + } + + get_containers_info(num, info_arr, *cons); + g_lcr_containers_info_free_op(&info_arr, (size_t)num); + +free_out: + return num; +} + +/* get container status */ +static int get_container_status(const char *name, const char *enginepath, struct engine_container_info *status) +{ + struct lcr_container_state lcs = { 0 }; + + if (g_lcr_state_op == NULL || g_lcr_container_state_free_op == NULL) { + ERROR("Not supported op"); + return -1; + } + + if (!g_lcr_state_op(name, enginepath, &lcs)) { + DEBUG("Failed to state for container '%s'", name); + g_lcr_container_state_free_op(&lcs); + return -1; + } + copy_container_status(&lcs, status); + g_lcr_container_state_free_op(&lcs); + return 0; +} + +/* free container status */ +static void free_container_status(struct engine_container_info *status) +{ + if (status == NULL) { + return; + } + + free(status->id); + status->id = NULL; +} + +#define CHECK_ERROR(P) do { \ + if (dlerror() != NULL) { \ + goto badcleanup; \ + } \ + } while (0) + +static bool load_lcr_exec_ops(void *lcr_handler, struct engine_operation *eop) +{ + eop->engine_create_op = dlsym(lcr_handler, "lcr_create"); + if (dlerror() != NULL) { + return false; + } + g_lcr_start_op = dlsym(lcr_handler, "lcr_start"); + if (dlerror() != NULL) { + return false; + } + eop->engine_kill_op = dlsym(lcr_handler, "lcr_kill"); + if (dlerror() != NULL) { + return false; + } + g_lcr_update_op = dlsym(lcr_handler, "lcr_update"); + if (dlerror() != NULL) { + return false; + } + eop->engine_pause_op = dlsym(lcr_handler, "lcr_pause"); + if (dlerror() != NULL) { + return false; + } + eop->engine_resume_op = dlsym(lcr_handler, "lcr_resume"); + if (dlerror() != NULL) { + return false; + } + eop->engine_clean_op = dlsym(lcr_handler, "lcr_clean"); + if (dlerror() != NULL) { + return false; + } + eop->engine_delete_op = dlsym(lcr_handler, "lcr_delete"); + if (dlerror() != NULL) { + return false; + } + eop->engine_exec_op = dlsym(lcr_handler, "lcr_attach"); + if (dlerror() != NULL) { + return false; + } + eop->engine_console_op = dlsym(lcr_handler, "lcr_console"); + if (dlerror() != NULL) { + return false; + } + return true; +} + +static bool load_lcr_info_ops(void *lcr_handler, struct engine_operation *eop) +{ + eop->engine_get_errmsg_op = dlsym(lcr_handler, "lcr_get_errmsg"); + if (dlerror() != NULL) { + return false; + } + eop->engine_clear_errmsg_op = dlsym(lcr_handler, "lcr_free_errmsg"); + if (dlerror() != NULL) { + return false; + } + eop->engine_get_container_pids_op = dlsym(lcr_handler, "lcr_get_container_pids"); + if (dlerror() != NULL) { + return false; + } + g_lcr_get_console_config_op = dlsym(lcr_handler, "lcr_get_console_config"); + if (dlerror() != NULL) { + return false; + } + g_lcr_free_console_config_op = dlsym(lcr_handler, "lcr_free_console_config"); + if (dlerror() != NULL) { + return false; + } + g_lcr_list_all_containers_op = dlsym(lcr_handler, "lcr_list_all_containers"); + if (dlerror() != NULL) { + return false; + } + g_lcr_containers_info_free_op = dlsym(lcr_handler, "lcr_containers_info_free"); + if (dlerror() != NULL) { + return false; + } + g_lcr_state_op = dlsym(lcr_handler, "lcr_state"); + if (dlerror() != NULL) { + return false; + } + g_lcr_container_state_free_op = dlsym(lcr_handler, "lcr_container_state_free"); + if (dlerror() != NULL) { + return false; + } + return true; +} + +/* lcr engine init */ +struct engine_operation *lcr_engine_init() +{ + void *lcr_handler = NULL; + struct engine_operation *eop = NULL; + lcr_handler = dlopen("liblcr.so", RTLD_NOW | RTLD_DEEPBIND); + if (lcr_handler == NULL) { + ERROR("Plugin error: %s", dlerror()); + return NULL; + } + + eop = util_common_calloc_s(sizeof(struct engine_operation)); + if (eop == NULL) { + ERROR("Failed to alloc memeory for engine_operation"); + goto badcleanup; + } + + eop->engine_type = util_strdup_s("lcr"); + + eop->engine_log_init_op = dlsym(lcr_handler, "lcr_log_init"); + if (dlerror() != NULL) { + ERROR("Load lcr log_init operations failed"); + goto badcleanup; + } + + if (!load_lcr_exec_ops(lcr_handler, eop)) { + ERROR("Load lcr exec operations failed"); + goto badcleanup; + } + + if (!load_lcr_info_ops(lcr_handler, eop)) { + ERROR("Load lcr info operations failed"); + goto badcleanup; + } + + eop->engine_get_all_containers_info_op = get_all_containers_info; + eop->engine_free_all_containers_info_op = free_all_containers_info; + eop->engine_get_container_status_op = get_container_status; + eop->engine_free_container_status_op = free_container_status; + eop->engine_update_op = lcr_update_container; + eop->engine_start_op = lcr_start_container; + eop->engine_get_console_config_op = get_console_config; + eop->engine_free_console_config_op = free_console_config; + + goto cleanup; + +badcleanup: + dlclose(lcr_handler); + if (eop != NULL) { + engine_operation_free(eop); + free(eop); + eop = NULL; + } +cleanup: + return eop; +} + diff --git a/src/engines/lcr/lcr_engine.h b/src/engines/lcr/lcr_engine.h new file mode 100644 index 0000000..e1af9da --- /dev/null +++ b/src/engines/lcr/lcr_engine.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container lcr engine definition + ******************************************************************************/ +#ifndef __LCRD_LCR_ENGINE_H +#define __LCRD_LCR_ENGINE_H + +#include "engine.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct engine_operation *lcr_engine_init(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..317d522 --- /dev/null +++ b/src/error.c @@ -0,0 +1,35 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container error functions + ******************************************************************************/ +#include "error.h" +#include "utils.h" + +#define LCRD_ERRMSG_GEN(n, s) { LCRD_##n, s }, +struct lcrd_strerror_tab_t { + lcrd_errno_t errcode; + const char *errmsg; +}; +static const struct lcrd_strerror_tab_t g_lcrd_strerror_tab[] = { + LCRD_ERRNO_MAP(LCRD_ERRMSG_GEN) +}; +#undef LCRD_ERRMSG_GEN + +/* errno to error message */ +const char *errno_to_error_message(lcrd_errno_t err) +{ + if ((size_t)err >= sizeof(g_lcrd_strerror_tab) / sizeof(g_lcrd_strerror_tab[0])) { + return g_lcrd_strerror_tab[LCRD_ERR_UNKNOWN].errmsg; + } + return g_lcrd_strerror_tab[err].errmsg; +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..ec139b2 --- /dev/null +++ b/src/error.h @@ -0,0 +1,76 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container error definition + ******************************************************************************/ +#ifndef __LCRD_ERROR_H_ +#define __LCRD_ERROR_H_ + +#include +#include +#include "utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DEF_SUCCESS_STR "Success" +#define DEF_ERR_RUNTIME_STR "Runtime error" + +#define LCRD_ERRNO_MAP(XX) \ + XX(SUCCESS, DEF_SUCCESS_STR) \ + \ + /* err in posix api call */ \ + XX(ERR_MEMOUT, "Out of memory") \ + XX(ERR_MEMSET, "Memory set error") \ + \ + /* err in other case or call function int thirdparty library */ \ + XX(ERR_FORMAT, "Error message is too long") \ + XX(ERR_INPUT, "Invalid input parameter") \ + XX(ERR_EXEC, "Execute operation failed") \ + XX(ERR_INTERNAL, "Server internal error") \ + XX(ERR_CONNECT, "Can not connect with server.Is the iSulad daemon running on the host?") \ + \ + /* err in runtime module */ \ + XX(ERR_RUNTIME, DEF_ERR_RUNTIME_STR) \ + \ + /* err max */ \ + XX(ERR_UNKNOWN, "Unknown error") + +#define LCRD_ERRNO_GEN(n, s) LCRD_##n, +typedef enum { LCRD_ERRNO_MAP(LCRD_ERRNO_GEN) } lcrd_errno_t; +#undef LCRD_ERRNO_GEN + +const char *errno_to_error_message(lcrd_errno_t err); + +static inline void format_errorf(char **err, const char *format, ...) +{ + int ret = 0; + char errbuf[BUFSIZ + 1] = { 0 }; + + va_list argp; + va_start(argp, format); + + ret = vsprintf_s(errbuf, BUFSIZ, format, argp); + va_end(argp); + if (ret < 0) { + *err = util_strdup_s("Error is too long!!!"); + return; + } + + *err = util_strdup_s(errbuf); +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/filters.c b/src/filters.c new file mode 100644 index 0000000..734d3fe --- /dev/null +++ b/src/filters.c @@ -0,0 +1,326 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-07 + * Description: provide filters functions + ******************************************************************************/ +#include "filters.h" +#include +#include +#include "log.h" +#include "utils.h" + + +static void filters_args_fields_free(void *key, void *val) +{ + free(key); + map_free(val); +} + +struct filters_args *filters_args_new(void) +{ + struct filters_args *filters = NULL; + + filters = util_common_calloc_s(sizeof(struct filters_args)); + if (filters == NULL) { + ERROR("Out of memory"); + return NULL; + } + // value of fields is a map of map[string][bool] + filters->fields = map_new(MAP_STR_PTR, MAP_DEFAULT_CMP_FUNC, filters_args_fields_free); + if (filters->fields == NULL) { + free(filters); + ERROR("Out of memory"); + return NULL; + } + return filters; +} + +// Free filters +void filters_args_free(struct filters_args *filters) +{ + if (filters == NULL) { + return; + } + + map_free(filters->fields); + filters->fields = NULL; + free(filters); +} + +char **filters_args_get(const struct filters_args *filters, const char *field) +{ + char **slice = NULL; + map_t *field_values_map = NULL; + map_itor *itor = NULL; + + if (filters == NULL || filters->fields == NULL) { + return NULL; + } + + field_values_map = map_search(filters->fields, (void *)field); + if (field_values_map == NULL || map_size(field_values_map) == 0) { + return NULL; + } + + itor = map_itor_new(field_values_map); + if (itor == NULL) { + ERROR("Out of memory"); + return NULL; + } + + for (; map_itor_valid(itor); map_itor_next(itor)) { + int aret; + aret = util_array_append(&slice, map_itor_key(itor)); + if (aret != 0) { + ERROR("Out of memory"); + util_free_array(slice); + map_itor_free(itor); + return NULL; + } + } + map_itor_free(itor); + return slice; +} + +// Add a new value to a filter field. +bool filters_args_add(struct filters_args *filters, const char *name, + const char *value) +{ + bool default_value = true; + map_t *map_str_bool = NULL; + + if (filters == NULL || filters->fields == NULL) { + return false; + } + + map_str_bool = map_search(filters->fields, (void *)name); + if (map_str_bool == NULL) { + map_str_bool = map_new(MAP_STR_BOOL, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC); + if (map_str_bool == NULL) { + ERROR("Out of memory"); + return false; + } + if (!map_replace(filters->fields, (void *)name, (void *)map_str_bool)) { + ERROR("Failed to insert name: %s", name); + map_free(map_str_bool); + return false; + } + } + if (!map_replace(map_str_bool, (void *)value, (void *)(&default_value))) { + ERROR("Failed to insert value: %s", value); + return false; + } + + return true; +} + +// Remove a value from a filter field. +bool filters_args_del(struct filters_args *filters, const char *name, + const char *value) +{ + map_t *map_str_bool = NULL; + + if (filters == NULL || filters->fields == NULL) { + return false; + } + + map_str_bool = map_search(filters->fields, (void *)name); + if (map_str_bool != NULL) { + if (!map_remove(map_str_bool, (void *)value)) { + ERROR("Failed to remove value %s from name %s", value, name); + return false; + } + } + return true; +} + +size_t filters_args_len(const struct filters_args *filters) +{ + if (filters == NULL || filters->fields == NULL) { + return 0; + } + return map_size(filters->fields); +} + +static bool do_filters_args_match_kv_list(const map_t *field_values_map, const map_t *sources) +{ + size_t size, i; + bool bret = false; + map_itor *itor = NULL; + + size = map_size(field_values_map); + itor = map_itor_new(field_values_map); + if (itor == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + for (i = 0; map_itor_valid(itor) && i < size; map_itor_next(itor), i++) { + const char *name2match = NULL; + const char *sources_value = NULL; + char **test_kv = NULL; + char *copy = NULL; + char *pos = NULL; + int aret = 0; + + name2match = map_itor_key(itor); + copy = util_strdup_s(name2match); + // Splitted by '=' to at most 2 substring, better to implement util_string_split_n + pos = strchr(copy, '='); + if (pos == NULL) { + aret = util_array_append(&test_kv, copy); + free(copy); + if (aret != 0) { + ERROR("Out of memory"); + util_free_array(test_kv); + goto cleanup; + } + } else { + *pos++ = '\0'; + aret = util_array_append(&test_kv, copy); + if (aret != 0) { + ERROR("Out of memory"); + free(copy); + util_free_array(test_kv); + goto cleanup; + } + aret = util_array_append(&test_kv, pos); + free(copy); + if (aret != 0) { + ERROR("Out of memory"); + util_free_array(test_kv); + goto cleanup; + } + } + + if (test_kv == NULL) { + ERROR("Out of memory"); + util_free_array(test_kv); + goto cleanup; + } + + sources_value = map_search(sources, (void *)test_kv[0]); + if (sources_value == NULL) { + util_free_array(test_kv); + goto cleanup; + } + + if (util_array_len(test_kv) == 2 && strcmp(test_kv[1], sources_value) != 0) { + util_free_array(test_kv); + goto cleanup; + } + util_free_array(test_kv); + } + bret = true; +cleanup: + map_itor_free(itor); + return bret; +} + +/* check if a field is match or not + * filters_args are {'label': {'label1=1','label2=2'}, 'image.name', {'busybox'}}, + * field is 'label' and sources are {'label1': '1', 'label2': '2'} + * it returns true. + */ +bool filters_args_match_kv_list(const struct filters_args *filters, const char *field, const map_t *sources) +{ + map_t *field_values_map = NULL; + + // Do not filter if there is no filter set or cannot determine filter + if (filters == NULL || filters->fields == NULL) { + return true; + } + + field_values_map = map_search(filters->fields, (void *)field); + if (field_values_map == NULL || map_size(field_values_map) == 0) { + return true; + } + + if (sources == NULL || map_size(sources) == 0) { + return false; + } + + if (sources->type != MAP_STR_STR) { + ERROR("Input arg is not valid map[string][string]"); + return false; + } + + return do_filters_args_match_kv_list(field_values_map, sources); +} + +bool filters_args_exact_match(const struct filters_args *filters, const char *field, const char *source) +{ + map_t *field_values_map = NULL; + + // Do not filter if there is no filter set or cannot determine filter + if (filters == NULL || filters->fields == NULL) { + return true; + } + + field_values_map = map_search(filters->fields, (void *)field); + if (field_values_map == NULL || map_size(field_values_map) == 0) { + return true; + } + + // try to march full name value to avoid O(N) regular expression matching + if (map_search(field_values_map, (void *)source) != NULL) { + return true; + } + + return false; +} + +bool filters_args_match(const struct filters_args *filters, const char *field, const char *source) +{ + map_t *field_values_map = NULL; + map_itor *itor = NULL; + + if (filters_args_exact_match(filters, field, source)) { + return true; + } + + field_values_map = map_search(filters->fields, (void *)field); + itor = map_itor_new(field_values_map); + if (itor == NULL) { + ERROR("Out of memory"); + return false; + } + + for (; map_itor_valid(itor); map_itor_next(itor)) { + int mret; + const char *name2match = map_itor_key(itor); + mret = util_reg_match(name2match, source); + if (mret != 0) { + continue; + } + map_itor_free(itor); + return true; + } + map_itor_free(itor); + return false; +} + +/* check whether field is one of accepted name or not*/ +bool filters_args_valid_key(const char **accepted, size_t len, const char *field) +{ + size_t i; + + if (field == NULL) { + return false; + } + for (i = 0; i < len; i++) { + if (accepted[i] != NULL && strcmp(accepted[i], field) == 0) { + return true; + } + } + return false; +} + diff --git a/src/filters.h b/src/filters.h new file mode 100644 index 0000000..df52317 --- /dev/null +++ b/src/filters.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-07 + * Description: provide filters function definition + ******************************************************************************/ +#ifndef __FILTERS_DEF_H_ +#define __FILTERS_DEF_H_ + +#include "map.h" + +struct filters_args { + // A map of map[string][map[string][bool]] + map_t *fields; +}; + +struct filters_args *filters_args_new(void); + +void filters_args_free(struct filters_args *filters); + +char **filters_args_get(const struct filters_args *filters, const char *field); + +bool filters_args_add(struct filters_args *filters, const char *name, + const char *value); + +bool filters_args_del(struct filters_args *filters, const char *name, + const char *value); + +size_t filters_args_len(const struct filters_args *filters); + +bool filters_args_valid_key(const char **accepted, size_t len, const char *field); + +bool filters_args_match_kv_list(const struct filters_args *filters, const char *field, + const map_t *sources); + +bool filters_args_exact_match(const struct filters_args *filters, const char *field, const char *source); + +bool filters_args_match(const struct filters_args *filters, const char *field, const char *source); + +#endif diff --git a/src/http/CMakeLists.txt b/src/http/CMakeLists.txt new file mode 100644 index 0000000..d495956 --- /dev/null +++ b/src/http/CMakeLists.txt @@ -0,0 +1,26 @@ +# set sources and headers for libhttpclient +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} http_client_srcs) + +if (NOT OPENSSL_VERIFY) + list(REMOVE_ITEM http_client_srcs "${CMAKE_CURRENT_SOURCE_DIR}/certificate.c") +endif() + +add_library(libhttpclient ${LIBTYPE} ${http_client_srcs}) + +target_include_directories(libhttpclient PUBLIC + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/cutils + ${CMAKE_SOURCE_DIR}/src/http + ${CHECKED_INCLUDE_DIRS} + ) + +# set libhttpclient FLAGS +set_target_properties(libhttpclient PROPERTIES PREFIX "") +target_link_libraries(libhttpclient ${HTTP_PARSER_LIBRARY} ${CURL_LIBRARY} ${LIBSECUREC_LIBRARY}) + +if (ISULAD_GCOV) + target_link_libraries(libhttpclient -lgcov) +endif() + +install(TARGETS libhttpclient + LIBRARY DESTINATION ${LIB_INSTALL_DIR_DEFAULT} PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE) diff --git a/src/http/buffer.c b/src/http/buffer.c new file mode 100644 index 0000000..f876bd5 --- /dev/null +++ b/src/http/buffer.c @@ -0,0 +1,175 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container buffer functions + ******************************************************************************/ +#define _GNU_SOURCE +#include "buffer.h" +#include + +#include "log.h" +#include "utils.h" + +/* buffer alloc */ +Buffer *buffer_alloc(size_t initial_size) +{ + Buffer *buf = NULL; + char *tmp = NULL; + + if (initial_size == 0) { + return NULL; + } + + buf = util_common_calloc_s(sizeof(Buffer)); + if (buf == NULL) { + return NULL; + } + + if (initial_size > SIZE_MAX / sizeof(char)) { + free(buf); + return NULL; + } + tmp = calloc(1, initial_size * sizeof(char)); + if (tmp == NULL) { + free(buf); + return NULL; + } + + buf->contents = tmp; + buf->bytes_used = 0; + buf->total_size = initial_size; + + return buf; +} + +/* buffer strlen */ +size_t buffer_strlen(const Buffer *buf) +{ + return buf == NULL ? 0 : buf->bytes_used; +} + +/* buffer free */ +void buffer_free(Buffer *buf) +{ + if (buf == NULL) { + return; + } + free(buf->contents); + buf->contents = NULL; + free(buf); +} + +/* buffer empty */ +void buffer_empty(Buffer *buf) +{ + errno_t ret = EOK; + + if (buf == NULL) { + return; + } + ret = memset_s(buf->contents, buf->total_size, 0x00, buf->total_size); + if (ret != EOK) { + ERROR("Failed to set memory"); + return; + } + + buf->bytes_used = 0; +} + +/* buffer grow */ +int buffer_grow(Buffer *buffer, size_t min_size) +{ + size_t factor = 0; + int ret; + size_t new_size = 0; + errno_t mret = EOK; + char *tmp = NULL; + + if (buffer == NULL) { + return -1; + } + + factor = buffer->total_size; + if (factor < min_size) { + factor = min_size; + } + if (factor > SIZE_MAX / 2) { + return -1; + } + new_size = factor * 2; + if (new_size == 0) { + return -1; + } + + tmp = util_common_calloc_s(new_size); + if (tmp == NULL) { + ERROR("Out of memory"); + return -1; + } + + ret = memcpy_s(tmp, new_size, buffer->contents, buffer->total_size); + if (ret) { + ERROR("Failed to copy memory"); + free(tmp); + return -1; + } + + mret = memset_s(buffer->contents, buffer->total_size, 0, buffer->total_size); + if (mret != EOK) { + ERROR("Failed to set memory"); + free(tmp); + return -1; + } + + free(buffer->contents); + buffer->contents = tmp; + buffer->total_size = new_size; + + return 0; +} + +/* buffer append */ +int buffer_append(Buffer *buf, const char *append, size_t len) +{ + size_t desired_length = 0; + size_t i = 0; + size_t bytes_copy = 0; + + if (buf == NULL) { + return -1; + } + + desired_length = len + 1; + if ((buf->total_size - buf->bytes_used) < desired_length) { + int status = buffer_grow(buf, desired_length); + if (status != 0) { + return -1; + } + } + + for (i = 0; i < len; i++) { + if (append[i] == '\0') { + break; + } + + size_t pos = buf->bytes_used + i; + *(buf->contents + pos) = append[i]; + + bytes_copy++; + } + + buf->bytes_used += bytes_copy; + /* string end */ + *(buf->contents + buf->bytes_used) = '\0'; + + return 0; +} diff --git a/src/http/buffer.h b/src/http/buffer.h new file mode 100644 index 0000000..ad36709 --- /dev/null +++ b/src/http/buffer.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container buffer definition + ******************************************************************************/ +#ifndef LCRD_BUFFER_H +#define LCRD_BUFFER_H + +#include +#include +#include + +struct Buffer { + char *contents; + size_t bytes_used; + size_t total_size; +}; + +typedef struct Buffer Buffer; + +Buffer *buffer_alloc(size_t initial_size); +size_t buffer_strlen(const Buffer *buf); +void buffer_free(Buffer *buf); +int buffer_append(Buffer *buf, const char *append, size_t len); +void buffer_empty(Buffer *buf); +#endif diff --git a/src/http/certificate.c b/src/http/certificate.c new file mode 100644 index 0000000..c756913 --- /dev/null +++ b/src/http/certificate.c @@ -0,0 +1,62 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2019-06-07 + * Description: provide certificate function + ******************************************************************************/ +#include "certificate.h" +#include +#include + +#include "log.h" +#include "utils.h" +#include "securec.h" + +int get_common_name_from_tls_cert(const char *cert_path, char *value, size_t len) +{ + int ret = 0; + X509 *cert = NULL; + X509_NAME *subject_name = NULL; + FILE *fp = NULL; + + if (cert_path == NULL || strlen(cert_path) == 0) { + return 0; + } + + fp = util_fopen(cert_path, "r"); + if (fp == NULL) { + ERROR("Failed to open cert file: %s", cert_path); + return -1; + } + cert = PEM_read_X509(fp, NULL, NULL, NULL); + if (cert == NULL) { + ERROR("Failed to parse cert in: %s", cert_path); + ret = -1; + goto out; + } + subject_name = X509_get_subject_name(cert); + if (subject_name == NULL) { + ERROR("Failed to get subject name in: %s\n", cert_path); + ret = -1; + goto out; + } + if (X509_NAME_get_text_by_NID(subject_name, NID_commonName, value, (int)len) < 0) { + ret = -1; + goto out; + } + +out: + if (cert != NULL) { + X509_free(cert); + } + fclose(fp); + return ret; +} diff --git a/src/http/certificate.h b/src/http/certificate.h new file mode 100644 index 0000000..e244d2a --- /dev/null +++ b/src/http/certificate.h @@ -0,0 +1,29 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2019-06-07 + * Description: provide certificate function + ******************************************************************************/ +#ifndef _ISULAD_HTTP_CERTIFICATE_H +#define _ISULAD_HTTP_CERTIFICATE_H +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int get_common_name_from_tls_cert(const char *cert_path, char *value, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/http/http.c b/src/http/http.c new file mode 100644 index 0000000..d707dfc --- /dev/null +++ b/src/http/http.c @@ -0,0 +1,413 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container http function + ******************************************************************************/ +#include +#include +#include + +#include "http.h" +#include "buffer.h" +#include "log.h" +#include "utils.h" +#include "securec.h" + +size_t fwrite_buffer(const char *ptr, size_t eltsize, size_t nmemb, void *buffer_) +{ + size_t size = eltsize * nmemb; + struct Buffer *buffer = buffer_; + int status = 0; + + status = buffer_append(buffer, ptr, size); + if (status != 0) { + ERROR("Failed to write buffer\n"); + } + return size; +} + +size_t fwrite_file(const void *ptr, size_t size, size_t nmemb, void *stream) +{ + size_t written = fwrite(ptr, size, nmemb, (FILE *)stream); + return written; +} + +size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf) +{ + return eltsize * nmemb; +} + +void free_http_get_options(struct http_get_options *options) +{ + if (options == NULL) { + return; + } + free(options->accepts); + options->accepts = NULL; + + free(options->authorization); + options->authorization = NULL; + + free(options->unix_socket_path); + options->unix_socket_path = NULL; + + /* The options->output is a FILE pointer, we should not free it here */ + free(options); + return; +} + +void http_global_init(void) +{ + curl_global_init(CURL_GLOBAL_NOTHING); +} + +void http_global_cleanup(void) +{ + curl_global_cleanup(); +} + +struct curl_slist *http_get_chunk_header(const struct http_get_options *options) +{ + int ret = 0; + int nret; + size_t len = 0; + struct curl_slist *chunk = NULL; + char *header = NULL; + + if (options->with_header_auth && options->authorization) { + if (strlen(options->authorization) > (SIZE_MAX - strlen("Authorization: ")) - 1) { + ERROR("Invalid authorization option"); + ret = -1; + goto out; + } + len = strlen(options->authorization) + strlen("Authorization: ") + 1; + header = util_common_calloc_s(len); + if (header == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + nret = sprintf_s(header, len, "Authorization: %s", options->authorization); + if (nret < 0) { + ERROR("Failed to print string"); + ret = -1; + goto out; + } + chunk = curl_slist_append(chunk, header); + free(header); + header = NULL; + } + + if (options->with_header_json) { + chunk = curl_slist_append(chunk, "Content-Type: application/json"); + // Disable "Expect: 100-continue" + chunk = curl_slist_append(chunk, "Expect:"); + } + + if (options->with_header_accept && options->accepts) { + if (strlen(options->accepts) > (SIZE_MAX - strlen("Accept: ")) - 1) { + ERROR("Invalid accepts option"); + ret = -1; + goto out; + } + len = strlen(options->accepts) + strlen("Accept: ") + 1; + header = util_common_calloc_s(len); + if (header == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + nret = sprintf_s(header, len, "Accept: %s", options->accepts); + if (nret < 0) { + ERROR("Failed to print string"); + ret = -1; + goto out; + } + chunk = curl_slist_append(chunk, header); + free(header); + header = NULL; + } +out: + if (ret) { + curl_slist_free_all(chunk); + chunk = NULL; + } + free(header); + + return chunk; +} + +static int http_custom_options(CURL *curl_handle, const struct http_get_options *options) +{ + int ret = 0; + + if (curl_handle == NULL || options == NULL) { + return -1; + } + + if (options->unix_socket_path) { + curl_easy_setopt(curl_handle, CURLOPT_UNIX_SOCKET_PATH, options->unix_socket_path); + } + + if (options->with_head) { + curl_easy_setopt(curl_handle, CURLOPT_HEADER, 1L); + } + + if (options->with_body == 0) { + curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1L); + } + + /* disable progress meter, set to 0L to enable and disable debug output */ + if (options->show_progress == 0) { + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1L); + } else if (options->show_progress && options->progressinfo && options->progress_info_op) { + curl_easy_setopt(curl_handle, CURLOPT_PROGRESSFUNCTION, options->progress_info_op); + /* pass the struct pointer into the progress function */ + curl_easy_setopt(curl_handle, CURLOPT_PROGRESSDATA, options->progressinfo); + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 0L); + } else { + curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 0L); + } + + if (options->input) { + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, options->input); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, options->input_len); + curl_easy_setopt(curl_handle, CURLOPT_POST, 1L); + } + + return ret; +} + +static void close_file(FILE *pagefile) +{ + if (pagefile != NULL) { + fclose(pagefile); + } +} + +static void free_rpath(char *rpath) +{ + free(rpath); +} + +static void check_buf_len(const char *errbuf) +{ + size_t len = 0; + len = strlen(errbuf); + if (len > 0) { + fprintf(stderr, "%s%s", errbuf, ((errbuf[len - 1] != '\n') ? "\n" : "")); + } +} + +static void buffer_empty_on_condition(struct http_get_options *options) +{ + if (options == NULL) { + return; + } + if (options->output && options->outputtype == HTTP_REQUEST_STRBUF) { + buffer_empty(options->output); + } + + if (options->with_header_auth && options->authorization) { + options->with_header_auth = 0; + } +} + +static void curl_getinfo_on_condition(long *response_code, CURL *curl_handle, char **tmp) +{ + if (response_code != NULL) { + curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, response_code); + } + curl_easy_getinfo(curl_handle, CURLINFO_REDIRECT_URL, tmp); +} + +static int ensure_path_file(char **rpath, const struct http_get_options *options, FILE **pagefile) +{ + if (util_ensure_path(rpath, options->output)) { + return -1; + } + *pagefile = util_fopen(*rpath, "w+"); + if (*pagefile == NULL) { + ERROR("Failed to open file %s\n", options->output); + return -1; + } + return 0; +} + +static struct curl_slist *set_custom_header(CURL *curl_handle, const struct http_get_options *options) +{ + struct curl_slist *chunk = NULL; + chunk = http_get_chunk_header(options); + if (chunk) { + /* set our custom set of headers */ + curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, chunk); + } + return chunk; +} + +int http_request(const char *url, struct http_get_options *options, long *response_code, int recursive_len) +{ +#define MAX_REDIRCT_NUMS 32 + CURL *curl_handle = NULL; + CURLcode curl_result = CURLE_OK; + struct curl_slist *chunk = NULL; + FILE *pagefile = NULL; + char *rpath = NULL; + int ret = 0; + char errbuf[CURL_ERROR_SIZE] = { 0 }; + bool strbuf_args; + bool file_args; + char *redir_url = NULL; + char *tmp = NULL; + + if (recursive_len + 1 >= MAX_REDIRCT_NUMS) { + ERROR("reach the max redirect num"); + return -1; + } + http_global_init(); + /* init the curl session */ + curl_handle = curl_easy_init(); + if (curl_handle == NULL) { + return -1; + } + + /* set URL to get here */ + curl_easy_setopt(curl_handle, CURLOPT_URL, url); + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1L); + /* complete connection within 5 seconds */ + curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 5L); + /* provide a buffer to store errors in */ + curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(curl_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0); + + ret = http_custom_options(curl_handle, options); + if (ret) { + goto out; + } + chunk = set_custom_header(curl_handle, options); + + strbuf_args = options->output && options->outputtype == HTTP_REQUEST_STRBUF; + file_args = options->output && options->outputtype == HTTP_REQUEST_FILE; + if (strbuf_args) { + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, options->output); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, fwrite_buffer); + } else if (file_args) { + /* open the file */ + if (ensure_path_file(&rpath, options, &pagefile) == -1) { + goto out; + } + curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, pagefile); + curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, fwrite_file); + } else { + /* do nothing */ + } + + /* get it! */ + curl_result = curl_easy_perform(curl_handle); + + if (curl_result != CURLE_OK) { + check_buf_len(errbuf); + ret = -1; + } else { + curl_getinfo_on_condition(response_code, curl_handle, &tmp); + if (tmp) { + redir_url = util_strdup_s(tmp); + } + } + +out: + close_file(pagefile); + free_rpath(rpath); + + /* cleanup curl stuff */ + curl_easy_cleanup(curl_handle); + curl_slist_free_all(chunk); + + if (redir_url) { + buffer_empty_on_condition(options); + + if (http_request(redir_url, options, response_code, recursive_len + 1)) { + ERROR("Failed to get http request\n"); + ret = -1; + } + free(redir_url); + } + + return ret; +} + +int authz_http_request(const char *username, const char *action, char **resp) +{ + char *request_body = NULL; + char err_msg[AUTHZ_ERROR_MSG_SIZE] = { 0 }; + long response_code = 0; + int ret = 0; + size_t length = 0; + struct http_get_options *options = NULL; + if (strlen(username) > ((SIZE_MAX - strlen(action)) - strlen(":")) - 1) { + ERROR("Invalid arguments"); + return -1; + } + length = strlen(username) + strlen(":") + strlen(action) + 1; + request_body = util_common_calloc_s(length); + if (request_body == NULL) { + ERROR("Out of memory"); + *resp = util_strdup_s("Inernal server error: Out of memory"); + return -1; + } + if (sprintf_s(request_body, length, "%s:%s", username, action) < 0) { + ERROR("Failed to print string"); + free(request_body); + return -1; + } + options = util_common_calloc_s(sizeof(struct http_get_options)); + if (options == NULL) { + ERROR("Failed to malloc http_get_options"); + *resp = util_strdup_s("Inernal server error: Out of memory"); + free(request_body); + return -1; + } + + options->with_head = 1; + options->with_header_json = 1; + options->input = request_body; + options->input_len = strlen(request_body); + options->unix_socket_path = util_strdup_s(AUTHZ_UNIX_SOCK); + + ret = http_request(AUTHZ_REQUEST_URL, options, &response_code, 0); + if (ret != 0) { + ERROR("Failed to request authz plugin. Is server running ?"); + *resp = util_strdup_s("Failed to request authz plugin. Is server running ?"); + ret = -1; + goto out; + } + if (response_code != StatusOK) { + ret = sprintf_s(err_msg, sizeof(err_msg), "action '%s' for user '%s': permission denied", action, username); + if (ret < 0) { + ERROR("Out of memory"); + *resp = util_strdup_s("Inernal server error: Out of memory"); + goto out; + } + *resp = util_strdup_s(err_msg); + ret = -1; + goto out; + } + +out: + free(request_body); + if (options != NULL) { + free(options->unix_socket_path); + free(options); + } + return ret; +} diff --git a/src/http/http.h b/src/http/http.h new file mode 100644 index 0000000..5e2fe81 --- /dev/null +++ b/src/http/http.h @@ -0,0 +1,171 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container http function definition + ******************************************************************************/ +#ifndef LCRD_HTTP_H +#define LCRD_HTTP_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int(*progress_info_func)(void *p, + double dltotal, double dlnow, + double ultotal, double ulnow); + +struct http_get_options { + unsigned with_head : 1, /*if set, means write output with response HEADER*/ + with_body : 1, /*if set, means write output with response BODY*/ + /*if set, means set request with "Authorization:(char *)authorization"*/ + with_header_auth : 1, + /*if set, means set requst with "Content-Type: application/json"*/ + with_header_json : 1, + /*if set, means set request with "Accept:(char *)accepts"*/ + with_header_accept : 1, + /*if set, means show the process progress"*/ + show_progress : 1; + + /* + *#define HTTP_REQUEST_STRBUF 0 + *#define HTTP_REQUEST_FILE 1 + */ + char outputtype; + + /* if set, means connnect to unix socket*/ + char *unix_socket_path; + + /* + * if outputtype is HTTP_REQUEST_STRBUF, the output is a pointer to struct Buffer + * if outputtype is HTTP_REQUEST_FILE, the output is a pointer to a file name + */ + void *output; + + /*http method PUT GET POST*/ + void *method; + /*body to be sent to server*/ + void *input; + size_t input_len; + + char *authorization; + + char *accepts; + + void *progressinfo; + progress_info_func progress_info_op; +}; + +#define HTTP_RES_OK 0 +#define HTTP_RES_MISSING_TARGET 1 +#define HTTP_RES_ERROR 2 +#define HTTP_RES_START_FAILED 3 +#define HTTP_RES_REAUTH 4 +#define HTTP_RES_NOAUTH 5 + +/*HTTP Get buffer size*/ +#define HTTP_GET_BUFFER_SIZE 1024 + +/*authz error msg size*/ +#define AUTHZ_ERROR_MSG_SIZE 256 + +/* http_request() targets */ +#define HTTP_REQUEST_STRBUF 0 +#define HTTP_REQUEST_FILE 1 + +/* authz unix sock and request url*/ +#define AUTHZ_UNIX_SOCK "/run/docker/plugins/authz-broker.sock" +#define AUTHZ_REQUEST_URL "http://localhost/isulad.auth" + +/* http response code*/ +enum http_response_code { + StatusContinue = 100, // RFC 7231, 6.2.1 + StatusSwitchingProtocols = 101, // RFC 7231, 6.2.2 + StatusProcessing = 102, // RFC 2518, 10.1 + + StatusOK = 200, // RFC 7231, 6.3.1 + StatusCreated = 201, // RFC 7231, 6.3.2 + StatusAccepted = 202, // RFC 7231, 6.3.3 + StatusNonAuthoritativeInfo = 203, // RFC 7231, 6.3.4 + StatusNoContent = 204, // RFC 7231, 6.3.5 + StatusResetContent = 205, // RFC 7231, 6.3.6 + StatusPartialContent = 206, // RFC 7233, 4.1 + StatusMultiStatus = 207, // RFC 4918, 11.1 + StatusAlreadyReported = 208, // RFC 5842, 7.1 + StatusIMUsed = 226, // RFC 3229, 10.4.1 + + StatusMultipleChoices = 300, // RFC 7231, 6.4.1 + StatusMovedPermanently = 301, // RFC 7231, 6.4.2 + StatusFound = 302, // RFC 7231, 6.4.3 + StatusSeeOther = 303, // RFC 7231, 6.4.4 + StatusNotModified = 304, // RFC 7232, 4.1 + StatusUseProxy = 305, // RFC 7231, 6.4.5 + _ = 306, // RFC 7231, 6.4.6 (Unused) + StatusTemporaryRedirect = 307, // RFC 7231, 6.4.7 + StatusPermanentRedirect = 308, // RFC 7538, 3 + + StatusBadRequest = 400, // RFC 7231, 6.5.1 + StatusUnauthorized = 401, // RFC 7235, 3.1 + StatusPaymentRequired = 402, // RFC 7231, 6.5.2 + StatusForbidden = 403, // RFC 7231, 6.5.3 + StatusNotFound = 404, // RFC 7231, 6.5.4 + StatusMethodNotAllowed = 405, // RFC 7231, 6.5.5 + StatusNotAcceptable = 406, // RFC 7231, 6.5.6 + StatusProxyAuthRequired = 407, // RFC 7235, 3.2 + StatusRequestTimeout = 408, // RFC 7231, 6.5.7 + StatusConflict = 409, // RFC 7231, 6.5.8 + StatusGone = 410, // RFC 7231, 6.5.9 + StatusLengthRequired = 411, // RFC 7231, 6.5.10 + StatusPreconditionFailed = 412, // RFC 7232, 4.2 + StatusRequestEntityTooLarge = 413, // RFC 7231, 6.5.11 + StatusRequestURITooLong = 414, // RFC 7231, 6.5.12 + StatusUnsupportedMediaType = 415, // RFC 7231, 6.5.13 + StatusRequestedRangeNotSatisfiable = 416, // RFC 7233, 4.4 + StatusExpectationFailed = 417, // RFC 7231, 6.5.14 + StatusTeapot = 418, // RFC 7168, 2.3.3 + StatusUnprocessableEntity = 422, // RFC 4918, 11.2 + StatusLocked = 423, // RFC 4918, 11.3 + StatusFailedDependency = 424, // RFC 4918, 11.4 + StatusUpgradeRequired = 426, // RFC 7231, 6.5.15 + StatusPreconditionRequired = 428, // RFC 6585, 3 + StatusTooManyRequests = 429, // RFC 6585, 4 + StatusRequestHeaderFieldsTooLarge = 431, // RFC 6585, 5 + StatusUnavailableForLegalReasons = 451, // RFC 7725, 3 + + StatusInternalServerError = 500, // RFC 7231, 6.6.1 + StatusNotImplemented = 501, // RFC 7231, 6.6.2 + StatusBadGateway = 502, // RFC 7231, 6.6.3 + StatusServiceUnavailable = 503, // RFC 7231, 6.6.4 + StatusGatewayTimeout = 504, // RFC 7231, 6.6.5 + StatusHTTPVersionNotSupported = 505, // RFC 7231, 6.6.6 + StatusVariantAlsoNegotiates = 506, // RFC 2295, 8.1 + StatusInsufficientStorage = 507, // RFC 4918, 11.5 + StatusLoopDetected = 508, // RFC 5842, 7.2 + StatusNotExtended = 510, // RFC 2774, 7 + StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 +}; + +void free_http_get_options(struct http_get_options *options); + +int http_request(const char *url, struct http_get_options *options, + long *response_code, int recursive_len); + +int authz_http_request(const char *username, const char *action, char **resp); + +void http_global_init(void); + +void http_global_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/http/mediatype.h b/src/http/mediatype.h new file mode 100644 index 0000000..550dbee --- /dev/null +++ b/src/http/mediatype.h @@ -0,0 +1,77 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container mediatype definition + ******************************************************************************/ +#ifndef _MEDIATYPE_H +#define _MEDIATYPE_H + +#define MediaTypeDockerSchema2Layer "application/vnd.docker.image.rootfs.diff.tar" +#define MediaTypeDockerSchema2LayerGzip "application/vnd.docker.image.rootfs.diff.tar.gzip" +#define MediaTypeDockerSchema2Config "application/vnd.docker.container.image.v1+json" +#define MediaTypeDockerSchema2Manifest "application/vnd.docker.distribution.manifest.v2+json" +#define MediaTypeDockerSchema2ManifestList "application/vnd.docker.distribution.manifest.list.v2+json" +// Checkpoint/Restore Media Types +#define MediaTypeContainerd1Checkpoint "application/vnd.containerd.container.criu.checkpoint.criu.tar" +#define MediaTypeContainerd1CheckpointPreDump "application/vnd.containerd.container.criu.checkpoint.predump.tar" +#define MediaTypeContainerd1Resource "application/vnd.containerd.container.resource.tar" +#define MediaTypeContainerd1RW "application/vnd.containerd.container.rw.tar" +#define MediaTypeContainerd1CheckpointConfig "application/vnd.containerd.container.checkpoint.config.v1+proto" +// Legacy Docker schema1 manifest +#define MediaTypeDockerSchema1Manifest "application/vnd.docker.distribution.manifest.v1+prettyjws" + +// MediaTypeDescriptor specifies the media type for a content descriptor. +#define MediaTypeDescriptor "application/vnd.oci.descriptor.v1+json" + +// MediaTypeLayoutHeader specifies the media type for the oci-layout. +#define MediaTypeLayoutHeader "application/vnd.oci.layout.header.v1+json" + +// MediaTypeImageManifest specifies the media type for an image manifest. +#define MediaTypeImageManifest "application/vnd.oci.image.manifest.v1+json" + +// MediaTypeImageIndex specifies the media type for an image index. +#define MediaTypeImageIndex "application/vnd.oci.image.index.v1+json" + +// MediaTypeImageLayer is the media type used for layers referenced by the manifest. +#define MediaTypeImageLayer "application/vnd.oci.image.layer.v1.tar" + +// MediaTypeImageLayerGzip is the media type used for gzipped layers +// referenced by the manifest. +#define MediaTypeImageLayerGzip "application/vnd.oci.image.layer.v1.tar+gzip" + +// MediaTypeImageLayerNonDistributable is the media type for layers referenced by +// the manifest but with distribution restrictions. +#define MediaTypeImageLayerNonDistributable "application/vnd.oci.image.layer.nondistributable.v1.tar" + +// MediaTypeImageLayerNonDistributableGzip is the media type for +// gzipped layers referenced by the manifest but with distribution +// restrictions. +#define MediaTypeImageLayerNonDistributableGzip "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip" + +// MediaTypeImageConfig specifies the media type for the image configuration. +#define MediaTypeImageConfig "application/vnd.oci.image.config.v1+json" + +// MediaTypeImageEmbedded specifies the media type for the embedded image configuration. +#define MediaTypeEmbeddedImageManifest "application/embedded.manifest+json" +#define MediaTypeEmbeddedLayerSquashfs "application/squashfs.image.rootfs.diff.img" +#define MediaTypeEmbeddedLayerDir "application/bind.image.rootfs.diff.dir" + +#define MediaTypeJson "application/json" + +#define DOCKER_REGISTRY "docker.io" +#define DOCKER_HOST "registry-1.docker.io" + +#define REGISTRY_PREFIX "/v2" +#define MANIFESTS "/manifests" +#define BLOBS "/blobs" + +#endif diff --git a/src/http/parser.c b/src/http/parser.c new file mode 100644 index 0000000..247c4d2 --- /dev/null +++ b/src/http/parser.c @@ -0,0 +1,292 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container parser functions + ******************************************************************************/ +#include +#include + +#include "parser.h" +#include "utils.h" +#include "log.h" + +size_t strlncat(char *dststr, size_t size, const char *srcstr, size_t nsize) +{ + size_t ssize, dsize; + + ssize = (size_t)strnlen(srcstr, nsize); + dsize = (size_t)strnlen(dststr, size); + + if (dsize < size) { + size_t rsize = size - dsize; + size_t ncpy = ssize < rsize ? ssize : (rsize - 1); + errno_t nret = memcpy_s(dststr + dsize, size - dsize, srcstr, ncpy); + if (nret != EOK) { + ERROR("Fail at strlncat memcpy!"); + return 0; + } + dststr[dsize + ncpy] = '\0'; + } + + return ssize + dsize; +} + +/* parser cb request url */ +static int parser_cb_request_url(http_parser *parser, const char *buf, + size_t len) +{ + struct parsed_http_message *m = parser->data; + + strlncat(m->request_url, sizeof(m->request_url), buf, len); + return 0; +} + +/* parser cb header field */ +static int parser_cb_header_field(http_parser *parser, const char *buf, + size_t len) +{ + struct parsed_http_message *m = parser->data; + + if (m->last_header_element != FIELD) { + m->num_headers++; + } + + strlncat(m->headers[m->num_headers - 1][0], sizeof(m->headers[m->num_headers - 1][0]), buf, len); + + m->last_header_element = FIELD; + + return 0; +} + +/* parser cb header value */ +static int parser_cb_header_value(http_parser *parser, const char *buf, + size_t len) +{ + struct parsed_http_message *m = parser->data; + + strlncat(m->headers[m->num_headers - 1][1], sizeof(m->headers[m->num_headers - 1][1]), buf, len); + m->last_header_element = VALUE; + return 0; +} + +/* parser check body is final */ +static void parser_check_body_is_final(const http_parser *parser) +{ + struct parsed_http_message *m = parser->data; + + if (m->body_is_final) { + fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " + "on last on_body callback call " + "but it doesn't! ***\n\n"); + abort(); + } + m->body_is_final = http_body_is_final(parser); +} + +/* parser body cb */ +static int parser_body_cb(http_parser *parser, const char *buf, size_t len) +{ + struct parsed_http_message *m = parser->data; + size_t newsize; + char *body = NULL; + if (m->body_size > (SIZE_MAX - len) - 1) { + ERROR("http body size is too large!"); + return -1; + } + newsize = m->body_size + len + 1; + body = util_common_calloc_s(newsize); + if (body == NULL) { + ERROR("Out of memory"); + return -1; + } + if (m->body != NULL && m->body_size > 0) { + if (memcpy_s(body, newsize, m->body, m->body_size) != EOK) { + ERROR("Failed to copy memory"); + free(body); + return -1; + } + free(m->body); + } + + m->body = body; + + strlncat(m->body, newsize, buf, len); + m->body_size += len; + parser_check_body_is_final(parser); + return 0; +} + +/* parser message begin cb */ +static int parser_message_begin_cb(http_parser *p) +{ + struct parsed_http_message *m = p->data; + m->message_begin_cb_called = TRUE; + return 0; +} + +/* parser headers complete cb */ +static int parser_headers_complete_cb(http_parser *p) +{ + struct parsed_http_message *m = p->data; + m->method = p->method; + m->status_code = (int)(p->status_code); + m->http_major = p->http_major; + m->http_minor = p->http_minor; + m->headers_complete_cb_called = TRUE; + m->should_keep_alive = http_should_keep_alive(p); + return 0; +} + +/* parser message complete cb */ +static int parser_message_complete_cb(http_parser *p) +{ + struct parsed_http_message *m = p->data; + + if (m->should_keep_alive != http_should_keep_alive(p)) { + fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same " + "value in both on_message_complete and on_headers_complete " + "but it doesn't! ***\n\n"); + abort(); + } + + if (m->body_size && + http_body_is_final(p) && + !m->body_is_final) { + fprintf(stderr, "\n\n *** Error http_body_is_final() should return 1 " + "on last on_body callback call " + "but it doesn't! ***\n\n"); + abort(); + } + + m->message_complete_cb_called = TRUE; + + + return 0; +} + +/* parser response status cb */ +static int parser_response_status_cb(http_parser *p, const char *buf, + size_t len) +{ + struct parsed_http_message *m = p->data; + + m->status_cb_called = TRUE; + + strlncat(m->response_status, sizeof(m->response_status), buf, + len); + return 0; +} + +/* parser chunk header cb */ +static int parser_chunk_header_cb(http_parser *p) +{ + struct parsed_http_message *m = p->data; + + int chunk_idx = m->num_chunks; + m->num_chunks++; + if (chunk_idx < MAX_CHUNKS && chunk_idx >= 0) { + m->chunk_lengths[chunk_idx] = (int)p->content_length; + } + + return 0; +} + +/* parser chunk complete cb */ +static int parser_chunk_complete_cb(http_parser *p) +{ + struct parsed_http_message *m = p->data; + + /* Here we want to verify that each chunk_header_cb is matched by a + * chunk_complete_cb, so not only should the total number of calls to + * both callbacks be the same, but they also should be interleaved + * properly */ + if (m->num_chunks != m->num_chunks_complete + 1) { + ERROR("chunk_header_cb is not matched by chunk_complate_cb"); + return -1; + } + + m->num_chunks_complete++; + return 0; +} + +static http_parser_settings g_settings = { + .on_message_begin = parser_message_begin_cb, + .on_header_field = parser_cb_header_field, + .on_header_value = parser_cb_header_value, + .on_url = parser_cb_request_url, + .on_status = parser_response_status_cb, + .on_body = parser_body_cb, + .on_headers_complete = parser_headers_complete_cb, + .on_message_complete = parser_message_complete_cb, + .on_chunk_header = parser_chunk_header_cb, + .on_chunk_complete = parser_chunk_complete_cb +}; + +/* parser init */ +static http_parser *parser_init(enum http_parser_type type, + struct parsed_http_message *m) +{ + http_parser *parser = NULL; + + parser = util_common_calloc_s(sizeof(http_parser)); + if (parser == NULL) { + return NULL; + } + + parser->data = m; + + http_parser_init(parser, type); + + return parser; +} + +/* parser free */ +static void parser_free(http_parser *parser) +{ + free(parser); +} + +/* parse */ +static size_t parse(const char *buf, size_t len, http_parser *parser) +{ + size_t nparsed; + nparsed = http_parser_execute(parser, &g_settings, buf, len); + return nparsed; +} + +/* parse http */ +int parse_http(const char *buf, size_t len, struct parsed_http_message *m, + enum http_parser_type type) +{ + int ret = 0; + http_parser *parser = NULL; + size_t nparsed = 0; + + parser = parser_init(type, m); + if (parser == NULL) { + ret = -1; + goto out; + } + + nparsed = parse(buf, len, parser); + if (nparsed != len) { + ERROR("Failed to parse it, parsed :%ld, intput:%ld \n", nparsed, len); + ret = -1; + goto free_out; + } + +free_out: + parser_free(parser); +out: + return ret; +} + diff --git a/src/http/parser.h b/src/http/parser.h new file mode 100644 index 0000000..b9d78a2 --- /dev/null +++ b/src/http/parser.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container parser definition + ******************************************************************************/ +#ifndef _PARSER_H +#define _PARSER_H + +#include "http_parser.h" + +#undef TRUE +#define TRUE 1 +#undef FALSE +#define FALSE 0 + +#define MAX_HEADERS 13 +#define MAX_ELEMENT_SIZE 2048 +#define MAX_CHUNKS 16 + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +struct parsed_http_message { + enum http_method method; + int status_code; + char response_status[MAX_ELEMENT_SIZE]; + + char request_url[MAX_ELEMENT_SIZE]; + + char *body; + size_t body_size; + + int num_headers; + enum { NONE = 0, FIELD, VALUE } last_header_element; + char headers[MAX_HEADERS][2][MAX_ELEMENT_SIZE]; + int should_keep_alive; + + int num_chunks; + int num_chunks_complete; + int chunk_lengths[MAX_CHUNKS]; + + unsigned short http_major; + unsigned short http_minor; + + int message_begin_cb_called; + int headers_complete_cb_called; + int message_complete_cb_called; + int status_cb_called; + int body_is_final; +}; + +int parse_http(const char *buf, size_t len, struct parsed_http_message *m, + enum http_parser_type type); + +#endif diff --git a/src/http/rest_common.c b/src/http/rest_common.c new file mode 100644 index 0000000..666d853 --- /dev/null +++ b/src/http/rest_common.c @@ -0,0 +1,295 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide rest common functions + ******************************************************************************/ +#include "rest_common.h" +#include +#include "securec.h" +#include "log.h" +#include "utils.h" + +typedef size_t (*buffer_strlen_t)(Buffer *buf); +typedef int (*parse_http_t)(const char *buf, size_t len, struct parsed_http_message *m, enum http_parser_type type); +typedef Buffer *(*buffer_alloc_t)(size_t initial_size); +typedef void (*buffer_free_t)(Buffer *buf); +typedef int (*http_request_t)(const char *url, struct http_get_options *options, long *response_code, + int recursive_len); +typedef void (*free_http_get_options_t)(struct http_get_options *options); + +struct httpclient_ops { + void *handle; + + parse_http_t parse_http_op; + http_request_t http_request_op; + free_http_get_options_t free_http_get_options_op; + + buffer_strlen_t buffer_strlen_op; + buffer_alloc_t buffer_alloc_op; + buffer_free_t buffer_free_op; +}; + +static struct httpclient_ops g_hc_ops; + +/* + * dlclose may leak the fd which is opened by dlopen in lower version of glibc, + * to avoid reach the fd limit when call http request multiple times, + * we limited the max times of open libhttpclient to 3, make sure we can release memory + * in subcmd run (include rest request of create, start, need call dlclose 2 times). + */ +#define MAX_KEEP_OPS_CNT 3 +static int g_ops_status = 0; + +/* check status code */ +int check_status_code(int status_code) +{ + if (status_code == EVHTP_RES_OK || status_code == EVHTP_RES_SERVERR) { + return 0; + } else if (status_code == EVHTP_RES_NOTIMPL) { + ERROR("Not implement interface"); + return -1; + } else if (status_code == EVHTP_RES_NOTFOUND) { + ERROR("Can not connect to service"); + return -1; + } else if (status_code == EVHTP_RES_ERROR) { + ERROR("Server internal error"); + return -1; + } + + ERROR("Unknown http status found:'%d'", status_code); + return -1; +} + +/* free httpclient ops */ +static void free_httpclient_ops(struct httpclient_ops *ops) +{ + errno_t rc = EOK; + + if (ops == NULL || ops->handle == NULL) { + return; + } + if (g_ops_status == MAX_KEEP_OPS_CNT) { + return; + } + dlclose(ops->handle); + rc = memset_s(ops, sizeof(struct httpclient_ops), 0, sizeof(struct httpclient_ops)); + if (rc != EOK) { + ERROR("Failed to set memory!"); + } +} + +/* ops init */ +static int ops_init(struct httpclient_ops *ops) +{ + void *handle = NULL; + int ret = -1; + errno_t rc = EOK; + + if (ops == NULL) { + return ret; + } + rc = memset_s(ops, sizeof(struct httpclient_ops), 0, sizeof(struct httpclient_ops)); + if (rc != EOK) { + ERROR("Failed to set memory!"); + goto out; + } + handle = dlopen("libhttpclient.so", RTLD_LAZY); + if (handle == NULL) { + COMMAND_ERROR("Dlopen libhttpclient: %s", dlerror()); + goto out; + } + ops->handle = handle; + ops->buffer_strlen_op = (buffer_strlen_t)dlsym(handle, "buffer_strlen"); + if (ops->buffer_strlen_op == NULL) { + goto badcleanup; + } + ops->buffer_alloc_op = (buffer_alloc_t)dlsym(handle, "buffer_alloc"); + if (ops->buffer_alloc_op == NULL) { + goto badcleanup; + } + ops->buffer_free_op = (buffer_free_t)dlsym(handle, "buffer_free"); + if (ops->buffer_free_op == NULL) { + goto badcleanup; + } + ops->parse_http_op = (parse_http_t)dlsym(handle, "parse_http"); + if (ops->parse_http_op == NULL) { + goto badcleanup; + } + ops->http_request_op = (http_request_t)dlsym(handle, "http_request"); + if (ops->http_request_op == NULL) { + goto badcleanup; + } + ops->free_http_get_options_op = (free_http_get_options_t)dlsym(handle, "free_http_get_options"); + if (ops->free_http_get_options_op == NULL) { + goto badcleanup; + } + + g_ops_status++; + if (g_ops_status > MAX_KEEP_OPS_CNT) { + g_ops_status = MAX_KEEP_OPS_CNT; + } + + return 0; +badcleanup: + ERROR("bad cleanup"); + free_httpclient_ops(ops); +out: + return ret; +} + +/* get response */ +int get_response(Buffer *output, unpack_response_func_t unpack_func, void *arg) +{ + char *tmp = NULL; + int ret = 0; + size_t reslen = 0; + struct parsed_http_message *msg = NULL; + + if (output == NULL || unpack_func == NULL) { + ERROR("Invalid parameter"); + return -1; + } + + if (g_hc_ops.handle == NULL || g_hc_ops.parse_http_op == NULL || g_hc_ops.buffer_strlen_op == NULL) { + ERROR("http client ops is null"); + return -1; + } + msg = util_common_calloc_s(sizeof(struct parsed_http_message)); + if (msg == NULL) { + ERROR("Failed to malloc memory"); + ret = -1; + goto out; + } + + tmp = strstr(output->contents, "HTTP/1.1"); + if (tmp == NULL) { + ERROR("Failed to parse response, the response did not have HTTP/1.1\n"); + ret = -1; + goto out; + } + + reslen = g_hc_ops.buffer_strlen_op(output) - (size_t)(tmp - output->contents); + + ret = g_hc_ops.parse_http_op(tmp, reslen, msg, HTTP_RESPONSE); + if (ret != 0) { + ERROR("Failed to parse response, the response did not have HTTP/1.1\n"); + ret = -1; + goto out; + } + + ret = unpack_func(msg, arg); + +out: + free_httpclient_ops(&g_hc_ops); + if (msg != NULL) { + if (msg->body != NULL) { + free(msg->body); + } + free(msg); + } + + return ret; +} + +static int init_http_client_opt() +{ + if (g_hc_ops.handle == NULL && ops_init(&g_hc_ops) != 0) { + return -1; + } + if (g_hc_ops.http_request_op == NULL || g_hc_ops.buffer_alloc_op == NULL || + g_hc_ops.free_http_get_options_op == NULL) { + return -1; + } + + return 0; +} + +static int set_http_get_options(const char *socket, char *request_body, size_t body_len, + struct http_get_options *options, Buffer **output) +{ + Buffer *output_buffer = NULL; + const char *unix_raw_socket = NULL; + const char *raw_socket = NULL; + + options->with_head = 1; + options->with_header_json = 1; + options->input = request_body; + + options->input_len = body_len; + raw_socket = socket; + unix_raw_socket = str_skip_str(raw_socket, "unix://"); + if (unix_raw_socket == NULL) { + ERROR("Failed to str_skip_str raw_socket"); + return -1; + } + options->unix_socket_path = util_strdup_s(unix_raw_socket); + output_buffer = g_hc_ops.buffer_alloc_op(HTTP_GET_BUFFER_SIZE); + if (output_buffer == NULL) { + ERROR("Failed to malloc output_buffer"); + return -1; + } + + *output = output_buffer; + options->outputtype = HTTP_REQUEST_STRBUF; + options->output = output_buffer; + + return 0; +} + +/* rest send requst */ +int rest_send_requst(const char *socket, const char *url, char *request_body, size_t body_len, Buffer **output) +{ + long response_code = 0; + int ret = 0; + struct http_get_options *options = NULL; + + if (socket == NULL || url == NULL || request_body == NULL || output == NULL) { + ERROR("Invalid parameter"); + return -1; + } + if (init_http_client_opt()) { + ERROR("Failed to init g_hc_ops"); + free_httpclient_ops(&g_hc_ops); + return -1; + } + + options = util_common_calloc_s(sizeof(struct http_get_options)); + if (options == NULL) { + ERROR("Failed to malloc http_get_options"); + return -1; + } + + if (set_http_get_options(socket, request_body, body_len, options, output)) { + ret = -1; + goto out; + } + + ret = g_hc_ops.http_request_op(url, options, &response_code, 0); + if (ret != 0) { + ERROR("Failed to get http request: %d", ret); + ret = -1; + goto out; + } + +out: + g_hc_ops.free_http_get_options_op(options); + if (ret != 0) { + free_httpclient_ops(&g_hc_ops); + } + return ret; +} + +/* put body */ +void put_body(char *body) +{ + free(body); +} diff --git a/src/http/rest_common.h b/src/http/rest_common.h new file mode 100644 index 0000000..1d30da4 --- /dev/null +++ b/src/http/rest_common.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container restful common function definition + ******************************************************************************/ +#ifndef __REST_COMMON_H +#define __REST_COMMON_H + +#include + +#include "http/buffer.h" +#include "http/http.h" +#include "parser.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*unpack_response_func_t)(const struct parsed_http_message *message, void *arg); + +int get_response(Buffer *output, unpack_response_func_t unpack_func, void *arg); + +int rest_send_requst(const char *socket, const char *url, char *request_body, size_t body_len, Buffer **output); + +int check_status_code(int status_code); + +void put_body(char *body); + +#ifdef __cplusplus +} +#endif + +#endif /* __REST_COMMON_H */ diff --git a/src/image/CMakeLists.txt b/src/image/CMakeLists.txt new file mode 100644 index 0000000..54b498b --- /dev/null +++ b/src/image/CMakeLists.txt @@ -0,0 +1,43 @@ +# get current directory sources files + +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} image_top_srcs) +add_subdirectory(external) + +set(local_image_srcs + ${image_top_srcs} + ${EXTERNAL_SRCS} + ) + +set(local_image_incs + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/external + ) + +if (NOT DISABLE_OCI) + add_subdirectory(oci) + list(APPEND local_image_srcs + ${OCI_SRCS} + ) + list(APPEND local_image_incs + ${OCI_INCS} + ) +endif() + +if (ENABLE_EMBEDDED) + add_subdirectory(embedded) + list(APPEND local_image_srcs + ${EMBEDDED_SRCS} + ) + list(APPEND local_image_incs + ${EMBEDDED_INCS} + ) +endif() + +set(IMAGE_SRCS + ${local_image_srcs} + PARENT_SCOPE + ) +set(IMAGE_INCS + ${local_image_incs} + PARENT_SCOPE + ) diff --git a/src/image/embedded/CMakeLists.txt b/src/image/embedded/CMakeLists.txt new file mode 100644 index 0000000..00bf94b --- /dev/null +++ b/src/image/embedded/CMakeLists.txt @@ -0,0 +1,17 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_embedded_srcs) +add_subdirectory(db) +add_subdirectory(snapshot) + +set(EMBEDDED_SRCS + ${local_embedded_srcs} + ${DB_SRCS} + ${SNAPSHOT_SRCS} + PARENT_SCOPE + ) +set(EMBEDDED_INCS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/db + ${CMAKE_CURRENT_SOURCE_DIR}/snapshot + PARENT_SCOPE + ) diff --git a/src/image/embedded/db/CMakeLists.txt b/src/image/embedded/db/CMakeLists.txt new file mode 100644 index 0000000..bd8ad14 --- /dev/null +++ b/src/image/embedded/db/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_db_srcs) + +set(DB_SRCS + ${local_db_srcs} + PARENT_SCOPE + ) diff --git a/src/image/embedded/db/db_all.c b/src/image/embedded/db/db_all.c new file mode 100644 index 0000000..e2349ae --- /dev/null +++ b/src/image/embedded/db/db_all.c @@ -0,0 +1,838 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide image functions + ******************************************************************************/ +#include +#include +#include "securec.h" +#include "log.h" +#include "utils.h" +#include "db_common.h" +#include "sqlite_common.h" +#include "db_all.h" + + +#define IMAGE_INFO_TABLE_COLUMS_NUM 11 +#define IMAGE_INFO_TABLE_STMT "CREATE TABLE IF NOT EXISTS image_info("\ + "image_type TEXT,"\ + "size REAL,"\ + "layer_num INTERGER,"\ + "top_chainid TEXT,"\ + "top_cacheid TEXT,"\ + "config_digest TEXT,"\ + "config_cacheid TEXT,"\ + "config_path TEXT,"\ + "created TEXT,"\ + "mount_string TEXT,"\ + "config TEXT,"\ + "UNIQUE(config_digest,config_path)"\ + ");" + +#define IMAGE_NAME_TABLE_COLUMS_NUM 2 +#define IMAGE_NAME_TABLE_STMT "CREATE TABLE IF NOT EXISTS image_names("\ + "image_name TEXT PRIMARY KEY,"\ + "image_rowid INTERGER,"\ + "UNIQUE(image_name,image_rowid));" + +struct db_image_name { + char *image_name; + long long image_rowid; +}; + +struct db_image_wrapper { + struct db_image *image; + long long image_rowid; +}; + +static pthread_mutex_t g_mutex; + +/* g mutex lock */ +static inline void g_mutex_lock() +{ + if (pthread_mutex_lock(&g_mutex)) { + ERROR("Failed to lock mutex"); + } +} + +/* g mutex unlock */ +static inline void g_mutex_unlock() +{ + if (pthread_mutex_unlock(&g_mutex)) { + ERROR("Failed to unlock mutex"); + } +} + +/* db all init */ +int db_all_init() +{ + int ret = 0; + + ret = pthread_mutex_init(&g_mutex, NULL); + if (ret) { + ERROR("Mutex initialization failed"); + return -1; + } + + ret = db_sqlite_request(IMAGE_INFO_TABLE_STMT); + if (ret != SQLITE_OK) { + ERROR("Failed to crerate table\n"); + ret = -1; + goto out; + } + + ret = db_sqlite_request(IMAGE_NAME_TABLE_STMT); + if (ret != SQLITE_OK) { + ERROR("Failed to crerate table\n"); + ret = -1; + goto out; + } +out: + + return ret; +} + +/* db imgname free */ +static void db_imgname_free(struct db_image_name **imagename) +{ + if (imagename == NULL) { + return; + } + if (*imagename == NULL) { + return; + } + + UTIL_FREE_AND_SET_NULL((*imagename)->image_name); + UTIL_FREE_AND_SET_NULL(*imagename); + + return; +} + +static int read_single_image_info(sqlite3_stmt *stmt, void *data) +{ + struct db_image *image = NULL; + long long image_rowid = 0; + if (sqlite3_column_count(stmt) < IMAGE_INFO_TABLE_COLUMS_NUM) { + ERROR("Invalid colums num when read image info:%d", sqlite3_column_count(stmt)); + return DB_FAIL; + } + + image = util_common_calloc_s(sizeof(struct db_image)); + if (image == NULL) { + ERROR("Out of memory"); + return DB_FAIL; + } + image_rowid = sqlite3_column_int64(stmt, 0); + + if (sqlite3_column_text(stmt, 1)) { + image->image_type = util_strdup_s((char *)sqlite3_column_text(stmt, 1)); + } + + image->size = sqlite3_column_int64(stmt, 2); + + image->layer_num = (size_t) sqlite3_column_int64(stmt, 3); + + if (sqlite3_column_text(stmt, 4)) { + image->top_chainid = util_strdup_s((const char *)sqlite3_column_text(stmt, 4)); + } + + if (sqlite3_column_text(stmt, 5)) { + image->top_cacheid = util_strdup_s((const char *)sqlite3_column_text(stmt, 5)); + } + + if (sqlite3_column_text(stmt, 6)) { + image->config_digest = util_strdup_s((const char *)sqlite3_column_text(stmt, 6)); + } + + if (sqlite3_column_text(stmt, 7)) { + image->config_cacheid = util_strdup_s((const char *)sqlite3_column_text(stmt, 7)); + } + + if (sqlite3_column_text(stmt, 8)) { + image->config_path = util_strdup_s((const char *)sqlite3_column_text(stmt, 8)); + } + + if (sqlite3_column_text(stmt, 9)) { + image->created = util_strdup_s((const char *)sqlite3_column_text(stmt, 9)); + } + + if (sqlite3_column_text(stmt, 10)) { + image->mount_string = util_strdup_s((const char *)sqlite3_column_text(stmt, 10)); + } + + if (sqlite3_column_text(stmt, 11)) { + image->config = util_strdup_s((const char *)sqlite3_column_text(stmt, 11)); + } + + ((struct db_image_wrapper *)data)->image = image; + ((struct db_image_wrapper *)data)->image_rowid = image_rowid; + return DB_OK; +} + +/* db read image sql */ +static int db_read_image_sql(const char *image_name, + struct db_image **image_info, + long long *image_rowid) +{ + int ret = 0; + struct db_image_wrapper w = { 0 }; + + char *sql = "SELECT " + "image_names.image_rowid," + "image_info.image_type," + "image_info.size," + "image_info.layer_num," + "image_info.top_chainid," + "image_info.top_cacheid," + "image_info.config_digest," + "image_info.config_cacheid," + "image_info.config_path," + "image_info.created," + "image_info.mount_string," + "image_info.config" + " FROM image_info,image_names WHERE " + "image_names.image_name = ? AND " + "image_info.rowid = " + "image_names.image_rowid"; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + db = get_global_db(); + ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ERROR("Failed to prepare SQL"); + goto cleanup; + } + sqlite3_bind_text(stmt, 1, image_name, -1, SQLITE_STATIC); + if (sqlite3_step(stmt) == SQLITE_ROW) { + ret = read_single_image_info(stmt, (void *)&w); + if (ret != DB_OK) { + ERROR("Failed to read image info by %s", image_name); + goto cleanup; + } + } + + if (w.image != NULL) { + w.image->image_name = util_strdup_s(image_name); + } + + *image_info = w.image; + *image_rowid = w.image_rowid; + +cleanup: + if (stmt && (sqlite3_finalize(stmt) != SQLITE_OK)) { + ERROR("Failed to finalize sqlite3_stmt"); + } + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db add image name sql */ +static int db_add_image_name_sql(const char *image_name, const char *digest, const char *path) +{ + int ret = 0; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + char *sql = "INSERT INTO image_names SELECT ?1,image_info.rowid" + " FROM image_info WHERE image_info.config_digest = ?2 AND " + "image_info.config_path = ?3;"; + db = get_global_db(); + ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ERROR("Failed to prepare SQL"); + goto cleanup; + } + sqlite3_bind_text(stmt, 1, image_name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, digest, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, path, -1, SQLITE_STATIC); + if (sqlite3_step(stmt) != SQLITE_DONE) { + ERROR("Failed to add image name info"); + ret = DB_FAIL; + } + +cleanup: + if (stmt && (sqlite3_finalize(stmt) != SQLITE_OK)) { + ERROR("Failed to finalize sqlite3_stmt"); + } + + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db save image info sql */ +static int db_save_image_info_sql(struct db_image *image) +{ + int ret = 0; + int64_t layer_num = 0; + sqlite3 *db = NULL; + char *sql = "INSERT INTO image_info" + " SELECT ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11 " + " WHERE NOT EXISTS(SELECT rowid FROM image_info WHERE " + "image_info.config_digest = ?12 AND " + "image_info.config_path = ?13);"; + sqlite3_stmt *stmt = NULL; + db = get_global_db(); + ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ERROR("Failed to prepare SQL"); + goto cleanup; + } + sqlite3_bind_text(stmt, 1, image->image_type, -1, SQLITE_STATIC); + sqlite3_bind_int64(stmt, 2, image->size); + sqlite3_bind_int64(stmt, 3, layer_num); + image->layer_num = (size_t) layer_num; + sqlite3_bind_text(stmt, 4, image->top_chainid, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 5, image->top_cacheid, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 6, image->config_digest, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 7, image->config_cacheid, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 8, image->config_path, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 9, image->created, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 10, image->mount_string, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 11, image->config, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 12, image->config_digest, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 13, image->config_path, -1, SQLITE_STATIC); + if (sqlite3_step(stmt) != SQLITE_DONE) { + ERROR("Insert image info into the image infomation table failed!"); + ret = DB_FAIL; + } + +cleanup: + if (stmt && (sqlite3_finalize(stmt) != SQLITE_OK)) { + ERROR("Failed to finalize sqlite3_stmt"); + } + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db delete image name sql */ +static int db_delete_image_name_sql(char *name) +{ + int ret = 0; + char *sql = "DELETE FROM image_names WHERE image_name = ?;"; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + db = get_global_db(); + ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ERROR("Failed to prepare SQL"); + goto cleanup; + } + sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); + if (sqlite3_step(stmt) != SQLITE_DONE) { + ERROR("Failed to delete image name by %s", name); + ret = DB_FAIL; + } +cleanup: + if (stmt && (sqlite3_finalize(stmt) != SQLITE_OK)) { + ERROR("Failed to finalize sqlite3_stmt"); + } + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db save image */ +int db_save_image(struct db_image *image) +{ + int ret = 0; + long long image_rowid = 0; + struct db_image *read_image = NULL; + + if (image == NULL) { + ERROR("invalid NULL param"); + return DB_INVALID_PARAM; + } + + g_mutex_lock(); + + ret = db_read_image_sql(image->image_name, &read_image, &image_rowid); + if (ret < 0) { + goto out; + } + + if (read_image != NULL) { + if (strcmp(read_image->config_digest, image->config_digest) == 0 && + strcmp(read_image->config_path, image->config_path) == 0) { + ret = DB_OK; + goto out; + } + + ret = DB_NAME_CONFLICT; + goto out; + } + + ret = db_save_image_info_sql(image); + if (ret < 0) { + goto out; + } + + ret = db_add_image_name_sql(image->image_name, + image->config_digest, image->config_path); + if (ret) { + /* Should not error when add image name. If error occured, + * database is abnormal, so do not rollback. */ + goto out; + } + +out: + g_mutex_unlock(); + if (read_image != NULL) { + db_image_free(&read_image); + } + + return ret; +} + +static int read_single_image_name(sqlite3_stmt *stmt, void *data) +{ + struct db_image_name *imagename = NULL; + + if (sqlite3_column_count(stmt) < IMAGE_NAME_TABLE_COLUMS_NUM) { + ERROR("Invalid colums num for image name:%d", sqlite3_column_count(stmt)); + return DB_FAIL; + } + + imagename = util_common_calloc_s(sizeof(struct db_image_name)); + if (imagename == NULL) { + ERROR("Out of memory"); + return DB_FAIL; + } + + if (sqlite3_column_text(stmt, 0)) { + imagename->image_name = util_strdup_s((const char *)sqlite3_column_text(stmt, 0)); + } + + imagename->image_rowid = sqlite3_column_int64(stmt, 1); + + *(struct db_image_name **)data = imagename; + return DB_OK; +} + +/* db read image name sql */ +static int db_read_image_name_sql(char *name, + struct db_image_name **imagename) +{ + int ret = 0; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + char *sql = "SELECT * FROM image_names WHERE image_name = ?"; + db = get_global_db(); + ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ERROR("Failed to prepare SQL"); + goto cleanup; + } + sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); + while (sqlite3_step(stmt) == SQLITE_ROW) { + ret = read_single_image_name(stmt, (void *)imagename); + if (ret != DB_OK) { + ERROR("Failed to read image name by %s", name); + } + } +cleanup: + if (stmt && (sqlite3_finalize(stmt) != SQLITE_OK)) { + ERROR("Failed to finalize sqlite3_stmt"); + } + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db read image rowid sql */ +static int db_read_image_rowid_sql(long long rowid, + struct db_image_name **imagename) +{ + int ret = 0; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + char *sql = "SELECT * FROM image_names WHERE image_rowid = ?"; + db = get_global_db(); + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) { + ERROR("Failed to prepare SQL"); + goto cleanup; + } + sqlite3_bind_int64(stmt, 1, rowid); + while (sqlite3_step(stmt) == SQLITE_ROW) { + ret = read_single_image_name(stmt, (void *)imagename); + if (ret != DB_OK) { + ERROR("Failed to read image rowid by %lld", rowid); + } + } +cleanup: + if (stmt && (sqlite3_finalize(stmt) != SQLITE_OK)) { + ERROR("Failed to finalize sqlite3_stmt"); + } + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db read image */ +int db_read_image(const char *name, struct db_image **image) +{ + int ret = 0; + long long image_rowid = 0; + + if (name == NULL || image == NULL) { + ERROR("invalid NULl param"); + return DB_INVALID_PARAM; + } + + g_mutex_lock(); + + ret = db_read_image_sql(name, image, &image_rowid); + if (ret < 0) { + goto out; + } + + if (*image == NULL) { + ret = DB_NOT_EXIST; + goto out; + } + +out: + g_mutex_unlock(); + + if (ret) { + db_image_free(image); + } + + return ret; +} + +/* db delete image info sql */ +static int db_delete_image_info_sql(long long image_rowid) +{ + int ret = 0; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + char *sql = "DELETE FROM image_info WHERE rowid = ?1 AND NOT EXISTS" + " (SELECT rowid FROM image_names WHERE image_rowid = ?2);"; + db = get_global_db(); + ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ERROR("Failed to prepare SQL"); + goto cleanup; + } + sqlite3_bind_int64(stmt, 1, image_rowid); + sqlite3_bind_int64(stmt, 2, image_rowid); + + if (sqlite3_step(stmt) != SQLITE_DONE) { + ERROR("Failed to delete image info by %lld", image_rowid); + ret = DB_FAIL; + } +cleanup: + if (stmt && (sqlite3_finalize(stmt) != SQLITE_OK)) { + ERROR("Failed to finalize sqlite3_stmt"); + } + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db delete image */ +int db_delete_image(char *name, bool force) +{ + int ret = 0; + struct db_image_name *imagename = NULL; + + if (name == NULL) { + ERROR("invalid NULl param"); + return DB_INVALID_PARAM; + } + + g_mutex_lock(); + + ret = db_read_image_name_sql(name, &imagename); + if (ret < 0) { + ret = -1; + goto out; + } + + if (imagename == NULL) { + ret = DB_NOT_EXIST; + goto out; + } + + ret = db_delete_image_name_sql(name); + if (ret < 0) { + goto out; + } + + ret = db_delete_image_info_sql(imagename->image_rowid); + if (ret < 0) { + goto out; + } + +out: + g_mutex_unlock(); + + db_imgname_free(&imagename); + + return ret; +} + +static int db_exec_sql(const char *sql) +{ + int ret = 0; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + + if (sql == NULL || strlen(sql) == 0) { + return DB_FAIL; + } + + db = get_global_db(); + ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); + if (ret != SQLITE_OK) { + ERROR("Failed to prepare SQL"); + goto cleanup; + } + if (sqlite3_step(stmt) != SQLITE_DONE) { + ERROR("Failed to delete dangling image name"); + ret = DB_FAIL; + } +cleanup: + if (stmt && (sqlite3_finalize(stmt) != SQLITE_OK)) { + ERROR("Failed to finalize sqlite3_stmt"); + } + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db delete dangling image name sql */ +static int db_delete_dangling_image_name_sql() +{ + char *sql = "DELETE FROM image_names WHERE image_rowid NOT IN " + " (SELECT rowid FROM image_info );"; + + return db_exec_sql(sql); +} + +/* db delete dangling image info sql */ +static int db_delete_dangling_image_info_sql() +{ + char *sql = "DELETE FROM image_info WHERE rowid NOT IN " + " (SELECT image_rowid FROM " + "image_names );"; + + return db_exec_sql(sql); +} + +/* db delete dangling images no lock */ +static int db_delete_dangling_image_no_lock() +{ + int ret = 0; + + ret = db_delete_dangling_image_name_sql(); + if (ret) { + goto out; + } + + ret = db_delete_dangling_image_info_sql(); + if (ret) { + goto out; + } + +out: + return ret; +} + +/* db delete dangling images */ +int db_delete_dangling_images() +{ + int ret = 0; + + g_mutex_lock(); + ret = db_delete_dangling_image_no_lock(); + g_mutex_unlock(); + + return ret; +} + +/* db image free */ +void db_image_free(struct db_image **image) +{ + if (image == NULL) { + return; + } + if (*image == NULL) { + return; + } + + UTIL_FREE_AND_SET_NULL((*image)->image_name); + UTIL_FREE_AND_SET_NULL((*image)->image_type); + UTIL_FREE_AND_SET_NULL((*image)->top_chainid); + UTIL_FREE_AND_SET_NULL((*image)->top_cacheid); + UTIL_FREE_AND_SET_NULL((*image)->config_digest); + UTIL_FREE_AND_SET_NULL((*image)->config_cacheid); + UTIL_FREE_AND_SET_NULL((*image)->config_path); + UTIL_FREE_AND_SET_NULL((*image)->created); + UTIL_FREE_AND_SET_NULL((*image)->mount_string); + UTIL_FREE_AND_SET_NULL((*image)->config); + UTIL_FREE_AND_SET_NULL(*image); + + return; +} + +static int read_all_images_info(sqlite3_stmt *stmt, void **data) +{ + struct db_all_images **imagesinfo = (struct db_all_images **)data; + struct db_image_wrapper wrapinfo = { 0 }; + size_t oldsize, newsize; + struct db_image_name *dbimg_name = NULL; + int ret = 0; + + /* malloc memory when first entering this callback */ + if (*imagesinfo == NULL) { + *imagesinfo = util_common_calloc_s(sizeof(struct db_all_images)); + if (*imagesinfo == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + } + + if (read_single_image_info(stmt, (void *)&wrapinfo) != DB_OK) { + ERROR("Failed to read image info!"); + goto cleanup; + } + if ((*imagesinfo)->imagesnum > (SIZE_MAX / sizeof(struct db_image *) - 1)) { + ERROR("List of images is too long:%d", (*imagesinfo)->imagesnum); + goto cleanup; + } + oldsize = (*imagesinfo)->imagesnum * sizeof(struct db_image *); + newsize = ((*imagesinfo)->imagesnum + 1) * sizeof(struct db_image *); + ret = mem_realloc((void **)(&(*imagesinfo)->images_info), newsize, + (*imagesinfo)->images_info, oldsize); + if (ret < 0) { + ERROR("Out of memory!"); + goto cleanup; + } + + ret = db_read_image_rowid_sql(wrapinfo.image_rowid, &dbimg_name); + if (ret != 0 || (dbimg_name == NULL) || dbimg_name->image_name == NULL) { + ERROR("Image not in image name table"); + goto cleanup; + } + /* append newinfo to infolist */ + wrapinfo.image->image_name = util_strdup_s(dbimg_name->image_name); + + (*imagesinfo)->images_info[(*imagesinfo)->imagesnum] = wrapinfo.image; + (*imagesinfo)->imagesnum++; + + db_imgname_free(&dbimg_name); + + return DB_OK; + +cleanup: + if (dbimg_name != NULL) { + db_imgname_free(&dbimg_name); + } + + if (*imagesinfo != NULL) { + db_all_imginfo_free(*imagesinfo); + *imagesinfo = NULL; + } + + if (wrapinfo.image != NULL) { + db_image_free(&wrapinfo.image); + } + return DB_FAIL; +} + +/* db read all images info sql */ +int db_read_all_images_info_sql(struct db_all_images **image_info) +{ + int ret = 0; + struct db_all_images *w = NULL; + char *sql = "SELECT rowid," + "image_info.image_type," + "image_info.size," + "image_info.layer_num," + "image_info.top_chainid," + "image_info.top_cacheid," + "image_info.config_digest," + "image_info.config_cacheid," + "image_info.config_path," + "image_info.created," + "image_info.mount_string," + "image_info.config" + " FROM image_info"; + sqlite3 *db = NULL; + sqlite3_stmt *stmt = NULL; + db = get_global_db(); + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) { + if (stmt != NULL) { + ERROR("Failed to prepare SQL"); + sqlite3_finalize(stmt); + return DB_FAIL; + } + } + while (sqlite3_step(stmt) == SQLITE_ROW) { + if (read_all_images_info(stmt, (void **)&w) != DB_OK) { + ERROR("Failed to read image info"); + if (w != NULL) { + db_all_imginfo_free(w); + } + sqlite3_finalize(stmt); + return DB_FAIL; + } + } + ret = sqlite3_finalize(stmt); + if (ret != SQLITE_OK) { + ERROR("Failed to finalize sqlite3_stmt"); + } + + *image_info = w; + + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db read all images info */ +int db_read_all_images_info(struct db_all_images **image_info) +{ + int ret = 0; + + g_mutex_lock(); + + ret = db_read_all_images_info_sql(image_info); + if (ret < 0) { + goto out; + } + + if (*image_info == NULL) { + ret = DB_NOT_EXIST; + goto out; + } + +out: + g_mutex_unlock(); + + if (ret) { + db_all_imginfo_free(*image_info); + } + + return ret; +} + +/* db all imginfo free */ +void db_all_imginfo_free(struct db_all_images *images_info) +{ + struct db_all_images *img = NULL; + + if (images_info == NULL) { + return; + } + + img = images_info; + + if (img->images_info != NULL) { + size_t i; + for (i = 0; i < img->imagesnum; i++) { + db_image_free(&(img->images_info[i])); + img->images_info[i] = NULL; + } + free(img->images_info); + img->images_info = NULL; + } + free(img); + + return; +} + diff --git a/src/image/embedded/db/db_all.h b/src/image/embedded/db/db_all.h new file mode 100644 index 0000000..00c42fe --- /dev/null +++ b/src/image/embedded/db/db_all.h @@ -0,0 +1,73 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide image function definition + ******************************************************************************/ +#ifndef __DB_ALL_H_ +#define __DB_ALL_H_ + +#include "db_common.h" + +struct db_sninfo { + char *snid; + char *parent_snid; + int64_t size; + uint32_t layer_attribute; + /* If layer is RW layer, this represent the image create this layer */ + char *image_name; + char *diffid; + uint32_t driver_type; + char *cacheid; + char *config_digest; + char *path_in_host; + char *path_in_container; +}; + +struct db_image { + char *image_name; + char *image_type; + int64_t size; + size_t layer_num; + char *top_chainid; + char *top_cacheid; + char *config_digest; + char *config_cacheid; + char *config_path; + char *created; + char *mount_string; + char *config; +}; + +struct db_all_images { + size_t imagesnum; + struct db_image **images_info; +}; + +int db_all_init(); + +int db_add_name(char *image_name, char *digest, char *path); + +int db_save_image(struct db_image *image); + +int db_read_image(const char *name, struct db_image **image); + +int db_delete_image(char *name, bool force); + +void db_image_free(struct db_image **image); + +int db_delete_dangling_images(); + +int db_read_all_images_info(struct db_all_images **image_info); + +void db_all_imginfo_free(struct db_all_images *images_info); + +#endif diff --git a/src/image/embedded/db/db_common.h b/src/image/embedded/db/db_common.h new file mode 100644 index 0000000..4b5365f --- /dev/null +++ b/src/image/embedded/db/db_common.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide image definition + ******************************************************************************/ +#ifndef __DB_COMMON_H_ +#define __DB_COMMON_H_ + +#define DB_OUT_OF_MEMORY -3 +#define DB_INVALID_PARAM -2 +#define DB_FAIL -1 +#define DB_OK 0 +#define DB_NAME_CONFLICT 1 +#define DB_INUSE 2 +#define DB_DEL_NAME_ONLY 3 +#define DB_DEREF_ONLY 4 +#define DB_NOT_EXIST 5 + +int db_common_init(const char *rootpath); + +void db_common_finish(void); + +#endif /*__DB_COMMON_H_*/ diff --git a/src/image/embedded/db/db_images_common.h b/src/image/embedded/db/db_images_common.h new file mode 100644 index 0000000..0c43dbc --- /dev/null +++ b/src/image/embedded/db/db_images_common.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide common functions for images + ******************************************************************************/ +#ifndef __DB_IMAGES_COMMON_H_ +#define __DB_IMAGES_COMMON_H_ + +#include + +/* common interface definition for database */ + +struct db_single_image_info { + char *imageref; + char *type; + char *digest; + int64_t size; /* Bytes */ + char *chain_id; +}; + +struct db_all_images_info { + int imagesnum; + struct db_single_image_info **images_info; +}; + +#endif diff --git a/src/image/embedded/db/sqlite_common.c b/src/image/embedded/db/sqlite_common.c new file mode 100644 index 0000000..2217fa8 --- /dev/null +++ b/src/image/embedded/db/sqlite_common.c @@ -0,0 +1,214 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide sqlite functions + ******************************************************************************/ +#include "sqlite_common.h" +#include +#include +#include +#include "securec.h" + +#include "utils.h" +#include "constants.h" +#include "log.h" +#include "db_common.h" + +// Waiting at most (10 * 1000ms) when database is busy +// to avoid concurrent write or read. +#define SQLITE_BUSY_TIMEOUT 120000 + +#define SQLITE_PAGECACHE_SIZE 4096 +#define SQLITE_PAGECACHE_NUM 8 + +sqlite3 *g_db = NULL; + +sqlite3 *get_global_db() +{ + return g_db; +} + +/* db sqlite init */ +int db_sqlite_init(const char *dbpath) +{ + int ret = 0; + + sqlite3_config(SQLITE_CONFIG_SERIALIZED); + ret = sqlite3_open(dbpath, &g_db); + if (ret != SQLITE_OK) { + ERROR("Failed to open database %s", sqlite3_errmsg(g_db)); + goto cleanup; + } + if (chmod(dbpath, DEFAULT_SECURE_FILE_MODE) != 0) { + ERROR("Change mode of db file failed: %s", strerror(errno)); + goto cleanup; + } + return 0; +cleanup: + if (g_db != NULL) { + (void)sqlite3_close(g_db); + } + return -1; +} + +/* db sqlite finish */ +void db_sqlite_finish(void) +{ + if (g_db != NULL) { + (void)sqlite3_close(g_db); + } +} + +/* db sqlite request */ +int db_sqlite_request(const char *stmt) +{ + char *errmsg = NULL; + int ret; + + ret = sqlite3_busy_timeout(g_db, SQLITE_BUSY_TIMEOUT); + if (ret != SQLITE_OK) { + ERROR("Falied to set sqlite busy timeout"); + return ret; + } + ret = sqlite3_exec(g_db, stmt, NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + ERROR("Statement %s -> error sqlite3_exec(): %s", stmt, errmsg); + sqlite3_free(errmsg); + } + return ret; +} + +/* db sqlite request callback */ +int db_sqlite_request_callback(const char *stmt, + sqlite_callback_t callback, void *data) +{ + char *errmsg = NULL; + int ret; + + ret = sqlite3_busy_timeout(g_db, SQLITE_BUSY_TIMEOUT); + if (ret != SQLITE_OK) { + ERROR("Falied to set sqlite busy timeout"); + return ret; + } + ret = sqlite3_exec(g_db, stmt, callback, data, &errmsg); + if (ret != SQLITE_OK) { + ERROR("Statement %s -> error sqlite3_exec(): %s", stmt, errmsg); + sqlite3_free(errmsg); + } + return ret; +} + +/* Callback for sql request of 'PRAGMA integrity_check' */ +static int callback_integrity_check_result(void *data, int argc, char **argv, char **colname) +{ + if (argc != 1) { + ERROR("Invalid colums num:%d, it should be 1", argc); + return DB_FAIL; + } + + if (argv[0] == NULL) { + ERROR("Empty result when do integrity check"); + return DB_FAIL; + } + + if (strcmp(argv[0], "ok") == 0) { + return DB_OK; + } else { + ERROR("Integrity check result not ok"); + return DB_FAIL; + } +} + +/* db integrity check */ +int db_integrity_check() +{ + int ret = 0; + char *buf; + + buf = sqlite3_mprintf("PRAGMA integrity_check;"); + if (buf == NULL) { + ERROR("Out of memory"); + return DB_OUT_OF_MEMORY; + } + + ret = db_sqlite_request_callback(buf, callback_integrity_check_result, + NULL); + if (ret != SQLITE_OK) { + ERROR("Failed to do integrity check"); + } + + sqlite3_free(buf); + return (ret == SQLITE_OK) ? DB_OK : DB_FAIL; +} + +/* db common init */ +int db_common_init(const char *rootpath) +{ + int ret = 0; + int nret = 0; + char dbpath[PATH_MAX] = { 0 }; + bool retry = true; + + nret = sprintf_s(dbpath, sizeof(dbpath), "%s/%s", rootpath, DBNAME); + if (nret < 0) { + ERROR("Failed to print string"); + return -1; + } + ret = sqlite3_config(SQLITE_CONFIG_PAGECACHE, NULL, SQLITE_PAGECACHE_SIZE, + SQLITE_PAGECACHE_NUM); + if (ret != SQLITE_OK) { + goto open_new_db; + } + +try_open_db: + ret = db_sqlite_init(dbpath); + if (ret != SQLITE_OK) { + goto open_new_db; + } + + /* Ensure database not broken. */ + ret = db_integrity_check(); + if (ret == DB_OUT_OF_MEMORY) { + db_common_finish(); + return -1; + } else if (ret != DB_OK) { + db_common_finish(); + goto open_new_db; + } + + (void)sqlite3_soft_heap_limit64(65536); + INFO("sqlite3 used size: %lld", sqlite3_memory_used()); + + return 0; + +open_new_db: + + if (retry) { + /* We can delete database file safely because user will + * reload image if image not found. Only image managerment + * module is using database currently. */ + (void)unlink(dbpath); + ERROR("Delete database file %s because database broken detected", dbpath); + + retry = false; + /* Try to open a new empty database file if it's deleted. */ + goto try_open_db; + } + + return -1; +} + +/* db common finish */ +void db_common_finish(void) +{ + db_sqlite_finish(); +} diff --git a/src/image/embedded/db/sqlite_common.h b/src/image/embedded/db/sqlite_common.h new file mode 100644 index 0000000..aa139ca --- /dev/null +++ b/src/image/embedded/db/sqlite_common.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide sqlite function definition + ******************************************************************************/ +#ifndef __DB_SQLITE_COMMON_H_ +#define __DB_SQLITE_COMMON_H_ + +#include + +#define DBNAME "sqlite.db" + +typedef int(*sqlite_callback_t)(void *, int, char **, char **); + +sqlite3 *get_global_db(); + +int db_sqlite_init(const char *dbpath); + +void db_sqlite_finish(void); + +int db_sqlite_request(const char *stmt); + +int db_sqlite_request_callback(const char *stmt, + sqlite_callback_t callback, void *data); + +#endif diff --git a/src/image/embedded/embedded_config_merge.c b/src/image/embedded/embedded_config_merge.c new file mode 100644 index 0000000..6bf2878 --- /dev/null +++ b/src/image/embedded/embedded_config_merge.c @@ -0,0 +1,223 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide embedded image merge config + ******************************************************************************/ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include + +#include "securec.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "oci_runtime_spec.h" +#include "embedded_manifest.h" +#include "specs_extend.h" +#include "specs_mount.h" +#include "lim.h" +#include "mediatype.h" +#include "embedded_config_merge.h" + +int merge_env_config(oci_runtime_spec *oci_spec, + embedded_manifest **manifest) +{ + if ((*manifest)->config->env && (*manifest)->config->env_len != 0) { + int ret = merge_env(oci_spec, (const char **)(*manifest)->config->env, (*manifest)->config->env_len); + if (ret != 0) { + ERROR("Failed to merge environment variables"); + return -1; + } + } + return 0; +} + +int replace_cmds_config(container_custom_config *custom_spec, + embedded_manifest **manifest) +{ + embedded_config *config = (*manifest)->config; + + if (config->entrypoint && custom_spec->entrypoint_len == 0) { + int ret = dup_array_of_strings((const char **)config->entrypoint, config->entrypoint_len, + &(custom_spec->entrypoint), &(custom_spec->entrypoint_len)); + if (ret != 0) { + ERROR("Failed to duplicate entrypoint from manifest"); + return -1; + } + custom_spec->entrypoint_len = (*manifest)->config->entrypoint_len; + } + return 0; +} + +int merge_config(oci_runtime_spec *oci_spec, + container_custom_config *custom_spec, + const char *image_config, + embedded_manifest **manifest) +{ + if ((*manifest)->config != NULL) { + if ((*manifest)->config->workdir != NULL) { + free(oci_spec->process->cwd); + oci_spec->process->cwd = util_strdup_s((*manifest)->config->workdir); + } + + if (merge_env_config(oci_spec, manifest) != 0) { + return -1; + } + + return replace_cmds_config(custom_spec, manifest); + } + return 0; +} + +int pre_deal_config(oci_runtime_spec *oci_spec, + container_custom_config *custom_spec, + const char *image_config, + embedded_manifest **manifest, + char **config_path, + char **err) +{ + bool param_error = (oci_spec == NULL || image_config == NULL); + if (param_error) { + ERROR("invalid NULL param"); + return -1; + } + + *manifest = embedded_manifest_parse_data(image_config, 0, err); + if (*manifest == NULL) { + ERROR("parse manifest failed: %s", *err); + return -1; + } + + if (merge_config(oci_spec, custom_spec, image_config, manifest) != 0) { + return -1; + } + + int ret = lim_query_image_data((*manifest)->image_name, IMAGE_DATA_TYPE_CONFIG_PATH, config_path, NULL); + if (ret != 0) { + ERROR("query config path for image %s failed", (*manifest)->image_name); + return -1; + } + return 0; +} + +int gen_abs_path(embedded_manifest **manifest, char **abs_path, char *config_path, char *real_path, int i) +{ + /* change source to absolute path */ + if ((*manifest)->layers[i]->path_in_host[0] == '/') { + (*abs_path) = util_strdup_s((*manifest)->layers[i]->path_in_host); + } else { + (*abs_path) = util_add_path(config_path, (*manifest)->layers[i]->path_in_host); + } + if ((*abs_path) == NULL) { + ERROR("add path %s and %s failed", config_path, + (*manifest)->layers[i]->path_in_host); + return -1; + } + + if (strlen(*abs_path) > PATH_MAX || realpath(*abs_path, real_path) == NULL) { + ERROR("get real path of %s failed", *abs_path); + return -1; + } + return 0; +} + +int gen_one_mount(embedded_manifest *manifest, char *mount, char *real_path, int i) +{ + int nret = 0; + if (manifest->layers[i]->media_type == NULL) { + ERROR("Unknown media type"); + return -1; + } + if (strcmp(manifest->layers[i]->media_type, MediaTypeEmbeddedLayerSquashfs) == 0) { + nret = sprintf_s(mount, PATH_MAX * 3, + "type=squashfs,ro=true,src=%s,dst=%s", + real_path, manifest->layers[i]->path_in_container); + } else { + nret = sprintf_s(mount, PATH_MAX * 3, + "type=bind,ro=true,bind-propagation=rprivate,src=%s,dst=%s", + real_path, manifest->layers[i]->path_in_container); + } + if (nret < 0) { + ERROR("print string for mounts failed"); + return -1; + } + return 0; +} + +int embedded_image_merge_config(oci_runtime_spec *oci_spec, + container_custom_config *custom_spec, + const char *image_config) +{ + int ret = 0; + char *err = NULL; + embedded_manifest *manifest = NULL; + int i = 0; + char **mounts = NULL; + size_t cap = 0; + char *config_path = NULL; + char *abs_path = NULL; + + if (pre_deal_config(oci_spec, custom_spec, image_config, &manifest, &config_path, &err) != 0) { + ret = -1; + goto out; + } + + ret = util_grow_array(&mounts, &cap, manifest->layers_len, + manifest->layers_len); + if (ret != 0) { + ERROR("grow array failed"); + ret = -1; + goto out; + } + /* First layer is used for rootfs, so begin from 1 */ + for (i = 1; i < (int)manifest->layers_len; i++) { + char real_path[PATH_MAX] = { 0 }; /* Init to zero every time loop enter here */ + + /* PATH_MAX * 3: one for src and one for dst, the left + * PATH_MAX is enough for other parameters */ + mounts[i - 1] = util_common_calloc_s(PATH_MAX * 3); + if (mounts[i - 1] == NULL) { + ret = -1; + goto out; + } + + if (gen_abs_path(&manifest, &abs_path, config_path, real_path, i) != 0) { + ret = -1; + goto out; + } + + UTIL_FREE_AND_SET_NULL(abs_path); + if (gen_one_mount(manifest, mounts[i - 1], real_path, i) != 0) { + ret = -1; + goto out; + } + } + + ret = merge_volumes(oci_spec, mounts, util_array_len(mounts), NULL, parse_mount); + if (ret) { + ERROR("Failed to merge layer into mounts"); + goto out; + } + +out: + free(err); + util_free_array(mounts); + free_embedded_manifest(manifest); + free(abs_path); + UTIL_FREE_AND_SET_NULL(config_path); + return ret; +} + + diff --git a/src/image/embedded/embedded_config_merge.h b/src/image/embedded/embedded_config_merge.h new file mode 100644 index 0000000..323e964 --- /dev/null +++ b/src/image/embedded/embedded_config_merge.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide embedded image merge config definition + ******************************************************************************/ +#ifndef __EMBEDDED_IMAGE_MERGE_CONFIG_H_ +#define __EMBEDDED_IMAGE_MERGE_CONFIG_H_ + +#include "oci_image_spec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int embedded_image_merge_config(oci_runtime_spec *oci_spec, + container_custom_config *custom_spec, + const char *image_config); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/embedded/embedded_image.c b/src/image/embedded/embedded_image.c new file mode 100644 index 0000000..6a27725 --- /dev/null +++ b/src/image/embedded/embedded_image.c @@ -0,0 +1,365 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Explanation: provide image functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "containers_store.h" +#include "specs_extend.h" +#include "log.h" +#include "securec.h" +#include "embedded_image.h" +#include "lim.h" +#include "embedded_config_merge.h" +#include "db_all.h" +#include "utils.h" + +static bool embedded_image_exist(const char *image_name) +{ + bool ret = false; + int nret = 0; + struct db_image *imginfo = NULL; + + nret = db_read_image(image_name, &imginfo); + if (nret != 0) { + WARN("can't find image %s in database", image_name); + goto out; + } + + ret = true; + +out: + db_image_free(&imginfo); + return ret; +} + +bool embedded_detect(const char *image_name) +{ + if (image_name == NULL && !util_valid_embedded_image_name(image_name)) { + WARN("invalid image name %s", image_name); + lcrd_set_error_message("Invalid image name '%s'", image_name); + return false; + } + + return embedded_image_exist(image_name); +} + +char *embedded_resolve_image_name(const char *image_name) +{ + return util_strdup_s(image_name); +} +int embedded_filesystem_usage(struct bim *bim, imagetool_fs_info **fs_usage) +{ + return 0; +} + +int embedded_prepare_rf(struct bim *bim, const json_map_string_string *storage_opt, char **real_rootfs) +{ + return lim_create_rw_layer(bim->image_name, bim->container_id, NULL, real_rootfs); +} + +int embedded_mount_rf(struct bim *bim) +{ + return 0; +} + +int embedded_umount_rf(struct bim *bim) +{ + return 0; +} + +int embedded_delete_rf(struct bim *bim) +{ + return 0; +} + +static int do_merge_embedded_image_conf(oci_runtime_spec *oci_spec, + container_custom_config *custom_spec, + const char *image_name) +{ + int ret = 0; + char *image_config = NULL; + char *image_type = NULL; + + if (oci_spec == NULL || image_name == NULL) { + ERROR("invalid NULL param"); + return -1; + } + + ret = lim_query_image_data(image_name, IMAGE_DATA_TYPE_CONFIG, &image_config, &image_type); + if (ret != 0 || image_config == NULL || image_type == NULL) { + ERROR("query image data for image %s failed", image_name); + goto out; + } + + if (strcmp(image_type, IMAGE_TYPE_EMBEDDED) == 0) { + ret = embedded_image_merge_config(oci_spec, custom_spec, image_config); + if (ret != 0) { + goto out; + } + } else { + ERROR("unsupported image type %s", image_type); + ret = -1; + goto out; + } + +out: + free(image_config); + free(image_type); + + return ret; +} + + +int embedded_merge_conf(oci_runtime_spec *oci_spec, const host_config *host_spec, container_custom_config *custom_spec, + struct bim *bim, char **real_rootfs) +{ + int ret = 0; + int nret = 0; + + nret = embedded_prepare_rf(bim, host_spec->storage_opt, real_rootfs); + if (nret != 0) { + return nret; + } + + if (merge_user("/", oci_spec, host_spec, custom_spec->user)) { + ERROR("Failed to merge user: %s", custom_spec->user); + ret = -1; + goto umount; + } + + nret = do_merge_embedded_image_conf(oci_spec, custom_spec, bim->image_name); + if (nret != 0) { + ret = nret; + goto umount; + } + +umount: + nret = embedded_umount_rf(bim); + if (nret != 0) { + ret = nret; + } + return ret; +} + +int embedded_get_user_conf(const char *basefs, host_config *hc, const char *userstr, + oci_runtime_spec_process_user *puser) +{ + return 0; +} + +static int embedded_images_to_imagetool_images(struct db_all_images *all_images, + imagetool_images_list *list) +{ + int ret = 0; + size_t images_num = 0; + size_t i = 0; + struct db_image *tmp_embedded = NULL; + + images_num = all_images->imagesnum; + if (images_num == 0) { + goto out; + } + + if (images_num >= (SIZE_MAX / sizeof(imagetool_image *))) { + ERROR("Too many images, out of memory"); + ret = -1; + lcrd_try_set_error_message("Get too many images info, out of memory"); + goto out; + } + list->images = util_common_calloc_s(sizeof(imagetool_image *) * images_num); + if (list->images == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + for (i = 0; i < images_num; i++) { + tmp_embedded = all_images->images_info[i]; + + list->images[i] = util_common_calloc_s(sizeof(imagetool_image)); + if (list->images[i] == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + list->images_len++; + + /* NOTE: embedded image do not have id, use config digest as image id, + * but can not use this id to manage the image or run container. + */ + if (tmp_embedded->config_digest != NULL) { + const char *psha = strstr(tmp_embedded->config_digest, SHA256_PREFIX); + if (psha != NULL) { + list->images[i]->id = util_strdup_s(psha + strlen(SHA256_PREFIX)); + } else { + list->images[i]->id = util_strdup_s(tmp_embedded->config_digest); + } + } + + list->images[i]->repo_tags = util_common_calloc_s(sizeof(char *)); + if (list->images[i]->repo_tags == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + list->images[i]->repo_tags[0] = util_strdup_s(tmp_embedded->image_name); + list->images[i]->repo_tags_len++; + + list->images[i]->repo_digests = util_common_calloc_s(sizeof(char *)); + if (list->images[i]->repo_digests == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + list->images[i]->repo_digests[0] = util_strdup_s(tmp_embedded->config_digest); + list->images[i]->repo_digests_len++; + + list->images[i]->size = (uint64_t)tmp_embedded->size; + + list->images[i]->created = util_strdup_s(tmp_embedded->created); + } + +out: + return ret; +} + +int embedded_list_images(im_list_request *request, imagetool_images_list **list) +{ + int ret = 0; + struct db_all_images *all_images = NULL; + + *list = util_common_calloc_s(sizeof(imagetool_images_list)); + if (*list == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + + if (request->filter.image.image != NULL) { + INFO("Embedded images do not support filter"); + ret = 0; + goto out; + } + + ret = lim_query_images(&all_images); + /* no images found, success should be returned */ + if (ret == EIMAGENOTFOUND) { + INFO("No image Found"); + ret = 0; + goto out; + } else if (ret != 0 || all_images == NULL || all_images->imagesnum == 0) { + ERROR("Get image info failed"); + ret = -1; + lcrd_try_set_error_message("Get image info failed"); + goto out; + } + + ret = embedded_images_to_imagetool_images(all_images, *list); + if (ret != 0) { + ERROR("Failed to translate embedded images to imagetool images"); + ret = -1; + lcrd_try_set_error_message("Failed to translate embedded images to imagetool images"); + goto out; + } + +out: + if (ret != 0) { + free_imagetool_images_list(*list); + *list = NULL; + } + if (all_images != NULL) { + db_all_imginfo_free(all_images); + } + + return ret; +} + +int embedded_remove_image(im_remove_request *request) +{ + bool force = false; + char *image_ref = NULL; + int ret = 0; + size_t i = 0; + size_t container_num = 0; + container_t **conts = NULL; + + force = request->force; + image_ref = request->image.image; + + if (!force) { + ret = containers_store_list(&conts, &container_num); + if (ret != 0) { + ERROR("query all containers info failed"); + ret = -1; + goto out; + } + /* check if container is using this image */ + for (i = 0; i < container_num; i++) { + if (ret != 0) { + goto unref_continue; + } + if (conts[i]->common_config->image == NULL) { + goto unref_continue; + } + + if (strcmp(conts[i]->common_config->image, image_ref) == 0) { + ERROR("unable to remove image %s, container %s is using it", + image_ref, conts[i]->common_config->id); + lcrd_set_error_message("Image is in use"); + ret = EIMAGEBUSY; + goto unref_continue; + } +unref_continue: + container_unref(conts[i]); + continue; + } + if (ret != 0) { + ret = -1; + goto out; + } + } + + ret = lim_delete_image(image_ref, force); + +out: + free(conts); + return ret; +} + +int embedded_inspect_image(struct bim *bim, char **inspected_json) +{ + char *image_ref = NULL; + + if (bim == NULL || inspected_json == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + image_ref = bim->image_name; + + return lim_query_image_data(image_ref, IMAGE_DATA_TYPE_CONFIG, inspected_json, NULL); +} + +int embedded_init(const char *rootpath) +{ + return lim_init(rootpath); +} + diff --git a/src/image/embedded/embedded_image.h b/src/image/embedded/embedded_image.h new file mode 100644 index 0000000..4bab2ab --- /dev/null +++ b/src/image/embedded/embedded_image.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Explanation: provide image function definition + ******************************************************************************/ +#ifndef __EMBEDDED_IMAGE_H +#define __EMBEDDED_IMAGE_H + +#include +#include "image.h" + +bool embedded_detect(const char *image_name); + +int embedded_prepare_rf(struct bim *bim, const json_map_string_string *storage_opt, char **real_rootfs); + +int embedded_filesystem_usage(struct bim *bim, imagetool_fs_info **fs_usage); + +int embedded_mount_rf(struct bim *bim); + +int embedded_umount_rf(struct bim *bim); + +int embedded_delete_rf(struct bim *bim); + +char *embedded_resolve_image_name(const char *image_name); + +int embedded_merge_conf(oci_runtime_spec *oci_spec, const host_config *host_spec, container_custom_config *custom_spec, + struct bim *bim, char **real_rootfs); + +int embedded_get_user_conf(const char *basefs, host_config *hc, const char *userstr, + oci_runtime_spec_process_user *puser); + +int embedded_list_images(im_list_request *request, + imagetool_images_list **list); + +int embedded_remove_image(im_remove_request *request); + +int embedded_inspect_image(struct bim *bim, char **inspected_json); + +int embedded_load_image(im_load_request *request); + +int embedded_init(const char *rootpath); + +#endif diff --git a/src/image/embedded/lim.c b/src/image/embedded/lim.c new file mode 100644 index 0000000..3d49987 --- /dev/null +++ b/src/image/embedded/lim.c @@ -0,0 +1,833 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide image list functions + ******************************************************************************/ +#include +#include +#include + +#include "error.h" +#include "log.h" +#include "lim.h" +#include "liblcrd.h" +#include "securec.h" +#include "mediatype.h" +#include "snapshot.h" +#include "snapshot_def.h" +#include "embedded_manifest.h" +#include "db_all.h" +#include "path.h" +#include "image.h" +#include "utils_verify.h" + +/* lim init */ +int lim_init(const char *rootpath) +{ + int ret = 0; + + if (rootpath == NULL) { + ERROR("invalid NULL param"); + return -1; + } + + /* Param driver_type is reserved for later implement. */ + ret = snapshot_init(DRIVER_TYPE_INVALID); + if (ret != 0) { + ERROR("init driver failed"); + ret = -1; + goto out; + } + + ret = db_all_init(); + if (ret != 0) { + ERROR("init database failed"); + ret = -1; + goto out; + } + + ret = db_delete_dangling_images(); + if (ret != 0) { + ERROR("delete dangling images failed"); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* free image creator */ +void free_image_creator(struct image_creator **ic) +{ + if (ic != NULL && *ic != NULL) { + UTIL_FREE_AND_SET_NULL((*ic)->type); + UTIL_FREE_AND_SET_NULL((*ic)->name); + UTIL_FREE_AND_SET_NULL((*ic)->media_type); + UTIL_FREE_AND_SET_NULL((*ic)->config_digest); + free(*ic); + *ic = NULL; + } + return; +} + +/* lim create image start */ +int lim_create_image_start(char *name, char *type, struct image_creator **pic) +{ + struct image_creator *ic = NULL; + int ret = 0; + + if (type == NULL || pic == NULL) { + ERROR("invalid NULL param"); + return EINVALIDARGS; + } + + ic = (struct image_creator *) util_common_calloc_s(sizeof(struct image_creator)); + if (ic == NULL) { + ERROR("out of memory"); + ret = -1; + goto out; + } + + if (strncmp(type, IMAGE_TYPE_EMBEDDED, strlen(IMAGE_TYPE_EMBEDDED)) == 0) { + ic->type = util_strdup_s(type); + } else { + ERROR("invalid image type %s", type); + ret = EINVALIDARGS; + goto out; + } + + /* assign only when success */ + *pic = ic; + +out: + if (ret != 0) { + free_image_creator(&ic); + } + + return ret; +} + +/* valid relative path */ +static bool valid_relative_path(char *path) +{ + if (path == NULL) { + ERROR("invalid NULL path"); + return false; + } + + if (path[0] == 0) { + ERROR("invalid empty path"); + return false; + } + + if (path[0] == '/') { + ERROR("path %s not a relative path", path); + return false; + } + + return true; +} + +/* valid absolute path */ +static bool valid_absolute_path(char *path) +{ + if (path == NULL) { + ERROR("invalid NULL path"); + return false; + } + + if (path[0] == 0) { + ERROR("invalid empty path"); + return false; + } + + if (path[0] != '/') { + ERROR("path %s not a absolute path", path); + return false; + } + + return true; +} + +/* validate layer path in container */ +static bool validate_layer_path_in_container(size_t layer_index, char *path) +{ + /* layer 0 does not contains path_in_container and digest, make sure it's empty. */ + if (layer_index == 0) { + if (path != NULL && path[0] != 0) { + ERROR("first layer's path in container must be empty, got %s", path); + lcrd_try_set_error_message("Invalid content in manifest: first layer path in container must be empty"); + return false; + } + return true; + } + + if (!valid_absolute_path(path)) { + ERROR("path in container %s should be absolute path, layer %llu", path, + (unsigned long long)layer_index); + lcrd_try_set_error_message("Invalid content in manifest:" + " layer path in container(except first layer) must be absolute path"); + return false; + } + return true; +} + +/* validate layer path in host real */ +static bool validate_layer_path_in_host_real(size_t layer_index, + char *path_in_host, char *real_path, uint32_t fmod) +{ + if (!util_file_exists(real_path)) { + ERROR("file not exist, path in host %s, real path is %s", + path_in_host, real_path); + lcrd_try_set_error_message("Invalid content in manifest: layer not exists"); + return false; + } + + if (!util_valid_file(real_path, fmod)) { + ERROR("invalid path in host %s, real path is %s, layer %u", + path_in_host, real_path, layer_index); + if (fmod == (uint32_t)S_IFREG) { + lcrd_try_set_error_message("Invalid content in manifest: layer(except first layer) is not a regular file"); + } else if ((int)fmod == S_IFDIR) { + lcrd_try_set_error_message("Invalid content in manifest: layer(except first layer) is not a directory"); + } else if ((int)fmod == S_IFBLK) { + lcrd_try_set_error_message("Invalid content in manifest: layer is not block device"); + } + return false; + } + return true; +} + +/* validate layer path in host */ +static bool validate_layer_path_in_host(size_t layer_index, const char *location, + char *path_in_host, char *real_path, uint32_t fmod) +{ + char *abs_path = NULL; + if (layer_index == 0) { + /* layer 0 is absolute path of rootfs device or host / */ + if (!valid_absolute_path(path_in_host)) { + ERROR("path in host %s not a absolute path, layer %u", path_in_host, + layer_index); + lcrd_try_set_error_message("Invalid content in manifest: first layer path in host must be absolute path"); + return false; + } + + if ((int)fmod == S_IFDIR && strcmp(path_in_host, "/") != 0) { + ERROR("expected / as root, got %s, layer %u", path_in_host, + layer_index); + lcrd_try_set_error_message("Invalid content in manifest: first layer path in host must be /"); + return false; + } + abs_path = util_strdup_s(path_in_host); + /* other layers must be relative path of squashfs image file */ + } else { + char *tmp_path = NULL; + char parent_location[PATH_MAX] = { 0 }; + int sret = 0; + if (!valid_relative_path(path_in_host)) { + ERROR("path in host %s not a relative path, layer %u", path_in_host, layer_index); + lcrd_try_set_error_message("Invalid content in manifest:" + " layer path in host(except first layer) must be relative path"); + return false; + } + abs_path = util_add_path(location, path_in_host); + sret = sprintf_s(parent_location, sizeof(parent_location), "%s/..", location); + if (sret < 0 || sret >= (int)sizeof(parent_location)) { + ERROR("Failed to sprintf parent_location"); + lcrd_try_set_error_message("Failed to sprintf parent_location"); + UTIL_FREE_AND_SET_NULL(abs_path); + UTIL_FREE_AND_SET_NULL(tmp_path); + return false; + } + tmp_path = follow_symlink_in_scope(abs_path, parent_location); + if (tmp_path == NULL || !strncmp(tmp_path, "..", 2)) { + ERROR("invalid layer path %s", path_in_host); + lcrd_try_set_error_message("Invalid content in manifest: layer not exists"); + UTIL_FREE_AND_SET_NULL(abs_path); + UTIL_FREE_AND_SET_NULL(tmp_path); + return false; + } + UTIL_FREE_AND_SET_NULL(tmp_path); + } + + if (strlen(abs_path) > PATH_MAX || realpath(abs_path, real_path) == NULL) { + ERROR("invalid layer path %s", abs_path); + lcrd_try_set_error_message("Invalid content in manifest: layer not exists"); + UTIL_FREE_AND_SET_NULL(abs_path); + return false; + } + UTIL_FREE_AND_SET_NULL(abs_path); + return validate_layer_path_in_host_real(layer_index, path_in_host, real_path, fmod); +} + +/* validate layer media type */ +static bool validate_layer_media_type(size_t layer_index, char *media_type, + uint32_t *fmod) +{ + if (media_type != NULL) { + if (strcmp(media_type, MediaTypeEmbeddedLayerSquashfs) == 0) { + // first layer is block device, others are regular files. + *fmod = layer_index ? S_IFREG : S_IFBLK; + return true; + } + if (strcmp(media_type, MediaTypeEmbeddedLayerDir) == 0) { + *fmod = S_IFDIR; + return true; + } + } + + lcrd_try_set_error_message("Invalid content in manifest: layer's media type must be" + " application/squashfs.image.rootfs.diff.img or application/bind.image.rootfs.diff.dir"); + ERROR("invalid layer media type %s", media_type); + return false; +} + +/* validate layer digest */ +static bool validate_layer_digest(size_t layer_index, char *path, uint32_t fmod, + char *digest) +{ + /* If no digest, do not check digest. Digest is optinal. */ + if (digest == NULL) { + return true; + } + if (!digest[0]) { + return true; + } + + // first layer's digest must be empty + if (layer_index == 0) { + lcrd_try_set_error_message("Invalid content in manifest: first layer's digest must be empty"); + ERROR("first layer's digest must be empty, got %s", digest); + } + + /* If layer is a directory, digest must be empty */ + if ((int)fmod == S_IFDIR) { + ERROR("Invalid digest %s, digest must be empty if media type is %s", digest, + MediaTypeEmbeddedLayerDir); + lcrd_try_set_error_message("Invalid content in mainfest: layer digest must be empty if mediaType is %s", + MediaTypeEmbeddedLayerDir); + return false; + } + + /* check if digest format is valid */ + if (!util_valid_digest(digest)) { + ERROR("invalid digest %s for layer", digest); + lcrd_try_set_error_message("Invalid content in mainfest: layer(except first layer) has invalid digest"); + return false; + } + + /* calc and check digest */ + if (!util_valid_digest_file(path, digest)) { + lcrd_try_set_error_message("Invalid content in mainfest: layer(except first layer) has invalid digest"); + return false; + } + + return true; +} + +/* validate layer host files */ +static bool validate_layer_host_files(size_t layer_index, const char *location, + embedded_layers *layer) +{ + uint32_t fmod; + char real_path[PATH_MAX] = { 0 }; + + if (layer == NULL) { + return false; + } + + if (!validate_layer_media_type(layer_index, layer->media_type, &fmod)) { + return false; + } + + if (!validate_layer_path_in_host(layer_index, location, layer->path_in_host, + real_path, fmod)) { + return false; + } + + return validate_layer_digest(layer_index, real_path, fmod, layer->digest); +} + +/* validate layer size */ +static bool validate_layer_size(size_t layer_index, + embedded_layers *layer) +{ + if (layer == NULL) { + return false; + } + + if (layer->size < 0) { + ERROR("invalid layer size %lld, layer %llu", (long long)layer->size, (unsigned long long)layer_index); + lcrd_try_set_error_message("Invalid content in manifest: layer's size must not be negative number"); + return false; + } + return true; +} + +/* validate create time */ +static bool validate_create_time(char *created) +{ + if (!util_valid_time_tz(created)) { + ERROR("invalid created time %s, invalid format", created); + lcrd_try_set_error_message("Invalid content in manifest: invalid created time"); + return false; + } + + /* ensure time can be processed by us */ + if (time_tz_to_seconds_nanos(created, NULL, NULL)) { + ERROR("invalid created time %s, invalid time value", created); + lcrd_try_set_error_message("Invalid content in manifest: invalid created time"); + return false; + } + return true; +} + +/* validate image name */ +static bool validate_image_name(char *image_name) +{ + if (image_name == NULL) { + ERROR("image name not exist"); + lcrd_try_set_error_message("Invalid content in manfiest: image name not exist"); + return false; + } + + if (strcmp(image_name, "none") == 0 || + strcmp(image_name, "none:latest") == 0) { + ERROR("image name %s must not be none or none:latest", image_name); + lcrd_try_set_error_message("Image name 'none' or 'none:latest' in manifest is reserved, please use other name"); + return false; + } + + if (!util_valid_embedded_image_name(image_name)) { + ERROR("invalid image name %s", image_name); + lcrd_try_set_error_message("Invalid content in manfiest: invalid image name"); + return false; + } + return true; +} + +/* validate image layers number */ +static bool validate_image_layers_number(size_t layers_len) +{ + if (layers_len > LAYER_NUM_MAX || layers_len < 1) { + ERROR("invalid layers number %d maxium is %d", layers_len, LAYER_NUM_MAX); + lcrd_try_set_error_message("Invalid content in mainfest: layer empty or max depth exceeded"); + return false; + } + return true; +} + +/* valid embedded manifest */ +static bool valid_embedded_manifest(embedded_manifest *manifest, const char *path) +{ + size_t i = 0; + + if (manifest == NULL || path == NULL) { + ERROR("invalid NULL param"); + return false; + } + + if (!validate_image_layers_number(manifest->layers_len)) { + return false; + } + + if (manifest->schema_version != 1) { + ERROR("invalid schema version %u", manifest->schema_version); + lcrd_try_set_error_message("Invalid content in manifest: schema version must be 1"); + return false; + } + + if (manifest->media_type == NULL || strcmp(manifest->media_type, MediaTypeEmbeddedImageManifest) != 0) { + ERROR("invalid manifest media type %s", manifest->media_type); + lcrd_try_set_error_message("Invalid content in manifest:" + " manifest's media type must be application/embedded.manifest+json"); + return false; + } + + if (!validate_image_name(manifest->image_name)) { + return false; + } + + if (!validate_create_time(manifest->created)) { + return false; + } + + for (i = 0; i < manifest->layers_len; i++) { + if (!validate_layer_size(i, manifest->layers[i])) { + return false; + } + // valitate path_in_host, media_type and digest + if (!validate_layer_host_files(i, path, manifest->layers[i])) { + return false; + } + + if (!validate_layer_path_in_container(i, manifest->layers[i]->path_in_container)) { + return false; + } + } + + return true; +} + +static bool valid_manifest_and_get_size(embedded_manifest *manifest, const char *path, int64_t *image_size) +{ + size_t i = 0; + int64_t size = 0; + char real_path[PATH_MAX] = { 0 }; + char *abs_path = NULL; + bool result = false; + + if (!valid_embedded_manifest(manifest, path)) { + ERROR("check manifest valid failed"); + return false; + } + + for (i = 1; i < (int)manifest->layers_len; i++) { + abs_path = util_add_path(path, manifest->layers[i]->path_in_host); + if (strlen(abs_path) > PATH_MAX || !realpath(abs_path, real_path)) { + ERROR("invalid file path %s", abs_path); + lcrd_try_set_error_message("Invalid content in manifest: layer not exists"); + goto out; + } + UTIL_FREE_AND_SET_NULL(abs_path); + + size = util_file_size(real_path); + if (size < 0) { + lcrd_try_set_error_message("Calculate layer size failed"); + goto out; + } + + if (INT64_MAX - size < *image_size) { + ERROR("The layer size is too large!"); + lcrd_try_set_error_message("The layer size is too large!"); + goto out; + } + *image_size += size; + } + + result = true; + +out: + free(abs_path); + return result; +} + +/* lim add manifest */ +int lim_add_manifest(struct image_creator *ic, char *path, char *digest, bool mv) +{ + int ret = 0; + char *manifest_digest = NULL; + embedded_manifest *manifest = NULL; + parser_error err = NULL; + int64_t image_size = 0; + struct db_image imginfo = { 0 }; + + if (ic == NULL || path == NULL) { + ERROR("invalid NULL param"); + return EINVALIDARGS; + } + if (strcmp(ic->type, IMAGE_TYPE_EMBEDDED) != 0) { + ERROR("invalid image type %s", ic->type); + lcrd_try_set_error_message("Invalid image type: image type must be embedded"); + return EINVALIDARGS; + } + + /* calc and check digest */ + manifest_digest = util_full_file_digest(path); + if (manifest_digest == NULL) { + ERROR("calc full digest of %s failed", path); + lcrd_try_set_error_message("Invalid manifest: invalid digest"); + return -1; + } + + if (digest != NULL) { + if (strcmp(manifest_digest, digest) != 0) { + ERROR("file %s digest %s not match %s", path, manifest_digest, digest); + ret = EINVALIDARGS; + lcrd_try_set_error_message("Invalid manifest: invalid digest"); + goto out; + } + } + + manifest = embedded_manifest_parse_file(path, 0, &err); + if (manifest == NULL) { + ERROR("parse embedded manifest file %s failed", path); + ret = EINVALIDARGS; + lcrd_try_set_error_message("Invalid content in manifest: parse manifest as a json file failed"); + goto out; + } + + if (valid_manifest_and_get_size(manifest, path, &image_size) != true) { + ret = EINVALIDARGS; + goto out; + } + + imginfo.image_name = manifest->image_name; + imginfo.image_type = IMAGE_TYPE_EMBEDDED; + imginfo.size = image_size; + imginfo.layer_num = manifest->layers_len; + imginfo.top_chainid = ""; + imginfo.top_cacheid = ""; + /* manifest contains config. We use manifest as config, + * user should parse it when using config */ + imginfo.config_digest = manifest_digest; + imginfo.config_cacheid = ""; + imginfo.config_path = path; + imginfo.created = manifest->created; + /* layer 0 is used as rootfs in embedded image */ + imginfo.mount_string = manifest->layers[0]->path_in_host; + imginfo.config = util_read_text_file(path); + if (imginfo.config == NULL) { + ERROR("read manifest data failed"); + ret = -1; + goto out; + } + + ret = db_save_image(&imginfo); + if (ret != 0) { + ERROR("Failed to save the image to DB, ret is %d", ret); + if (ret == DB_NAME_CONFLICT) { + lcrd_try_set_error_message("Image name is conflicted in the database"); + ret = ENAMECONFLICT; + } else { + ret = -1; + } + goto out; + } + + INFO("Load image %s success", manifest->image_name); + +out: + free(imginfo.config); + free(manifest_digest); + free(err); + free_embedded_manifest(manifest); + + return ret; +} + +/* lim create image end */ +int lim_create_image_end(struct image_creator *ic) +{ + int ret = 0; + + if (ic == NULL) { + ERROR("invalid NULL param"); + return -1; + } + + if (strcmp(ic->type, IMAGE_TYPE_EMBEDDED) != 0) { + ERROR("invalid image type %u", ic->type); + return -1; + } + + free_image_creator(&ic); + + return ret; +} + +/* image type to driver type */ +uint32_t image_type_to_driver_type(const char *image_type) +{ + if (image_type == NULL) { + ERROR("invalid NULL param"); + return DRIVER_TYPE_INVALID; + } + + /* only support embedded currently */ + if (strcmp(image_type, IMAGE_TYPE_EMBEDDED) == 0) { + return DRIVER_TYPE_EMBEDDED; + } else { + return DRIVER_TYPE_INVALID; + } +} + +/* lim delete image */ +int lim_delete_image(char *name, bool force) +{ + struct db_image *imginfo = NULL; + int ret = 0; + + if (name == NULL) { + ERROR("invalid NULL param"); + return EINVALIDARGS; + } + + ret = db_read_image(name, &imginfo); + if (ret != 0) { + ERROR("can't find image %s in database", name); + lcrd_try_set_error_message("No such image:%s", name); + ret = EIMAGENOTFOUND; + goto out; + } + + /* delete image name from database */ + ret = db_delete_image(name, force); + if (ret < 0) { + ERROR("delete image %s in database failed", name); + ret = -1; + goto out; + } else if (ret == DB_DEL_NAME_ONLY) { /* no need to delete layers */ + DEBUG("delete image name %s only, no need to delete layers", name); + ret = 0; + goto out; + } else if (ret == DB_INUSE) { + ERROR("image %s is in use", name); + lcrd_try_set_error_message("Image is in use"); + ret = EIMAGEBUSY; + goto out; + } else if (ret == DB_NOT_EXIST) { + ERROR("image %s not exist", name); + lcrd_try_set_error_message("No such image:%s", name); + + ret = EIMAGENOTFOUND; + goto out; + } else { + INFO("Delete image %s success", name); + } + +out: + db_image_free(&imginfo); + + return ret; +} + +/* lim query images */ +int lim_query_images(void *images_info) +{ + struct db_all_images **info = (struct db_all_images **)images_info; + int ret = 0; + + if (info == NULL) { + ERROR("invalid NULL param"); + ret = -1; + goto out; + } + + ret = db_read_all_images_info(info); + if (ret == DB_NOT_EXIST) { + WARN("Failed to find image in database"); + ret = EIMAGENOTFOUND; + } else if (ret != 0) { + ERROR("Failed to find image in database"); + } +out: + + return ret; +} + +/* lim create rw layer */ +int lim_create_rw_layer(char *name, const char *id, char **options, + char **mount_string) +{ + int ret = 0; + struct db_image *imginfo = NULL; + uint32_t driver_type = DRIVER_TYPE_INVALID; + + if (name == NULL || id == NULL) { + ERROR("invalid NULL param"); + return EINVALIDARGS; + } + + ret = db_read_image(name, &imginfo); + if (ret != 0) { + ERROR("can't find image %s in database", name); + lcrd_try_set_error_message("No such image:%s", name); + ret = EIMAGENOTFOUND; + goto out; + } + + driver_type = image_type_to_driver_type(imginfo->image_type); + if (driver_type > DRIVER_TYPE_NUM) { + ERROR("get driver type from image type %s failed", imginfo->image_type); + ret = EINVALIDARGS; + goto out; + } + + if (mount_string != NULL) { + ret = snapshot_generate_mount_string(driver_type, imginfo, NULL, mount_string); + if (ret) { + ERROR("generate mount string failed"); + goto out; + } + } + +out: + db_image_free(&imginfo); + + return ret; +} + +static bool valid_param(const char *name, const char *type, char **data) +{ + if (name == NULL || type == NULL || data == NULL) { + return false; + } + + return true; +} + +/* lim query image data */ +int lim_query_image_data(const char *name, const char *type, + char **data, char **image_type) +{ + struct db_image *imginfo = NULL; + int ret = 0; + + if (valid_param(name, type, data) != true) { + ERROR("invalid NULL param"); + return -1; + } + + ret = db_read_image((char *)name, &imginfo); + if (ret != 0 || imginfo == NULL) { + ERROR("can't find image %s in database", name); + ret = -1; + lcrd_try_set_error_message("No such image:%s", name); + goto out; + } + + if (imginfo->image_type == NULL || imginfo->config_path == NULL || imginfo->config == NULL) { + ERROR("image info NULL"); + ret = -1; + goto out; + } + + if (strcmp(type, IMAGE_DATA_TYPE_CONFIG_PATH) == 0) { + *data = util_strdup_s(imginfo->config_path); + } else if (strcmp(type, IMAGE_DATA_TYPE_CONFIG) == 0) { + *data = util_strdup_s(imginfo->config); + + if (image_type != NULL) { + *image_type = util_strdup_s(imginfo->image_type); + } + } else { + ERROR("unsupported image data type %s", type); + ret = -1; + goto out; + } + +out: + if (imginfo != NULL) { + db_image_free(&imginfo); + } + + if (ret != 0) { + UTIL_FREE_AND_SET_NULL(*data); + if (image_type != NULL) { + UTIL_FREE_AND_SET_NULL(*image_type); + } + } + + return ret; +} diff --git a/src/image/embedded/lim.h b/src/image/embedded/lim.h new file mode 100644 index 0000000..6575993 --- /dev/null +++ b/src/image/embedded/lim.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide image list function definition + ******************************************************************************/ +#ifndef __LIM_H +#define __LIM_H + +#include +#include +#include + +#define IMAGE_DATA_TYPE_CONFIG "config" +#define IMAGE_DATA_TYPE_CONFIG_PATH "config_path" + +struct image_creator { + char *name; + char *type; + char *media_type; + char *config_digest; + int64_t size; +}; + +struct image_info { + char *image_name; /* image name */ + char *image_type; /* image type. docker or embedded */ + int64_t size; /* image sieze */ + char *chain_id; /* chain id of image's top layer */ + char *config_digest; /* sha256 digest of image's config */ +}; + +int lim_init(const char *rootpath); + +int lim_create_image_start(char *name, char *type, struct image_creator **pic); + +int lim_add_manifest(struct image_creator *ic, char *path, char *digest, bool mv); + +int lim_create_image_end(struct image_creator *ic); + +int lim_delete_image(char *name, bool force); + +int lim_query_images(void *images_info); + +int lim_query_image_data(const char *name, const char *type, + char **data, char **image_type); + +int lim_create_rw_layer(char *name, const char *id, char **options, + char **mount_string); + +#endif diff --git a/src/image/embedded/load.c b/src/image/embedded/load.c new file mode 100644 index 0000000..f4914a2 --- /dev/null +++ b/src/image/embedded/load.c @@ -0,0 +1,207 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide image load functions + ******************************************************************************/ +#include +#include + +#include "error.h" +#include "liblcrd.h" +#include "embedded_image.h" +#include "lim.h" +#include "limits.h" +#include "log.h" +#include "securec.h" +#include "image.h" + +#define RAW_DIGEST_LEN 64 + +/* return a new string which replace file's suffix to sgn */ +static char *replace_suffix_to_sgn(const char *file) +{ + char *sgn_file = NULL; + size_t i = 0; + size_t len = 0; + + if (file == NULL) { + ERROR("invalid NULL param"); + return NULL; + } + if (sizeof(".sgn") > SIZE_MAX - strlen(file)) { + return NULL; + } + len = strlen(file) + sizeof(".sgn"); + sgn_file = util_common_calloc_s(len); + if (sgn_file == NULL) { + ERROR("out of memory"); + return NULL; + } + + /* dump chars to sgn_file */ + if (strcat_s(sgn_file, len, file) != EOK) { + ERROR("strcat string failed"); + free(sgn_file); + return NULL; + } + + /* strip file's suffix */ + for (i = strlen(sgn_file); i > 0; i--) { + if (sgn_file[i] == '/') { + break; + } + + if (sgn_file[i] == '.') { + sgn_file[i] = 0; + break; + } + } + + /* add .sgn to tail as suffix */ + if (strcat_s(sgn_file, len, ".sgn") != EOK) { + ERROR("strcat string failed"); + free(sgn_file); + return NULL; + } + + return sgn_file; +} + +/* + * CloudRAN's file structure: + * /xxx/.../container_name.manifest + * /xxx/.../container_name.sgn + * /xxx/.../platform.img + * /xxx/.../app.img + * + * File container_name.sgn must exist. + * + * */ +static char *get_digest(const char *file) +{ + char *sgn_file = NULL; + char *digest = NULL; + size_t digest_len = RAW_DIGEST_LEN + strlen(SHA256_PREFIX); + char real_path[PATH_MAX] = { 0 }; + + if (file == NULL) { + ERROR("invalid NULL param"); + return NULL; + } + + sgn_file = replace_suffix_to_sgn(file); + if (sgn_file == NULL) { + ERROR("replace suffix to sgn failed"); + return NULL; + } + + if (strlen(sgn_file) > PATH_MAX || realpath(sgn_file, real_path) == NULL) { + ERROR("get real path of %s failed", sgn_file); + lcrd_try_set_error_message("Manifest's signature file not exist"); + goto out; + } + + if (!util_file_exists(real_path)) { + lcrd_try_set_error_message("Manifest's signature file not exist"); + goto out; + } + + digest = util_read_text_file(real_path); + if (digest == NULL) { + ERROR("read digest from file %s failed", real_path); + lcrd_try_set_error_message("Invalid manifest: invalid digest"); + goto out; + } + + if (strnlen(digest, digest_len + 1) != digest_len) { + DEBUG("digest %s too short", digest); + UTIL_FREE_AND_SET_NULL(digest); + lcrd_try_set_error_message("Invalid manifest: invalid digest"); + + goto out; + } + + digest[digest_len] = 0; /* strip '\n' or other chars if exists */ + +out: + + free(sgn_file); + + return digest; +} + +/* embedded load image */ +static int load_image(char *file) +{ + int ret = 0; + struct image_creator *ic = NULL; + char *digest = NULL; + char real_path[PATH_MAX] = { 0 }; + + if (file == NULL) { + ERROR("invalid NULL param"); + return EINVALIDARGS; + } + + if (strlen(file) > PATH_MAX || !realpath(file, real_path)) { + ERROR("invalid file path %s", file); + lcrd_try_set_error_message("Invalid manifest: manifest not found"); + return EINVALIDARGS; + } + + if (!util_file_exists(real_path)) { + ERROR("file %s not exist", file); + lcrd_try_set_error_message("Invalid manifest: manifest not found"); + return EINVALIDARGS; + } + + if (!util_valid_file(real_path, S_IFREG)) { + ERROR("manifest file %s is not a regular file", real_path); + lcrd_try_set_error_message("Invalid manifest: manifest is not a regular file"); + return EINVALIDARGS; + } + + ret = lim_create_image_start(NULL, IMAGE_TYPE_EMBEDDED, &ic); + if (ret != 0) { + goto out; + } + + /* Manifest must have it's corresponding sigature file, + * get digest from the sigature file */ + digest = get_digest(real_path); + if (digest == NULL) { + ret = EINVALIDARGS; + goto out; + } + + ret = lim_add_manifest(ic, real_path, digest, false); + if (ret != 0) { + goto out; + } + +out: + free(digest); + lim_create_image_end(ic); + + return ret; +} + +int embedded_load_image(im_load_request *request) +{ + if (request == NULL) { + ERROR("invalid NULL param"); + return EINVALIDARGS; + } + + return load_image(request->file); +} + diff --git a/src/image/embedded/snapshot/CMakeLists.txt b/src/image/embedded/snapshot/CMakeLists.txt new file mode 100644 index 0000000..88e8860 --- /dev/null +++ b/src/image/embedded/snapshot/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_snapshot_srcs) + +set(SNAPSHOT_SRCS + ${local_snapshot_srcs} + PARENT_SCOPE + ) diff --git a/src/image/embedded/snapshot/embedded.c b/src/image/embedded/snapshot/embedded.c new file mode 100644 index 0000000..e64daff --- /dev/null +++ b/src/image/embedded/snapshot/embedded.c @@ -0,0 +1,71 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container embedded functions + ******************************************************************************/ + + +#include "utils.h" +#include "linked_list.h" +#include "log.h" +#include "securec.h" +#include "snapshot_def.h" +#include "embedded.h" + +struct snapshot_plugin ebd_plugin() +{ + struct snapshot_plugin sp = { + .cl = ebd_create_layer, + .dl = ebd_delete_layer, + .ad = ebd_apply_diff, + .gms = ebd_generate_mount_string + }; + + return sp; +} + +int ebd_create_layer(char *id, char *parent, uint32_t layer_attribute, + char **options, char **mount_string) +{ + /* nothing to do */ + return 0; +} + +int ebd_delete_layer(char *id) +{ + /* nothing to do */ + return 0; +} + +int ebd_apply_diff(char *id, char *parent, char *archive, char *metadata) +{ + /* nothing to do */ + return 0; +} + +int ebd_generate_mount_string(struct db_image *imginfo, + struct db_sninfo **sninfos, char **mount_string) +{ + if (imginfo == NULL || mount_string == NULL) { + ERROR("invalid NULL param"); + return -1; + } + + if (imginfo->mount_string == NULL) { + ERROR("invalid NULL mount string"); + return -1; + } + + *mount_string = util_strdup_s(imginfo->mount_string); + + return 0; +} diff --git a/src/image/embedded/snapshot/embedded.h b/src/image/embedded/snapshot/embedded.h new file mode 100644 index 0000000..f1e4bd3 --- /dev/null +++ b/src/image/embedded/snapshot/embedded.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container embedded definition + ******************************************************************************/ + +#ifndef _SN_EMBEDDED_H +#define _SN_EMBEDDED_H + +#include "linked_list.h" +#include "snapshot_def.h" + +struct snapshot_plugin ebd_plugin(); + +int ebd_create_layer(char *id, char *parent, uint32_t layer_attribute, + char **options, char **mount_string); + +int ebd_delete_layer(char *id); + +int ebd_apply_diff(char *id, char *parent, char *archive, char *metadata); + +int ebd_generate_mount_string(struct db_image *imginfo, + struct db_sninfo **sninfos, char **mount_string); + +#endif diff --git a/src/image/embedded/snapshot/snapshot.c b/src/image/embedded/snapshot/snapshot.c new file mode 100644 index 0000000..b1dc6af --- /dev/null +++ b/src/image/embedded/snapshot/snapshot.c @@ -0,0 +1,75 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container snapshot functions + *******************************************************************************/ +#include "snapshot.h" + +#include "embedded.h" +#include "utils.h" +#include "log.h" + +struct snapshot_drivers { + struct snapshot_plugin plugins[DRIVER_TYPE_NUM]; +}; + +static struct snapshot_drivers *g_sd = NULL; + +/* snapshot_ nit */ +int snapshot_init(uint32_t driver_type) +{ + g_sd = (struct snapshot_drivers *) util_common_calloc_s(sizeof(struct snapshot_drivers)); + if (g_sd == NULL) { + ERROR("out of memory"); + return -1; + } + + g_sd->plugins[DRIVER_TYPE_EMBEDDED] = ebd_plugin(); + + return 0; +} + +/* check driver type valid */ +int check_driver_type_valid(uint32_t driver_type) +{ + int ret = 0; + + if (driver_type >= DRIVER_TYPE_NUM) { + ret = -1; + goto out; + } + + if (driver_type != DRIVER_TYPE_EMBEDDED) { + ERROR("only support driver type embedded, got %d", driver_type); + ret = -1; + goto out; + } + +out: + if (ret) { + ERROR("invalid driver type %d", driver_type); + } + + return ret; +} + +/* snapshot generate mount string */ +int snapshot_generate_mount_string(uint32_t driver_type, + struct db_image *imginfo, + struct db_sninfo **sninfos, char **mount_string) +{ + if (check_driver_type_valid(driver_type)) { + return -1; + } + + return g_sd->plugins[driver_type].gms(imginfo, sninfos, mount_string); +} diff --git a/src/image/embedded/snapshot/snapshot.h b/src/image/embedded/snapshot/snapshot.h new file mode 100644 index 0000000..4437d08 --- /dev/null +++ b/src/image/embedded/snapshot/snapshot.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container snapshot functions definition + *******************************************************************************/ + +#ifndef __SNAPSHOT_H +#define __SNAPSHOT_H + +#include +#include +#include + +#include "db_all.h" + +int snapshot_init(uint32_t driver_type); + +int snapshot_generate_mount_string(uint32_t driver_type, + struct db_image *imginfo, + struct db_sninfo **sninfos, char **mount_string); + +#endif diff --git a/src/image/embedded/snapshot/snapshot_def.h b/src/image/embedded/snapshot/snapshot_def.h new file mode 100644 index 0000000..28fe887 --- /dev/null +++ b/src/image/embedded/snapshot/snapshot_def.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container snapshot definition + ******************************************************************************/ +#ifndef __SNAPSHOT_DEF_H +#define __SNAPSHOT_DEF_H + +#include "db_all.h" + +#define DRIVER_TYPE_EMBEDDED 0 +#define DRIVER_TYPE_NUM 1 +#define DRIVER_TYPE_INVALID 1024 + +#define LAYER_ATTRIBUTE_RO 1 +#define LAYER_ATTRIBUTE_RW 2 + +#define LAYER_NUM_MAX 125 + +typedef int(*create_layer_cb)(char *id, char *parent, uint32_t layer_attribute, + char **options, char **mount_string); + +typedef int(*delete_layer_cb)(char *id); + +typedef int(*apply_diff_cb)(char *id, char *parent, char *archive, + char *metadata); + +typedef int(*generate_mount_string_cb)(struct db_image *imginfo, + struct db_sninfo **sninfos, char **mount_string); + +struct snapshot_plugin { + create_layer_cb cl; + delete_layer_cb dl; + apply_diff_cb ad; + generate_mount_string_cb gms; +}; + + +#endif diff --git a/src/image/external/CMakeLists.txt b/src/image/external/CMakeLists.txt new file mode 100644 index 0000000..a3db5ee --- /dev/null +++ b/src/image/external/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_external_srcs) + +set(EXTERNAL_SRCS + ${local_external_srcs} + PARENT_SCOPE + ) diff --git a/src/image/external/ext_image.c b/src/image/external/ext_image.c new file mode 100644 index 0000000..06ed52d --- /dev/null +++ b/src/image/external/ext_image.c @@ -0,0 +1,223 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide image functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "specs_extend.h" +#include "securec.h" +#include "ext_image.h" + +#ifdef ENABLE_OCI_IMAGE +#include "oci_image.h" +#include "oci_images_store.h" +#include "oci_config_merge.h" +#endif + +bool ext_detect(const char *image_name) +{ + if (image_name == NULL) { + return false; + } + + if (image_name[0] != '/') { + INFO("Rootfs should be absolutely path"); + return false; + } + + return util_file_exists(image_name); +} +int ext_filesystem_usage(struct bim *bim, imagetool_fs_info **fs_usage) +{ + return 0; +} + +int ext_prepare_rf(struct bim *bim, const json_map_string_string *storage_opt, char **real_rootfs) +{ + int ret = 0; + + if (real_rootfs != NULL) { + if (bim->image_name != NULL) { + char real_path[PATH_MAX] = { 0 }; + if (bim->image_name[0] != '/') { + ERROR("Rootfs should be absolutely path"); + lcrd_set_error_message("Rootfs should be absolutely path"); + return -1; + } + if (realpath(bim->image_name, real_path) == NULL) { + ERROR("Failed to clean rootfs path '%s': %s", bim->image_name, strerror(errno)); + lcrd_set_error_message("Failed to clean rootfs path '%s': %s", bim->image_name, strerror(errno)); + return -1; + } + *real_rootfs = util_strdup_s(real_path); + } else { + ERROR("Failed to get external rootfs"); + ret = -1; + } + } + return ret; +} + +int ext_mount_rf(struct bim *bim) +{ + return 0; +} + +int ext_umount_rf(struct bim *bim) +{ + return 0; +} + +int ext_delete_rf(struct bim *bim) +{ + return 0; +} + +char *ext_resolve_image_name(const char *image_name) +{ + return util_strdup_s(image_name); +} + +#ifdef ENABLE_OCI_IMAGE +int ext_merge_conf(oci_runtime_spec *oci_spec, const host_config *host_spec, container_custom_config *custom_spec, + struct bim *bim, char **real_rootfs) +{ + int ret = 0; + char *resolved_name = NULL; + oci_image_t *image_info = NULL; + + // Ensure rootfs is valid. + ret = ext_prepare_rf(bim, host_spec->storage_opt, real_rootfs); + if (ret != 0) { + return ret; + } + + ret = ext_umount_rf(bim); + if (ret != 0) { + return ret; + } + + // No config neeed merge if NULL. + if (bim->ext_config_image == NULL) { + ret = 0; + goto out; + } + + // Get image's config and merge configs. + resolved_name = oci_resolve_image_name(bim->ext_config_image); + if (resolved_name == NULL) { + ERROR("Resolve external config image name failed, image name is %s", bim->ext_config_image); + ret = -1; + goto out; + } + + image_info = oci_images_store_get(resolved_name); + if (image_info == NULL) { + ERROR("Get image from image store failed, image name is %s", resolved_name); + ret = -1; + goto out; + } + + ret = oci_image_merge_config(image_info->info, oci_spec, custom_spec); + +out: + free(resolved_name); + resolved_name = NULL; + + oci_image_unref(image_info); + image_info = NULL; + + return ret; +} +#else +int ext_merge_conf(oci_runtime_spec *oci_spec, const host_config *host_spec, container_custom_config *custom_spec, + struct bim *bim, char **real_rootfs) +{ + int ret = 0; + + // Ensure rootfs is valid. + ret = ext_prepare_rf(bim, host_spec->storage_opt, real_rootfs); + if (ret != 0) { + return ret; + } + + ret = ext_umount_rf(bim); + if (ret != 0) { + return ret; + } + + return ret; +} +#endif + +int ext_get_user_conf(const char *basefs, host_config *hc, const char *userstr, oci_runtime_spec_process_user *puser) +{ + if (basefs == NULL || puser == NULL) { + ERROR("Empty basefs or puser"); + return -1; + } + return get_user(basefs, hc, userstr, puser); +} + +int ext_list_images(im_list_request *request, imagetool_images_list **list) +{ + int ret = 0; + + *list = util_common_calloc_s(sizeof(imagetool_images_list)); + if (*list == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } +out: + return ret; +} + +int ext_remove_image(im_remove_request *request) +{ + return 0; +} + +int ext_inspect_image(struct bim *bim, char **inspected_json) +{ + return 0; +} + +int ext_load_image(im_load_request *request) +{ + return 0; +} + +int ext_login(im_login_request *request) +{ + return 0; +} + +int ext_logout(im_logout_request *request) +{ + return 0; +} + +int ext_init(const char *rootpath) +{ + return 0; +} diff --git a/src/image/external/ext_image.h b/src/image/external/ext_image.h new file mode 100644 index 0000000..cc05a92 --- /dev/null +++ b/src/image/external/ext_image.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Explanation: provide image function definition + ******************************************************************************/ +#ifndef __EXT_IMAGE_H +#define __EXT_IMAGE_H + +#include +#include "image.h" + +bool ext_detect(const char *image_name); +int ext_filesystem_usage(struct bim *bim, imagetool_fs_info **fs_usage); + +int ext_prepare_rf(struct bim *bim, const json_map_string_string *storage_opt, char **real_rootfs); +int ext_mount_rf(struct bim *bim); +int ext_umount_rf(struct bim *bim); +int ext_delete_rf(struct bim *bim); +char *ext_resolve_image_name(const char *image_name); + +int ext_merge_conf(oci_runtime_spec *oci_spec, const host_config *host_spec, container_custom_config *custom_spec, + struct bim *bim, char **real_rootfs); +int ext_get_user_conf(const char *basefs, host_config *hc, const char *userstr, oci_runtime_spec_process_user *puser); +int ext_list_images(im_list_request *request, imagetool_images_list **list); +int ext_remove_image(im_remove_request *request); +int ext_inspect_image(struct bim *bim, char **inspected_json); +int ext_load_image(im_load_request *request); +int ext_login(im_login_request *request); +int ext_logout(im_logout_request *request); + +int ext_init(const char *rootpath); + +#endif diff --git a/src/image/image.c b/src/image/image.c new file mode 100644 index 0000000..dd4ceec --- /dev/null +++ b/src/image/image.c @@ -0,0 +1,1173 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide image functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "securec.h" +#include "image.h" +#include "liblcrd.h" +#include "log.h" +#include "utils.h" + +#include "ext_image.h" + +#ifdef ENABLE_OCI_IMAGE +#include "oci_image.h" +#include "oci_image_status.h" +#include "oci_image_load.h" +#include "oci_login.h" +#include "oci_logout.h" +#endif + +#ifdef ENABLE_EMBEDDED_IMAGE +#include "embedded_image.h" +#include "db_all.h" + +/* embedded */ +static const struct bim_ops g_embedded_ops = { + .init = embedded_init, + .detect = embedded_detect, + + .prepare_rf = embedded_prepare_rf, + .mount_rf = embedded_mount_rf, + .umount_rf = embedded_umount_rf, + .delete_rf = embedded_delete_rf, + + .merge_conf = embedded_merge_conf, + .get_user_conf = embedded_get_user_conf, + + .list_ims = embedded_list_images, + .rm_image = embedded_remove_image, + .inspect_image = embedded_inspect_image, + .resolve_image_name = embedded_resolve_image_name, + .filesystem_usage = embedded_filesystem_usage, + .load_image = embedded_load_image, + .pull_image = NULL, +}; +#endif + +#ifdef ENABLE_OCI_IMAGE +/* oci */ +static const struct bim_ops g_oci_ops = { + .init = oci_init, + .detect = oci_detect, + + .prepare_rf = oci_prepare_rf, + .mount_rf = oci_mount_rf, + .umount_rf = oci_umount_rf, + .delete_rf = oci_delete_rf, + + .merge_conf = oci_merge_conf, + .get_user_conf = oci_get_user_conf, + + .list_ims = oci_list_images, + .rm_image = oci_remove_image, + .inspect_image = oci_inspect_image, + .resolve_image_name = oci_resolve_image_name, + .filesystem_usage = oci_filesystem_usage, + .load_image = oci_load_image, + .pull_image = oci_pull_image, + .login = oci_login, + .logout = oci_logout, +}; +#endif + +/* external */ +static const struct bim_ops g_ext_ops = { + .init = ext_init, + .detect = ext_detect, + + .prepare_rf = ext_prepare_rf, + .mount_rf = ext_mount_rf, + .umount_rf = ext_umount_rf, + .delete_rf = ext_delete_rf, + + .merge_conf = ext_merge_conf, + .get_user_conf = ext_get_user_conf, + + .list_ims = ext_list_images, + .rm_image = ext_remove_image, + .inspect_image = ext_inspect_image, + .resolve_image_name = ext_resolve_image_name, + .filesystem_usage = ext_filesystem_usage, + .load_image = ext_load_image, + .pull_image = NULL, + .login = ext_login, + .logout = ext_logout, +}; + +static const struct bim_type g_bims[] = { +#ifdef ENABLE_OCI_IMAGE + { + .image_type = IMAGE_TYPE_OCI, + .ops = &g_oci_ops, + }, +#endif + { .image_type = IMAGE_TYPE_EXTERNAL, .ops = &g_ext_ops }, +#ifdef ENABLE_EMBEDDED_IMAGE + { .image_type = IMAGE_TYPE_EMBEDDED, .ops = &g_embedded_ops }, +#endif +}; + +static const size_t g_numbims = sizeof(g_bims) / sizeof(struct bim_type); + +static const struct bim_type *bim_query(const char *image_name) +{ + size_t i; + char *temp = NULL; + + for (i = 0; i < g_numbims; i++) { + temp = g_bims[i].ops->resolve_image_name(image_name); + if (temp == NULL) { + lcrd_append_error_message("Failed to resovle image name%s", image_name); + return NULL; + } + int r = g_bims[i].ops->detect(temp); + + free(temp); + temp = NULL; + + if (r != 0) { + break; + } + } + + if (i == g_numbims) { + return NULL; + } + return &g_bims[i]; +} + +static const struct bim_type *get_bim_by_type(const char *image_type) +{ + size_t i; + + for (i = 0; i < g_numbims; i++) { + if (strcmp(g_bims[i].image_type, image_type) == 0) { + return &g_bims[i]; + } + } + + ERROR("Backing store %s unknown but not caught earlier\n", image_type); + return NULL; +} + +static void bim_put(struct bim *bim) +{ + if (bim == NULL) { + return; + } + + free(bim->image_name); + bim->image_name = NULL; + free(bim->ext_config_image); + bim->ext_config_image = NULL; + free(bim->container_id); + bim->container_id = NULL; + free(bim); +} + +static struct bim *bim_get(const char *image_type, const char *image_name, const char *ext_config_image, + const char *container_id) +{ + struct bim *bim = NULL; + const struct bim_type *q = NULL; + + if (image_type == NULL) { + return NULL; + } + + q = get_bim_by_type(image_type); + if (q == NULL) { + return NULL; + } + + bim = util_common_calloc_s(sizeof(struct bim)); + if (bim == NULL) { + return NULL; + } + + bim->ops = q->ops; + bim->type = q->image_type; + + if (image_name != NULL) { + bim->image_name = bim->ops->resolve_image_name(image_name); + if (bim->image_name == NULL) { + lcrd_append_error_message("Failed to resovle image name%s", bim->image_name); + bim_put(bim); + return NULL; + } + } + if (ext_config_image != NULL) { + bim->ext_config_image = util_strdup_s(ext_config_image); + if (bim->ext_config_image == NULL) { + lcrd_append_error_message("Failed to strdup external config image %s", bim->ext_config_image); + bim_put(bim); + return NULL; + } + } + if (container_id != NULL) { + bim->container_id = util_strdup_s(container_id); + } + return bim; +} + +int im_get_container_filesystem_usage(const char *image_type, const char *id, imagetool_fs_info **fs_usage) +{ + int ret = 0; + imagetool_fs_info *filesystemusage = NULL; + const struct bim_type *q = NULL; + struct bim *bim = NULL; + + if (image_type == NULL || id == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + q = get_bim_by_type(image_type); + if (q == NULL) { + ret = -1; + goto out; + } + + bim = util_common_calloc_s(sizeof(struct bim)); + if (bim == NULL) { + ret = -1; + goto out; + } + + bim->ops = q->ops; + bim->type = q->image_type; + + if (id != NULL) { + bim->container_id = util_strdup_s(id); + } + + ret = bim->ops->filesystem_usage(bim, &filesystemusage); + if (ret != 0) { + ERROR("Failed to get filesystem usage for container %s", id); + ret = -1; + goto out; + } + + *fs_usage = filesystemusage; + +out: + bim_put(bim); + return ret; +} + +int im_remove_container_rootfs(const char *image_type, const char *container_id) +{ + int ret = 0; + struct bim *bim = NULL; + + if (container_id == NULL || image_type == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + bim = bim_get(image_type, NULL, NULL, container_id); + if (bim == NULL) { + ERROR("Failed to init bim for container %s", container_id); + ret = -1; + goto out; + } + + ret = bim->ops->delete_rf(bim); + if (ret != 0) { + ERROR("Failed to delete rootfs for container %s", container_id); + ret = -1; + goto out; + } + +out: + bim_put(bim); + return ret; +} + +int im_umount_container_rootfs(const char *image_type, const char *image_name, const char *container_id) +{ + int ret = 0; + struct bim *bim = NULL; + + if (container_id == NULL || image_type == NULL || image_name == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + bim = bim_get(image_type, image_name, NULL, container_id); + if (bim == NULL) { + ERROR("Failed to init bim for container %s", container_id); + ret = -1; + goto out; + } + + ret = bim->ops->umount_rf(bim); + if (ret != 0) { + ERROR("Failed to umount rootfs for container %s", container_id); + ret = -1; + goto out; + } + +out: + bim_put(bim); + return ret; +} + +int im_mount_container_rootfs(const char *image_type, const char *image_name, const char *container_id) +{ + int ret = 0; + struct bim *bim = NULL; + + if (image_name == NULL || container_id == NULL || image_type == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + bim = bim_get(image_type, image_name, NULL, container_id); + if (bim == NULL) { + ERROR("Failed to init bim for container %s", container_id); + ret = -1; + goto out; + } + + ret = bim->ops->mount_rf(bim); + if (ret != 0) { + ERROR("Failed to mount rootfs for container %s", container_id); + ret = -1; + goto out; + } + +out: + bim_put(bim); + return ret; +} + +char *im_get_image_type(const char *image, const char *external_rootfs) +{ + const char *image_name = NULL; + const struct bim_type *bim_type = NULL; + + image_name = (external_rootfs != NULL) ? external_rootfs : image; + if (image_name == NULL) { + ERROR("Should specify the image name or external rootfs"); + return NULL; + } + + bim_type = bim_query(image_name); + if (bim_type == NULL) { + ERROR("Failed to query type of image %s", image_name); + lcrd_set_error_message("No such image:%s", image_name); + return NULL; + } + + return util_strdup_s(bim_type->image_type); +} + +bool im_config_image_exist(const char *image_name) +{ + const struct bim_type *bim_type = NULL; + + bim_type = bim_query(image_name); + if (bim_type == NULL) { + ERROR("Config image %s not exist", image_name); + lcrd_set_error_message("Image %s not exist", image_name); + return false; + } + + return true; +} + +int im_merge_image_config(const char *id, const char *image_type, const char *image_name, + const char *ext_config_image, oci_runtime_spec *oci_spec, + host_config *host_spec, container_custom_config *custom_spec, + char **real_rootfs) +{ + int ret = 0; + struct bim *bim = NULL; + + if (real_rootfs == NULL || oci_spec == NULL || image_type == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + bim = bim_get(image_type, image_name, ext_config_image, id); + if (bim == NULL) { + ERROR("Failed to init bim of image %s", image_name); + ret = -1; + goto out; + } + + ret = bim->ops->merge_conf(oci_spec, host_spec, custom_spec, bim, real_rootfs); + if (ret != 0) { + ERROR("Failed to merge image %s config, config image is %s", image_name, ext_config_image); + ret = -1; + goto out; + } + INFO("Use real rootfs: %s with type: %s", *real_rootfs, image_type); + +out: + bim_put(bim); + return ret; +} + +int im_get_user_conf(const char *image_type, const char *basefs, host_config *hc, const char *userstr, + oci_runtime_spec_process_user *puser) +{ + int ret = 0; + struct bim *bim = NULL; + + if (basefs == NULL || hc == NULL || image_type == NULL || puser == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + bim = bim_get(image_type, NULL, NULL, NULL); + if (bim == NULL) { + ERROR("Failed to init bim for image type: %s", image_type); + ret = -1; + goto out; + } + + ret = bim->ops->get_user_conf(basefs, hc, userstr, puser); + if (ret != 0) { + ERROR("Failed to get user config"); + ret = -1; + goto out; + } + +out: + bim_put(bim); + return ret; +} + +static int append_images_to_response(im_list_response *response, imagetool_images_list *images_in) +{ + int ret = 0; + size_t images_num = 0; + size_t old_num = 0; + imagetool_image **tmp = NULL; + size_t i = 0; + size_t new_size = 0; + size_t old_size = 0; + + if (images_in == NULL || response == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + if (response->images == NULL) { + response->images = util_common_calloc_s(sizeof(imagetool_images_list)); + if (response->images == NULL) { + ERROR("Memeory out"); + ret = -1; + goto out; + } + } + + images_num = images_in->images_len; + + // no images need to append + if (images_num == 0) { + goto out; + } + if (images_num > SIZE_MAX / sizeof(imagetool_image *) - response->images->images_len) { + ERROR("Too many images to append!"); + ret = -1; + goto out; + } + + old_num = response->images->images_len; + + new_size = (old_num + images_num) * sizeof(imagetool_image *); + old_size = old_num * sizeof(imagetool_image *); + ret = mem_realloc((void **)(&tmp), new_size, response->images->images, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for append images"); + ret = -1; + goto out; + } + response->images->images = tmp; + for (i = 0; i < images_num; i++) { + response->images->images[old_num + i] = images_in->images[i]; + images_in->images[i] = NULL; + images_in->images_len--; + response->images->images_len++; + } + +out: + return ret; +} + +int im_list_images(im_list_request *request, im_list_response **response) +{ + char *filter = NULL; + size_t i; + imagetool_images_list *images_tmp = NULL; + + filter = request->filter.image.image; + + *response = util_common_calloc_s(sizeof(im_list_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + EVENT("Event: {Object: list images, Type: listing, Filter: %s}", filter ? filter : ""); + + for (i = 0; i < g_numbims; i++) { + int ret = g_bims[i].ops->list_ims(request, &images_tmp); + if (ret != 0) { + ERROR("Failed to list all images with type:%s", g_bims[i].image_type); + continue; + } + ret = append_images_to_response(*response, images_tmp); + if (ret != 0) { + ERROR("Failed to append images with type:%s", g_bims[i].image_type); + } + free_imagetool_images_list(images_tmp); + images_tmp = NULL; + } + + EVENT("Event: {Object: list images, Type: listed, Filter: %s}", filter ? filter : ""); + + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + return 0; +} + +void free_im_list_request(im_list_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->filter.image.image); + ptr->filter.image.image = NULL; + + free(ptr); +} + +void free_im_list_response(im_list_response *ptr) +{ + if (ptr == NULL) { + return; + } + free_imagetool_images_list(ptr->images); + ptr->images = NULL; + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + +static bool check_im_pull_args(const im_pull_request *req, im_pull_response * const *resp) +{ + if (req == NULL || resp == NULL) { + ERROR("Request or response is NULL"); + return false; + } + if (req->image == NULL) { + ERROR("Empty image required"); + lcrd_set_error_message("Empty image required"); + return false; + } + return true; +} + +int im_pull_image(const im_pull_request *request, im_pull_response **response) +{ + int ret = -1; + struct bim *bim = NULL; + + if (!check_im_pull_args(request, response)) { + return ret; + } + + bim = bim_get(request->type, NULL, NULL, NULL); + if (bim == NULL) { + ERROR("Failed to init bim, image type: %s", request->type); + goto out; + } + + if (bim->ops->pull_image == NULL) { + WARN("Unimplements pull image in %s", bim->type); + ret = 0; + goto out; + } + + ret = bim->ops->pull_image(request, response); + if (ret != 0) { + ERROR("Pull image %s failed", request->image); + ret = -1; + goto out; + } + +out: + bim_put(bim); + return ret; +} + +void free_im_pull_request(im_pull_request *req) +{ + if (req == NULL) { + return; + } + free(req->type); + req->type = NULL; + free(req->image); + req->image = NULL; + free_sensitive_string(req->username); + req->username = NULL; + free_sensitive_string(req->password); + req->password = NULL; + free_sensitive_string(req->auth); + req->auth = NULL; + free_sensitive_string(req->server_address); + req->server_address = NULL; + free_sensitive_string(req->registry_token); + req->registry_token = NULL; + free_sensitive_string(req->identity_token); + req->identity_token = NULL; + free(req); +} + +void free_im_pull_response(im_pull_response *resp) +{ + if (resp == NULL) { + return; + } + free(resp->image_ref); + resp->image_ref = NULL; + free(resp->errmsg); + resp->errmsg = NULL; + free(resp); +} + +int im_load_image(im_load_request *request, im_load_response **response) +{ + int ret = -1; + struct bim *bim = NULL; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(im_load_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (request->file == NULL) { + ERROR("Load image requires image tarball file path"); + lcrd_set_error_message("Load image requires image tarball file path"); + goto pack_response; + } + + if (request->type == NULL) { + ERROR("Missing image type"); + lcrd_set_error_message("Missing image type"); + goto pack_response; + } + + bim = bim_get(request->type, NULL, NULL, NULL); + if (bim == NULL) { + ERROR("Failed to init bim, image type:%s", request->type); + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: loading}", request->file); + + ret = bim->ops->load_image(request); + if (ret != 0) { + ERROR("Failed to load image from %s with tag %s and type %s", request->file, request->tag, request->type); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: loaded}", request->file); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + bim_put(bim); + return ret; +} + +void free_im_load_request(im_load_request *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->file); + ptr->file = NULL; + + free(ptr->tag); + ptr->file = NULL; + + free(ptr->type); + ptr->type = NULL; + + free(ptr); +} + +void free_im_load_response(im_load_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + +int im_login(im_login_request *request, im_login_response **response) +{ + int ret = -1; + struct bim *bim = NULL; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(im_login_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (request->server == NULL) { + ERROR("Login requires server address"); + lcrd_set_error_message("Login requires server address"); + goto pack_response; + } + + if (request->type == NULL) { + ERROR("Login requires image type"); + lcrd_set_error_message("Login requires image type"); + goto pack_response; + } + + if (request->username == NULL || request->password == NULL) { + ERROR("Missing username or password"); + lcrd_set_error_message("Missing username or password"); + goto pack_response; + } + + bim = bim_get(request->type, NULL, NULL, NULL); + if (bim == NULL) { + ERROR("Failed to init bim, image type:%s", request->type); + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: logining}", request->server); + + ret = bim->ops->login(request); + if (ret != 0) { + ERROR("Failed to login %s", request->server); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: logined}", request->server); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + bim_put(bim); + return ret; +} + +void free_im_login_request(im_login_request *ptr) +{ + if (ptr == NULL) { + return; + } + + free_sensitive_string(ptr->username); + ptr->username = NULL; + + free_sensitive_string(ptr->password); + ptr->password = NULL; + + free(ptr->type); + ptr->type = NULL; + + free_sensitive_string(ptr->server); + ptr->server = NULL; + + free(ptr); +} + +void free_im_login_response(im_login_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + +int im_logout(im_logout_request *request, im_logout_response **response) +{ + int ret = -1; + struct bim *bim = NULL; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(im_logout_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (request->server == NULL) { + ERROR("Logout requires server address"); + lcrd_set_error_message("Logout requires server address"); + goto pack_response; + } + + if (request->type == NULL) { + ERROR("Logout requires image type"); + lcrd_set_error_message("Logout requires image type"); + goto pack_response; + } + + bim = bim_get(request->type, NULL, NULL, NULL); + if (bim == NULL) { + ERROR("Failed to init bim, image type:%s", request->type); + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: logouting}", request->server); + + ret = bim->ops->logout(request); + if (ret != 0) { + ERROR("Failed to logout %s", request->server); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: logouted}", request->server); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + bim_put(bim); + return ret; +} + +void free_im_logout_request(im_logout_request *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->type); + ptr->type = NULL; + + free(ptr->server); + ptr->server = NULL; + + free(ptr); +} + +void free_im_logout_response(im_logout_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + +int im_rm_image(im_remove_request *request, im_remove_response **response) +{ + int ret = -1; + char *image_ref = NULL; + const struct bim_type *bim_type = NULL; + struct bim *bim = NULL; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(im_remove_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (request->image.image == NULL) { + ERROR("remove image requires image ref"); + lcrd_set_error_message("remove image requires image ref"); + goto pack_response; + } + + image_ref = util_strdup_s(request->image.image); + + EVENT("Event: {Object: %s, Type: removing}", image_ref); + + bim_type = bim_query(image_ref); + if (bim_type == NULL) { + ERROR("No such image:%s", image_ref); + lcrd_set_error_message("No such image:%s", image_ref); + goto pack_response; + } + + bim = bim_get(bim_type->image_type, image_ref, NULL, NULL); + if (bim == NULL) { + ERROR("Failed to init bim for image %s", image_ref); + goto pack_response; + } + + ret = bim->ops->rm_image(request); + if (ret != 0) { + ERROR("Failed to remove image %s", image_ref); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: removed}", image_ref); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + free(image_ref); + bim_put(bim); + return ret; +} + +void free_im_remove_request(im_remove_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->image.image); + ptr->image.image = NULL; + + free(ptr); +} + +void free_im_remove_response(im_remove_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + +int im_inspect_image(const im_inspect_request *request, im_inspect_response **response) +{ + int ret = 0; + char *image_ref = NULL; + char *inspected_json = NULL; + const struct bim_type *bim_type = NULL; + struct bim *bim = NULL; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(im_inspect_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (request->image.image == NULL) { + ERROR("inspect image requires image ref"); + lcrd_set_error_message("inspect image requires image ref"); + ret = -1; + goto pack_response; + } + + image_ref = util_strdup_s(request->image.image); + + EVENT("Event: {Object: %s, Type: inspecting}", image_ref); + + bim_type = bim_query(image_ref); + if (bim_type == NULL) { + ERROR("No such image:%s", image_ref); + lcrd_set_error_message("No such image:%s", image_ref); + ret = -1; + goto pack_response; + } + + bim = bim_get(bim_type->image_type, image_ref, NULL, NULL); + if (bim == NULL) { + ERROR("Failed to init bim for image %s", image_ref); + ret = -1; + goto pack_response; + } + + ret = bim->ops->inspect_image(bim, &inspected_json); + if (ret != 0) { + ERROR("Failed to inspect image %s", image_ref); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: inspected}", image_ref); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + if (inspected_json != NULL) { + (*response)->im_inspect_json = util_strdup_s(inspected_json); + } + free(image_ref); + free(inspected_json); + bim_put(bim); + return ret; +} + +void free_im_inspect_request(im_inspect_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->image.image); + ptr->image.image = NULL; + + free(ptr); +} + +void free_im_inspect_response(im_inspect_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->im_inspect_json); + ptr->im_inspect_json = NULL; + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + +static int bims_init(const char *rootpath) +{ + int ret = 0; + size_t i; + + for (i = 0; i < g_numbims; i++) { + ret = g_bims[i].ops->init(rootpath); + if (ret != 0) { + ERROR("Failed to init bim %s", g_bims[i].image_type); + break; + } + } + + return ret; +} + +int image_module_init(const char *rootpath) +{ + return bims_init(rootpath); +} + +int map_to_key_value_string(const json_map_string_string *map, char ***array, size_t *array_len) +{ + char **strings = NULL; + size_t strings_len = 0; + size_t i; + int ret; + + if (map == NULL) { + return 0; + } + for (i = 0; i < map->len; i++) { + char *str = NULL; + size_t len; + if (strlen(map->keys[i]) > (SIZE_MAX - strlen(map->values[i])) - 2) { + ERROR("Invalid keys/values"); + goto cleanup; + } + len = strlen(map->keys[i]) + strlen(map->values[i]) + 2; + str = util_common_calloc_s(len); + if (str == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + ret = sprintf_s(str, len, "%s=%s", map->keys[i], map->values[i]); + if (ret < 0) { + ERROR("Failed to print string"); + free(str); + goto cleanup; + } + ret = util_array_append(&strings, str); + free(str); + if (ret != 0) { + ERROR("Failed to append array"); + goto cleanup; + } + strings_len++; + } + *array = strings; + *array_len = strings_len; + return 0; + +cleanup: + util_free_array(strings); + return -1; +} + diff --git a/src/image/image.h b/src/image/image.h new file mode 100644 index 0000000..79d11c6 --- /dev/null +++ b/src/image/image.h @@ -0,0 +1,267 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: weiwei + * Create: 2017-11-22 + * Description: provide image function definition + ******************************************************************************/ +#ifndef __IMAGE_H +#define __IMAGE_H + +#include + +#include "oci_image_manifest.h" +#include "oci_image_index.h" +#include "oci_image_spec.h" +#include "oci_runtime_spec.h" +#include "container_custom_config.h" +#include "host_config.h" +#include "liblcrd.h" + +#ifdef ENABLE_OCI_IMAGE +#include "oci_image_type.h" +#endif + +#include "imagetool_images_list.h" +#include "imagetool_fs_info.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define IMAGE_TYPE_OCI "oci" +#ifdef ENABLE_EMBEDDED_IMAGE +#define IMAGE_TYPE_EMBEDDED "embedded" +#endif +#define IMAGE_TYPE_EXTERNAL "external" + +#ifndef ENABLE_OCI_IMAGE +typedef struct { + char *image; +} image_spec; + +typedef struct { + image_spec image; +} image_filter; +#endif + +typedef struct { + image_filter filter; + bool check; +} im_list_request; + +typedef struct { + imagetool_images_list *images; + char *errmsg; +} im_list_response; + +typedef struct { + // Spec of the image. + image_spec image; + + bool force; +} im_remove_request; + +typedef struct { + char *errmsg; +} im_remove_response; + +typedef struct { + // Spec of the image. + image_spec image; +} im_inspect_request; + +typedef struct { + char *im_inspect_json; + char *errmsg; +} im_inspect_response; + +typedef struct { + char *file; + char *tag; + char *type; +} im_load_request; + +typedef struct { + char *errmsg; +} im_load_response; + +typedef struct { + char *type; + char *image; + + /* auth configs */ + char *username; + char *password; + char *auth; + char *server_address; + char *identity_token; + char *registry_token; +} im_pull_request; + +typedef struct { + char *image_ref; + char *errmsg; +} im_pull_response; + +typedef struct { + char *server; + char *username; + char *password; + char *type; +} im_login_request; + +typedef struct { + char *errmsg; +} im_login_response; + +typedef struct { + char *server; + char *type; +} im_logout_request; + +typedef struct { + char *errmsg; +} im_logout_response; + +struct bim; + + +struct bim_ops { + int (*init)(const char *rootpath); + /* detect whether image is of this bim type */ + bool (*detect)(const char *image_name); + + /* rootfs ops*/ + int (*prepare_rf)(struct bim *bim, const json_map_string_string *storage_opt, char **real_rootfs); + int (*mount_rf)(struct bim *bim); + int (*umount_rf)(struct bim *bim); + int (*delete_rf)(struct bim *bim); + char *(*resolve_image_name)(const char *image_name); + + /* merge image config ops*/ + int (*merge_conf)(oci_runtime_spec *oci_spec, const host_config *host_spec, container_custom_config *custom_spec, + struct bim *bim, char **real_rootfs); + + /* get user config ops */ + int (*get_user_conf)(const char *basefs, host_config *hc, + const char *userstr, oci_runtime_spec_process_user *puser); + + /* list images */ + int (*list_ims)(im_list_request *request, imagetool_images_list **images); + + /* remove image */ + int (*rm_image)(im_remove_request *request); + + /* inspect image */ + int (*inspect_image)(struct bim *bim, char **inpected_json); + + int (*filesystem_usage)(struct bim *bim, imagetool_fs_info **fs_usage); + + /* load image */ + int (*load_image)(im_load_request *request); + + /* pull image */ + int (*pull_image)(const im_pull_request *request, im_pull_response **response); + + /* login */ + int (*login)(im_login_request *request); + + /* logout */ + int (*logout)(im_logout_request *request); +}; + +struct bim { + /* common arguments */ + const struct bim_ops *ops; + const char *type; + + char *image_name; + char *ext_config_image; + char *container_id; +}; + +struct bim_type { + const char *image_type; + const struct bim_ops *ops; +}; + +int image_module_init(const char *rootpath); + +int im_get_container_filesystem_usage(const char *image_type, const char *id, imagetool_fs_info **fs_usage); + +int im_mount_container_rootfs(const char *image_type, const char *image_name, const char *container_id); + +int im_umount_container_rootfs(const char *image_type, const char *image_name, + const char *container_id); + +int im_remove_container_rootfs(const char *image_type, const char *container_id); + +int im_merge_image_config(const char *id, const char *image_type, const char *image_name, + const char *ext_config_image, oci_runtime_spec *oci_spec, host_config *host_spec, + container_custom_config *custom_spec, char **real_rootfs); + +int im_get_user_conf(const char *image_type, const char *basefs, host_config *hc, const char *userstr, + oci_runtime_spec_process_user *puser); + +int im_list_images(im_list_request *request, im_list_response **response); + +void free_im_list_request(im_list_request *ptr); + +void free_im_list_response(im_list_response *ptr); + +int im_rm_image(im_remove_request *request, im_remove_response **response); + +void free_im_remove_request(im_remove_request *ptr); + +void free_im_remove_response(im_remove_response *ptr); + +int im_inspect_image(const im_inspect_request *request, im_inspect_response **response); + +void free_im_inspect_request(im_inspect_request *ptr); + +void free_im_inspect_response(im_inspect_response *ptr); + +int map_to_key_value_string(const json_map_string_string *map, char ***array, size_t *array_len); + +int im_load_image(im_load_request *request, im_load_response **response); + +void free_im_load_request(im_load_request *ptr); + +void free_im_load_response(im_load_response *ptr); + +int im_pull_image(const im_pull_request *request, im_pull_response **response); + +void free_im_pull_request(im_pull_request *req); + +void free_im_pull_response(im_pull_response *resp); + +char *im_get_image_type(const char *image, const char *external_rootfs); + +bool im_config_image_exist(const char *image_name); + +int im_login(im_login_request *request, im_login_response **response); + +void free_im_login_request(im_login_request *ptr); + +void free_im_login_response(im_login_response *ptr); + +int im_logout(im_logout_request *request, im_logout_response **response); + +void free_im_logout_request(im_logout_request *ptr); + +void free_im_logout_response(im_logout_response *ptr); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/CMakeLists.txt b/src/image/oci/CMakeLists.txt new file mode 100644 index 0000000..8006f3b --- /dev/null +++ b/src/image/oci/CMakeLists.txt @@ -0,0 +1,12 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_oci_srcs) + +set(OCI_SRCS + ${local_oci_srcs} + PARENT_SCOPE + ) + +set(OCI_INCS + ${CMAKE_CURRENT_SOURCE_DIR} + PARENT_SCOPE + ) diff --git a/src/image/oci/isula_imtool_interface.c b/src/image/oci/isula_imtool_interface.c new file mode 100644 index 0000000..c11eda7 --- /dev/null +++ b/src/image/oci/isula_imtool_interface.c @@ -0,0 +1,818 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide imtool interface + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include "isula_imtool_interface.h" +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "lcrd_config.h" +#include "image.h" +#include "oci_image_pull.h" +#include "oci_image_status.h" +#include "oci_fs_info.h" +#include "oci_rootfs_prepare.h" +#include "oci_rootfs_mount.h" +#include "oci_rootfs_umount.h" +#include "oci_rootfs_remove.h" +#include "oci_rootfs_export.h" +#include "driver.h" + +#define PARAM_NUM 100 + +/* image tool name */ +#define ISULA_IMTOOL "isulad_kit" + +/* global options */ +#define IMTOOL_GB_OPTION_GRAPH_ROOT "--graph-root" +#define IMTOOL_GB_OPTION_RUN_ROOT "--run-root" +#define IMTOOL_GB_OPTION_DRIVER_NAME "--driver-name" +#define IMTOOL_GB_OPTION_DRIVER_OPTIONS "--driver-options" +#define IMTOOL_GB_OPTION_STORAGE_OPTIONS "--storage-opt" +#define IMTOOL_GB_OPTION_REGISTRY "--registry" +#define IMTOOL_GB_OPTION_INSEC_REGISTRY "--insecure-registry" +#define IMTOOL_GB_OPTION_OPT_TIMEOUT "--command-timeout" +#define IMTOOL_GB_OPTION_LOG_LEVEL "--log-level" + +/* sub command */ +#define IMTOOL_SUB_CMD_PULL "pull" +#define IMTOOL_CMD_PULL_OPTION_CREDS "--creds" +#define IMTOOL_CMD_PULL_OPTION_AUTH "--auth" +#define IMTOOL_CMD_PULL_DISABLE_TLS_VERIFY "--tls-verify=false" +#define IMTOOL_CMD_PULL_DISABLE_USE_DECRYPTED "--use-decrypted-key=false" + +#define IMTOOL_SUB_CMD_STATUS "status" + +#define IMTOOL_SUB_CMD_REMOVE_IMAGE "rmi" + +#define IMTOOL_SUB_CMD_FS_INFO "fsinfo" + +#define IMTOOL_SUB_CMD_LIST_IMAGES "images" +#define IMTOOL_CMD_IMAGES_OPTION_FILTER "--filter" +#define IMTOOL_CMD_IMAGES_OPTION_CHECK "--check" + +#define IMTOOL_SUB_CMD_PREPARE_ROOTFS "prepare" +#define IMTOOL_CMD_PREPARE_OPTION_IMAGE "--image" +#define IMTOOL_CMD_PREPARE_OPTION_NAME "--name" +#define IMTOOL_CMD_PREPARE_OPTION_ID "--id" + +#define IMTOOL_SUB_CMD_MOUNT_ROOTFS "mount" + +#define IMTOOL_SUB_CMD_UMOUNT_ROOTFS "umount" + +#define IMTOOL_SUB_CMD_REMOVE_ROOTFS "rm" + +#define IMTOOL_SUB_CMD_INFO_IMAGE "info" + +#define IMTOOL_SUB_CMD_STORAGE_STATUS "storage_status" + +#define IMTOOL_SUB_CMD_UMOUNT_STORAGE "storage_umount" + +#define IMTOOL_SUB_CMD_CONTAINER_FS_INFO "filesystemusage" + +#define IMTOOL_SUB_CMD_EXPORT_ROOTFS "export" +#define IMTOOL_CMD_OUTPUT_OPTION_ROOTFS "--output" + +#define IMTOOL_SUB_CMD_LOAD_IMAGE "load" +#define IMTOOL_CMD_INPUT_OPTION_IMAGE "--input" +#define IMTOOL_CMD_TAG_OPTION_IMAGE "--tag" + +#define IMTOOL_SUB_CMD_LOGIN "login" +#define IMTOOL_SUB_CMD_LOGOUT "logout" + +static inline void add_array_elem(char **array, size_t total, size_t *pos, const char *elem) +{ + if (*pos + 1 >= total - 1) { + return; + } + array[*pos] = util_strdup_s(elem); + *pos += 1; +} + +static inline void add_array_kv(char **array, size_t total, size_t *pos, const char *k, const char *v) +{ + if (k == NULL || v == NULL) { + return; + } + add_array_elem(array, total, pos, k); + add_array_elem(array, total, pos, v); +} + +static int pack_global_graph_driver(char *params[], size_t *count, bool ignore_storage_opt_size) +{ + int ret = -1; + char *graph_driver = NULL; + struct graphdriver *driver = NULL; + char **graph_opts = NULL; + char **p = NULL; + size_t i = 0; + + i = *count; + + graph_driver = conf_get_lcrd_storage_driver(); + if (graph_driver == NULL) { + COMMAND_ERROR("Failed to get graph driver"); + goto out; + } + driver = graphdriver_get(graph_driver); + if (strcmp(graph_driver, "overlay2") == 0) { + // Treating overlay2 as overlay cause image was downloaded always + // in '/var/lib/lcrd/storage/overlay' directory. + // See iSulad-kit/vendor/github.com/containers/storage/drivers/overlay/overlay.go, + // Driver is inited by name "overlay". + graph_driver[strlen(graph_driver) - 1] = '\0'; + } + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_DRIVER_NAME, graph_driver); + + graph_opts = conf_get_storage_opts(); + // since iSulad-kit will set quota when pull image, which is differ from docker, + // and we may get some error if setted, ignore it if neccessary. + for (p = graph_opts; (p != NULL) && (*p != NULL); p++) { + if (ignore_storage_opt_size && driver != NULL && driver->ops->is_quota_options(driver, *p)) { + continue; + } + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_DRIVER_OPTIONS, *p); + } + + ret = 0; + *count = i; +out: + free(graph_driver); + util_free_array(graph_opts); + return ret; +} + +static int pack_global_graph_root(char *params[], size_t *count) +{ + int ret = -1; + char *graph_root = NULL; + size_t i = 0; + + i = *count; + + graph_root = conf_get_graph_rootpath(); + if (graph_root == NULL) { + COMMAND_ERROR("Failed to get graph root directory"); + goto out; + } + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_GRAPH_ROOT, graph_root); + + ret = 0; + *count = i; +out: + free(graph_root); + return ret; +} + +static int pack_global_graph_run(char *params[], size_t *count) +{ + int ret = -1; + char *graph_run = NULL; + size_t i = 0; + + i = *count; + + graph_run = conf_get_graph_run_path(); + if (graph_run == NULL) { + COMMAND_ERROR("Failed to get graph run directory"); + goto out; + } + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_RUN_ROOT, graph_run); + + ret = 0; + *count = i; +out: + free(graph_run); + return ret; +} + +static int pack_global_graph_registry(char *params[], size_t *count) +{ + int ret = -1; + size_t i = 0; + char **registry = NULL; + char **insecure_registry = NULL; + char **p = NULL; + + i = *count; + + registry = conf_get_registry_list(); + for (p = registry; (p != NULL) && (*p != NULL); p++) { + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_REGISTRY, *p); + } + + insecure_registry = conf_get_insecure_registry_list(); + for (p = insecure_registry; (p != NULL) && (*p != NULL); p++) { + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_INSEC_REGISTRY, *p); + } + + ret = 0; + *count = i; + + util_free_array(registry); + util_free_array(insecure_registry); + return ret; +} + +static int pack_global_opt_time(char *params[], size_t *count) +{ + int ret = -1; + size_t i = 0; + unsigned int opt_timeout = 0; + char timeout_str[UINT_LEN + 2] = { 0 }; /*format: XXXs*/ + + i = *count; + + opt_timeout = conf_get_im_opt_timeout(); + if (opt_timeout != 0) { + add_array_elem(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_OPT_TIMEOUT); + if (sprintf_s(timeout_str, UINT_LEN, "%us", opt_timeout) < 0) { + COMMAND_ERROR("Failed to print string"); + goto out; + } + add_array_elem(params, PARAM_NUM, &i, timeout_str); + } + + ret = 0; + *count = i; +out: + return ret; +} + +static int pack_global_option(char *params[], size_t *count, bool ignore_storage_opt_size) +{ + int ret = -1; + size_t i = 0; + + i = *count; + + add_array_elem(params, PARAM_NUM, &i, ISULA_IMTOOL); + + if (pack_global_graph_root(params, &i) != 0) { + goto out; + } + + if (pack_global_graph_run(params, &i) != 0) { + goto out; + } + + if (pack_global_graph_driver(params, &i, ignore_storage_opt_size) != 0) { + goto out; + } + + if (pack_global_graph_registry(params, &i) != 0) { + goto out; + } + + if (pack_global_opt_time(params, &i) != 0) { + goto out; + } + + ret = 0; + *count = i; + +out: + return ret; +} + +void execute_pull_image(void *args) +{ + image_pull_request *request = (image_pull_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + if (request->auth.server_address != NULL) { + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_REGISTRY, request->auth.server_address); + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_PULL); + + if (conf_get_use_decrypted_key_flag() == false) { + add_array_elem(params, PARAM_NUM, &i, IMTOOL_CMD_PULL_DISABLE_USE_DECRYPTED); + } + + if (conf_get_skip_insecure_verify_flag() == true) { + add_array_elem(params, PARAM_NUM, &i, IMTOOL_CMD_PULL_DISABLE_TLS_VERIFY); + } + + add_array_elem(params, PARAM_NUM, &i, request->image.image); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot pull image with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_status_image(void *args) +{ + oci_image_status_request *request = (oci_image_status_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_STATUS); + + add_array_elem(params, PARAM_NUM, &i, request->image.image); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot status image with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_remove_image(void *args) +{ + im_remove_request *request = (im_remove_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_REMOVE_IMAGE); + + add_array_elem(params, PARAM_NUM, &i, request->image.image); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot remove image with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_list_images(void *args) +{ + im_list_request *request = (im_list_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + const char *log_level = "error"; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_LOG_LEVEL, log_level); + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_LIST_IMAGES); + + if (request->filter.image.image != NULL) { + add_array_kv(params, PARAM_NUM, &i, IMTOOL_CMD_IMAGES_OPTION_FILTER, request->filter.image.image); + } + + if (request->check) { + add_array_elem(params, PARAM_NUM, &i, IMTOOL_CMD_IMAGES_OPTION_CHECK); + } + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot list images with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_fs_info(void *args) +{ + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_FS_INFO); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot get image filesystem info with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_prepare_rootfs(void *args) +{ + rootfs_prepare_request *request = (rootfs_prepare_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, false) != 0) { + goto out; + } + + if (request->storage_opts != NULL && request->storage_opts_len > 0) { + int j; + for (j = 0; j < (int)request->storage_opts_len; j++) { + add_array_kv(params, PARAM_NUM, &i, IMTOOL_GB_OPTION_STORAGE_OPTIONS, request->storage_opts[j]); + } + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_PREPARE_ROOTFS); + + add_array_kv(params, PARAM_NUM, &i, IMTOOL_CMD_PREPARE_OPTION_IMAGE, request->image); + + add_array_kv(params, PARAM_NUM, &i, IMTOOL_CMD_PREPARE_OPTION_NAME, request->name); + + add_array_kv(params, PARAM_NUM, &i, IMTOOL_CMD_PREPARE_OPTION_ID, request->id); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot prepare rootfs with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_mount_rootfs(void *args) +{ + rootfs_mount_request *mount_request = (rootfs_mount_request *)args; + char *mount_params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (mount_request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(mount_params, &i, false) != 0) { + goto out; + } + + add_array_elem(mount_params, PARAM_NUM, &i, IMTOOL_SUB_CMD_MOUNT_ROOTFS); + + if (mount_request->name_id != NULL) { + add_array_elem(mount_params, PARAM_NUM, &i, mount_request->name_id); + } + + execvp(ISULA_IMTOOL, mount_params); + + COMMAND_ERROR("Cannot mount rootfs with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_umount_rootfs(void *args) +{ + rootfs_umount_request *umount_request = (rootfs_umount_request *)args; + char *umount_params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (umount_request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(umount_params, &i, false) != 0) { + goto out; + } + + add_array_elem(umount_params, PARAM_NUM, &i, IMTOOL_SUB_CMD_UMOUNT_ROOTFS); + + if (umount_request->name_id != NULL) { + add_array_elem(umount_params, PARAM_NUM, &i, umount_request->name_id); + } + + execvp(ISULA_IMTOOL, umount_params); + + COMMAND_ERROR("Cannot umount rootfs with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_remove_rootfs(void *args) +{ + rootfs_remove_request *remove_request = (rootfs_remove_request *)args; + char *remove_params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (remove_request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(remove_params, &i, true) != 0) { + goto out; + } + + add_array_elem(remove_params, PARAM_NUM, &i, IMTOOL_SUB_CMD_REMOVE_ROOTFS); + + if (remove_request->name_id != NULL) { + add_array_elem(remove_params, PARAM_NUM, &i, remove_request->name_id); + } + + execvp(ISULA_IMTOOL, remove_params); + + COMMAND_ERROR("Cannot remove rootfs with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_storage_status(void *args) +{ + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_STORAGE_STATUS); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot get storage status with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_container_fs_info(void *args) +{ + const char *id = (const char *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (id == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_CONTAINER_FS_INFO); + + add_array_elem(params, PARAM_NUM, &i, id); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot get filesystem usage with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_export_rootfs(void *args) +{ + rootfs_export_request *request = (rootfs_export_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_EXPORT_ROOTFS); + + add_array_kv(params, PARAM_NUM, &i, IMTOOL_CMD_OUTPUT_OPTION_ROOTFS, request->file); + + add_array_elem(params, PARAM_NUM, &i, request->id); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot export rootfs with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_load_image(void *args) +{ + im_load_request *request = (im_load_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_LOAD_IMAGE); + + add_array_kv(params, PARAM_NUM, &i, IMTOOL_CMD_INPUT_OPTION_IMAGE, request->file); + + if (request->tag != NULL) { + add_array_kv(params, PARAM_NUM, &i, IMTOOL_CMD_TAG_OPTION_IMAGE, request->tag); + } + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot load image with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_login(void *args) +{ + im_login_request *request = (im_login_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_LOGIN); + + if (conf_get_use_decrypted_key_flag() == false) { + add_array_elem(params, PARAM_NUM, &i, IMTOOL_CMD_PULL_DISABLE_USE_DECRYPTED); + } + + if (conf_get_skip_insecure_verify_flag() == true) { + add_array_elem(params, PARAM_NUM, &i, IMTOOL_CMD_PULL_DISABLE_TLS_VERIFY); + } + + add_array_elem(params, PARAM_NUM, &i, request->server); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot login with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +void execute_logout(void *args) +{ + im_logout_request *request = (im_logout_request *)args; + char *params[PARAM_NUM] = { NULL }; + size_t i = 0; + + if (request == NULL) { + COMMAND_ERROR("Invalid input arguments"); + return; + } + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + if (pack_global_option(params, &i, true) != 0) { + goto out; + } + + add_array_elem(params, PARAM_NUM, &i, IMTOOL_SUB_CMD_LOGOUT); + + if (conf_get_use_decrypted_key_flag() == false) { + add_array_elem(params, PARAM_NUM, &i, IMTOOL_CMD_PULL_DISABLE_USE_DECRYPTED); + } + + if (conf_get_skip_insecure_verify_flag() == true) { + add_array_elem(params, PARAM_NUM, &i, IMTOOL_CMD_PULL_DISABLE_TLS_VERIFY); + } + + add_array_elem(params, PARAM_NUM, &i, request->server); + + execvp(ISULA_IMTOOL, params); + + COMMAND_ERROR("Cannot logout with '%s':%s", ISULA_IMTOOL, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} diff --git a/src/image/oci/isula_imtool_interface.h b/src/image/oci/isula_imtool_interface.h new file mode 100644 index 0000000..19ed1a3 --- /dev/null +++ b/src/image/oci/isula_imtool_interface.h @@ -0,0 +1,58 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide imtool interface + ******************************************************************************/ + +#ifndef __ISULA_IMTOOL_INTERFACE_H_ +#define __ISULA_IMTOOL_INTERFACE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void execute_pull_image(void *args); + +void execute_status_image(void *args); + +void execute_remove_image(void *args); + +void execute_list_images(void *args); + +void execute_fs_info(void *args); + +void execute_prepare_rootfs(void *args); + +void execute_mount_rootfs(void *args); + +void execute_umount_rootfs(void *args); + +void execute_remove_rootfs(void *args); + +void execute_storage_status(void *args); + +void execute_container_fs_info(void *args); + +void execute_export_rootfs(void *args); + +void execute_load_image(void *args); + +void execute_login(void *args); + +void execute_logout(void *args); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_auth.c b/src/image/oci/oci_auth.c new file mode 100644 index 0000000..c5e9c4b --- /dev/null +++ b/src/image/oci/oci_auth.c @@ -0,0 +1,64 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-06-24 + * Description: provide oci auth functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "utils.h" +#include "log.h" +#include "imagetool_auth_input.h" +#include "json_common.h" +#include "oci_auth.h" + +char *pack_input_auth_string(auth_config *auth) +{ + char *auth_string = NULL; + imagetool_auth_input *auth_data = NULL; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + + auth_data = util_common_calloc_s(sizeof(imagetool_auth_input)); + if (auth_data == NULL) { + ERROR("memory out"); + goto out; + } + + auth_data->username = auth->username != NULL ? util_strdup_s(auth->username) : NULL; + auth_data->password = auth->password != NULL ? util_strdup_s(auth->password) : NULL; + auth_data->auth = auth->auth != NULL ? util_strdup_s(auth->auth) : NULL; + + auth_string = imagetool_auth_input_generate_json(auth_data, &ctx, &err); + if (auth_string == NULL) { + ERROR("Failed to generate json: %s", err); + goto out; + } + +out: + free_sensitive_string(auth_data->username); + auth_data->username = NULL; + free_sensitive_string(auth_data->password); + auth_data->password = NULL; + free_imagetool_auth_input(auth_data); + free(err); + return auth_string; +} + diff --git a/src/image/oci/oci_auth.h b/src/image/oci/oci_auth.h new file mode 100644 index 0000000..1b77f88 --- /dev/null +++ b/src/image/oci/oci_auth.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-06-24 + * Description: provide auth definition + ******************************************************************************/ +#ifndef __OCI_AUTH_H +#define __OCI_AUTH_H + +#include "oci_image_type.h" + +#ifdef __cplusplus +extern "C" { +#endif + +char *pack_input_auth_string(auth_config *auth); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_config_merge.c b/src/image/oci/oci_config_merge.c new file mode 100644 index 0000000..319bc62 --- /dev/null +++ b/src/image/oci/oci_config_merge.c @@ -0,0 +1,301 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci config merge functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include "oci_config_merge.h" +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include "securec.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "specs_mount.h" +#include "specs_extend.h" + +static void oci_image_merge_working_dir(const char *working_dir, oci_runtime_spec *oci_spec) +{ + if (working_dir == NULL) { + return; + } + + free(oci_spec->process->cwd); + oci_spec->process->cwd = util_strdup_s(working_dir); +} + +static int oci_image_merge_env(const oci_image_spec_config *config, oci_runtime_spec *oci_spec) +{ + if (config->env == NULL || config->env_len == 0) { + return 0; + } + if (merge_env(oci_spec, (const char **)config->env, config->env_len) != 0) { + ERROR("Failed to merge environment variables"); + return -1; + } + + return 0; +} + +static int do_duplicate_commands(const oci_image_spec_config *config, container_custom_config *custom_spec) +{ + size_t i; + + if (custom_spec->cmd_len != 0 || config->cmd_len == 0) { + return 0; + } + + if (config->cmd_len > SIZE_MAX / sizeof(char *)) { + ERROR("too many commands!"); + return -1; + } + + custom_spec->cmd = (char **)util_common_calloc_s(sizeof(char *) * config->cmd_len); + if (custom_spec->cmd == NULL) { + ERROR("Out of memory"); + return -1; + } + + for (i = 0; i < config->cmd_len; i++) { + custom_spec->cmd[i] = util_strdup_s(config->cmd[i]); + custom_spec->cmd_len++; + } + + return 0; +} + +static int do_duplicate_entrypoints(const oci_image_spec_config *config, container_custom_config *custom_spec) +{ + size_t i; + + if (config->entrypoint_len == 0) { + return 0; + } + + if (config->entrypoint_len > SIZE_MAX / sizeof(char *)) { + ERROR("too many entrypoints!"); + return -1; + } + + custom_spec->entrypoint = (char **)util_common_calloc_s(sizeof(char *) * config->entrypoint_len); + if (custom_spec->entrypoint == NULL) { + ERROR("Out of memory"); + return -1; + } + + for (i = 0; i < config->entrypoint_len; i++) { + custom_spec->entrypoint[i] = util_strdup_s(config->entrypoint[i]); + custom_spec->entrypoint_len++; + } + + return 0; +} + +static int oci_image_merge_entrypoint(const oci_image_spec_config *config, container_custom_config *custom_spec) +{ + if (custom_spec->entrypoint_len != 0) { + return 0; + } + + if (do_duplicate_commands(config, custom_spec) != 0) { + return -1; + } + + if (do_duplicate_entrypoints(config, custom_spec) != 0) { + return -1; + } + + return 0; +} + +static void oci_image_merge_user(const char *user, container_custom_config *custom_spec) +{ + if (custom_spec->user != NULL) { + return; + } + + custom_spec->user = util_strdup_s(user); +} + +static int oci_image_merge_volumes(const oci_image_spec_config *config, oci_runtime_spec *oci_spec) +{ + int ret; + + if (config->volumes == NULL) { + return 0; + } + ret = merge_volumes(oci_spec, config->volumes->keys, config->volumes->len, NULL, parse_volume); + if (ret != 0) { + ERROR("Failed to merge volumes"); + return -1; + } + + return 0; +} + +static int dup_health_check_from_image(const defs_health_check *image_health_check, + container_custom_config *custom_spec) +{ + int ret = 0; + size_t i; + defs_health_check *health_check = (defs_health_check *)util_common_calloc_s(sizeof(defs_health_check)); + if (health_check == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + if (image_health_check->test_len > SIZE_MAX / sizeof(char *)) { + ERROR("invalid health check commands!"); + ret = -1; + goto out; + } + + health_check->test = util_common_calloc_s(sizeof(char *) * image_health_check->test_len); + if (health_check->test == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + for (i = 0; i < image_health_check->test_len; i++) { + health_check->test[i] = util_strdup_s(image_health_check->test[i]); + health_check->test_len++; + } + health_check->interval = image_health_check->interval; + health_check->timeout = image_health_check->timeout; + health_check->start_period = image_health_check->start_period; + health_check->retries = image_health_check->retries; + health_check->exit_on_unhealthy = image_health_check->exit_on_unhealthy; + + custom_spec->health_check = health_check; + + health_check = NULL; + +out: + free_defs_health_check(health_check); + return ret; +} + +static int update_health_check_from_image(const defs_health_check *image_health_check, + container_custom_config *custom_spec) +{ + if (custom_spec->health_check->test_len == 0) { + size_t i; + + if (image_health_check->test_len > SIZE_MAX / sizeof(char *)) { + ERROR("invalid health check commands!"); + return -1; + } + custom_spec->health_check->test = util_common_calloc_s(sizeof(char *) * image_health_check->test_len); + if (custom_spec->health_check->test == NULL) { + ERROR("Out of memory"); + return -1; + } + for (i = 0; i < image_health_check->test_len; i++) { + custom_spec->health_check->test[i] = util_strdup_s(image_health_check->test[i]); + custom_spec->health_check->test_len++; + } + } + if (custom_spec->health_check->interval == 0) { + custom_spec->health_check->interval = image_health_check->interval; + } + if (custom_spec->health_check->timeout == 0) { + custom_spec->health_check->timeout = image_health_check->timeout; + } + if (custom_spec->health_check->start_period == 0) { + custom_spec->health_check->start_period = image_health_check->start_period; + } + if (custom_spec->health_check->retries == 0) { + custom_spec->health_check->retries = image_health_check->retries; + } + + return 0; +} + +static int oci_image_merge_health_check(const defs_health_check *image_health_check, + container_custom_config *custom_spec) +{ + int ret = 0; + + if (custom_spec == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + if (image_health_check == NULL) { + return 0; + } + + if (image_health_check->test_len == 0) { + ERROR("health check commands required"); + return -1; + } + + if (custom_spec->health_check == NULL) { + if (dup_health_check_from_image(image_health_check, custom_spec) != 0) { + ret = -1; + goto out; + } + } else { + if (update_health_check_from_image(image_health_check, custom_spec) != 0) { + ret = -1; + goto out; + } + } + +out: + return ret; +} + +int oci_image_merge_config(imagetool_image *image_conf, oci_runtime_spec *oci_spec, + container_custom_config *custom_spec) +{ + int ret = 0; + + if (image_conf == NULL || oci_spec == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + if (image_conf->spec != NULL && image_conf->spec->config != NULL) { + oci_image_merge_working_dir(image_conf->spec->config->working_dir, oci_spec); + + if (oci_image_merge_env(image_conf->spec->config, oci_spec) != 0) { + ret = -1; + goto out; + } + + if (oci_image_merge_entrypoint(image_conf->spec->config, custom_spec) != 0) { + ret = -1; + goto out; + } + + oci_image_merge_user(image_conf->spec->config->user, custom_spec); + + if (oci_image_merge_volumes(image_conf->spec->config, oci_spec) != 0) { + ret = -1; + goto out; + } + } + + if (oci_image_merge_health_check(image_conf->healthcheck, custom_spec) != 0) { + ret = -1; + goto out; + } + +out: + return ret; +} + diff --git a/src/image/oci/oci_config_merge.h b/src/image/oci/oci_config_merge.h new file mode 100644 index 0000000..7fb8a2a --- /dev/null +++ b/src/image/oci/oci_config_merge.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci config merge functions + ******************************************************************************/ + +#ifndef __OCI_IMAGE_MERGE_CONFIG_H_ +#define __OCI_IMAGE_MERGE_CONFIG_H_ + +#include "imagetool_image.h" +#include "container_custom_config.h" +#include "oci_runtime_spec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int oci_image_merge_config(imagetool_image *image_conf, oci_runtime_spec *oci_spec, + container_custom_config *custom_spec); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_container_fs_usage.c b/src/image/oci/oci_container_fs_usage.c new file mode 100644 index 0000000..e04b9b8 --- /dev/null +++ b/src/image/oci/oci_container_fs_usage.c @@ -0,0 +1,76 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image fs functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include "oci_container_fs_usage.h" +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_fs_info.h" +#include "log.h" +#include "utils.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" +#include "oci_rootfs_prepare.h" + +bool do_oci_container_fs_info(char *id, imagetool_fs_info **fs_info) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + parser_error err = NULL; + + if (id == NULL || fs_info == NULL) { + ERROR("Invalid input arguments"); + return false; + } + + command_ret = util_exec_cmd(execute_container_fs_info, id, NULL, &stdout_buffer, &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to get container fs info with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to container fs info with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec inspect fs info command"); + lcrd_set_error_message("Failed to exec inspect fs info command"); + } + goto free_out; + } + + if (stdout_buffer == NULL) { + ERROR("Failed to get container filesystem info becase can not get stdoutput"); + lcrd_set_error_message("Failed to get container filesystem info becase can not get stdoutput"); + goto free_out; + } + + *fs_info = imagetool_fs_info_parse_data(stdout_buffer, NULL, &err); + if (*fs_info == NULL) { + ERROR("Failed to parse output json:%s", err); + lcrd_set_error_message("Failed to parse output json:%s", err); + goto free_out; + } + + ret = true; + +free_out: + free(err); + free(stdout_buffer); + free(stderr_buffer); + return ret; +} diff --git a/src/image/oci/oci_container_fs_usage.h b/src/image/oci/oci_container_fs_usage.h new file mode 100644 index 0000000..f2f2168 --- /dev/null +++ b/src/image/oci/oci_container_fs_usage.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image fs functions + ******************************************************************************/ + +#ifndef __OCI_FS_INFO_H_ +#define __OCI_FS_INFO_H_ + +#include "oci_image_type.h" +#include "imagetool_fs_info.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool do_oci_container_fs_info(char *id, imagetool_fs_info **fs_info); + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/image/oci/oci_fs_info.c b/src/image/oci/oci_fs_info.c new file mode 100644 index 0000000..7884b84 --- /dev/null +++ b/src/image/oci/oci_fs_info.c @@ -0,0 +1,119 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image fs functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_fs_info.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" + +static bool do_fs_info(imagetool_fs_info **fs_info) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + parser_error err = NULL; + + command_ret = util_exec_cmd(execute_fs_info, NULL, NULL, &stdout_buffer, &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to get image fs info with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to image fs info with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec inspect fs info command"); + lcrd_set_error_message("Failed to exec inspect fs info command"); + } + goto free_out; + } + + if (stdout_buffer == NULL) { + ERROR("Failed to get image filesystem info becase can not get stdoutput"); + lcrd_set_error_message("Failed to get image filesystem info becase can not get stdoutput"); + goto free_out; + } + + *fs_info = imagetool_fs_info_parse_data(stdout_buffer, NULL, &err); + if (*fs_info == NULL) { + ERROR("Failed to parse output json:%s", err); + lcrd_set_error_message("Failed to parse output json:%s", err); + goto free_out; + } + + ret = true; + +free_out: + free(err); + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + + +int get_fs_info(image_fs_info_response **response) +{ + int ret = 0; + imagetool_fs_info *fs_info = NULL; + + if (response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(image_fs_info_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + EVENT("Event: {Object: sysinfo, Type: inspecting}"); + + if (!do_fs_info(&fs_info)) { + ERROR("Failed to inspect image filesystem info"); + ret = -1; + goto pack_response; + } + EVENT("Event: {Object: sysinfo, Type: inspected}"); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + (*response)->fs_info = fs_info; + + return ret; +} + +void free_image_fs_info_response(image_fs_info_response *ptr) +{ + if (ptr == NULL) { + return; + } + free_imagetool_fs_info(ptr->fs_info); + ptr->fs_info = NULL; + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + diff --git a/src/image/oci/oci_fs_info.h b/src/image/oci/oci_fs_info.h new file mode 100644 index 0000000..0494e82 --- /dev/null +++ b/src/image/oci/oci_fs_info.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image fs functions + ******************************************************************************/ + +#ifndef __OCI_FS_INFO_H_ +#define __OCI_FS_INFO_H_ + +#include "oci_image_type.h" +#include "imagetool_fs_info.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + imagetool_fs_info *fs_info; + char *errmsg; +} image_fs_info_response; + +int get_fs_info(image_fs_info_response **response); + +void free_image_fs_info_response(image_fs_info_response *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_image.c b/src/image/oci/oci_image.c new file mode 100644 index 0000000..aa33ad0 --- /dev/null +++ b/src/image/oci/oci_image.c @@ -0,0 +1,731 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide image function definition + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "securec.h" +#include "oci_images_store.h" +#include "oci_image.h" +#include "oci_rootfs_prepare.h" +#include "oci_rootfs_mount.h" +#include "oci_rootfs_umount.h" +#include "oci_rootfs_remove.h" +#include "oci_config_merge.h" +#include "oci_container_fs_usage.h" +#include "oci_image_pull.h" +#include "specs_extend.h" + +static int oci_image_prepare_rootfs(const char *image, const char *name, const json_map_string_string *storage_opt, + rootfs_prepare_and_get_image_conf_response **response) +{ + int ret = 0; + rootfs_prepare_request *request = NULL; + + if (image == NULL || name == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + request = util_common_calloc_s(sizeof(rootfs_prepare_request)); + if (request == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + + request->image = util_strdup_s(image); + request->name = util_strdup_s(name); + request->id = util_strdup_s(name); + if (map_to_key_value_string(storage_opt, &request->storage_opts, &request->storage_opts_len) != 0) { + ret = -1; + goto out; + } + + ret = prepare_rootfs_and_get_image_conf(request, response); + if (ret != 0) { + ERROR("Failed to prepare rootfs for %s with image %s", name, image); + ret = -1; + goto out; + } + +out: + free_rootfs_prepare_request(request); + return ret; +} + +static int oci_image_mount_rootfs(const char *name) +{ + int ret = 0; + rootfs_mount_request *request = NULL; + rootfs_mount_response *response = NULL; + + if (name == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + request = util_common_calloc_s(sizeof(rootfs_mount_request)); + if (request == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + + request->name_id = util_strdup_s(name); + + ret = mount_rootfs(request, &response); + if (ret != 0 || response == NULL) { + ERROR("Failed to mount rootfs for %s", name); + ret = -1; + goto out; + } + +out: + free_rootfs_mount_request(request); + free_rootfs_mount_response(response); + return ret; +} + +static int oci_image_umount_rootfs(const char *name) +{ + int ret = 0; + rootfs_umount_request *request = NULL; + rootfs_umount_response *response = NULL; + + if (name == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + request = util_common_calloc_s(sizeof(rootfs_umount_request)); + if (request == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + + request->name_id = util_strdup_s(name); + + ret = umount_rootfs(request, &response); + if (ret != 0) { + ERROR("Failed to umount rootfs for %s", name); + ret = -1; + goto out; + } + +out: + free_rootfs_umount_request(request); + free_rootfs_umount_response(response); + return ret; +} + +static int oci_image_remove_rootfs(const char *name) +{ + int ret = 0; + rootfs_remove_request *request = NULL; + rootfs_remove_response *response = NULL; + + if (name == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + request = util_common_calloc_s(sizeof(rootfs_remove_request)); + if (request == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + + request->name_id = util_strdup_s(name); + + ret = remove_rootfs(request, &response); + if (ret != 0) { + ERROR("Failed to remove rootfs for %s", name); + ret = -1; + goto out; + } + +out: + free_rootfs_remove_request(request); + free_rootfs_remove_response(response); + return ret; +} + +static bool oci_image_exist(const char *image_name) +{ + bool ret = false; + oci_image_t *image_info = NULL; + + image_info = oci_images_store_get(image_name); + if (image_info != NULL) { + ret = true; + oci_image_unref(image_info); + } + + return ret; +} + +bool oci_detect(const char *image_name) +{ + if (image_name == NULL) { + return false; + } + + return oci_image_exist(image_name); +} + +int oci_filesystem_usage(struct bim *bim, imagetool_fs_info **fs_usage) +{ + int ret = 0; + imagetool_fs_info *container_fs_usage = NULL; + + if (bim == NULL || fs_usage == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + EVENT("Event: {Object: sysinfo, Type: inspecting}"); + + if (!do_oci_container_fs_info(bim->container_id, &container_fs_usage)) { + ERROR("Failed to inspect cotainer filesystem info"); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: sysinfo, Type: inspected}"); + +pack_response: + *fs_usage = container_fs_usage; + + return ret; +} + +int oci_prepare_and_get_conf_rf(struct bim *bim, const json_map_string_string *storage_opt, + rootfs_prepare_and_get_image_conf_response **response) +{ + return oci_image_prepare_rootfs(bim->image_name, bim->container_id, storage_opt, response); +} + +int oci_prepare_rf(struct bim *bim, const json_map_string_string *storage_opt, char **real_rootfs) +{ + int ret = 0; + rootfs_prepare_and_get_image_conf_response *response = NULL; + + if (bim == NULL || real_rootfs == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + ret = oci_image_prepare_rootfs(bim->image_name, bim->container_id, storage_opt, &response); + if (ret == 0) { + *real_rootfs = response->raw_response->mount_point; + response->raw_response->mount_point = NULL; + } + free_rootfs_prepare_and_get_image_conf_response(response); + return ret; +} + +int oci_mount_rf(struct bim *bim) +{ + return oci_image_mount_rootfs(bim->container_id); +} + +int oci_umount_rf(struct bim *bim) +{ + return oci_image_umount_rootfs(bim->container_id); +} + +int oci_delete_rf(struct bim *bim) +{ + return oci_image_remove_rootfs(bim->container_id); +} + +// normalize the unqualified image to be domain/repo/image... +char *oci_normalize_image_name(const char *name) +{ +#define DEFAULT_TAG ":latest" +#define DEFAULT_HOSTNAME "docker.io/" +#define DEFAULT_REPO_PREFIX "library/" + + char temp[PATH_MAX] = { 0 }; + + + if (strstr(name, "/") == NULL) { + if (sprintf_s(temp, sizeof(temp), "%s%s", DEFAULT_HOSTNAME, DEFAULT_REPO_PREFIX) < 0) { + ERROR("sprint temp image name failed"); + return NULL; + } + } + + if (sprintf_s(temp + strlen(temp), sizeof(temp) - strlen(temp), "%s", name) < 0) { + ERROR("sprint temp image name failed"); + return NULL; + } + + if (util_tag_pos(name) == NULL) { + if (sprintf_s(temp + strlen(temp), sizeof(temp) - strlen(temp), "%s", DEFAULT_TAG) < 0) { + ERROR("sprint temp image name failed"); + return NULL; + } + } + + return util_strdup_s(temp); +} + +char *oci_resolve_image_name(const char *name) +{ + if (util_valid_short_sha256_id(name) && oci_image_exist(name)) { + return util_strdup_s(name); + } + + return oci_normalize_image_name(name); +} + +static int merge_oci_image_conf(const char *image_name, oci_runtime_spec *oci_spec, + container_custom_config *custom_spec) +{ + int ret = 0; + oci_image_t *image_info = NULL; + char *resolved_name = NULL; + + if (oci_spec == NULL || image_name == NULL) { + ERROR("invalid NULL param"); + return -1; + } + + resolved_name = oci_resolve_image_name(image_name); + if (resolved_name == NULL) { + ERROR("Resolve external config image name failed, image name is %s", image_name); + ret = -1; + goto out; + } + + image_info = oci_images_store_get(resolved_name); + if (image_info == NULL) { + ERROR("Get image from image store failed, image name is %s", resolved_name); + ret = -1; + goto out; + } + + ret = oci_image_merge_config(image_info->info, oci_spec, custom_spec); + if (ret != 0) { + ERROR("Failed to merge oci config for image %s", resolved_name); + ret = -1; + goto out; + } + +out: + oci_image_unref(image_info); + free(resolved_name); + resolved_name = NULL; + return ret; +} + + +int oci_merge_conf(oci_runtime_spec *oci_spec, const host_config *host_spec, container_custom_config *custom_spec, + struct bim *bim, char **real_rootfs) +{ + int ret = 0; + int nret = 0; + rootfs_prepare_and_get_image_conf_response *response = NULL; + + nret = oci_prepare_and_get_conf_rf(bim, host_spec->storage_opt, &response); + if (nret != 0) { + ret = nret; + goto free_out; + } + + nret = merge_oci_image_conf(bim->image_name, oci_spec, custom_spec); + if (nret != 0) { + ret = nret; + goto free_out; + } + + + *real_rootfs = response->raw_response->mount_point; + response->raw_response->mount_point = NULL; + +free_out: + free_rootfs_prepare_and_get_image_conf_response(response); + return ret; +} + +int oci_get_user_conf(const char *basefs, host_config *hc, const char *userstr, oci_runtime_spec_process_user *puser) +{ + if (basefs == NULL || puser == NULL) { + ERROR("Empty basefs or puser"); + return -1; + } + return get_user(basefs, hc, userstr, puser); +} + +static int dup_oci_image_info(const imagetool_image *src, imagetool_image **dest) +{ + int ret = -1; + char *json = NULL; + parser_error err = NULL; + + if (src == NULL) { + *dest = NULL; + return 0; + } + + json = imagetool_image_generate_json(src, NULL, &err); + if (json == NULL) { + ERROR("Failed to generate json: %s", err); + goto out; + } + *dest = imagetool_image_parse_data(json, NULL, &err); + if (*dest == NULL) { + ERROR("Failed to parse json: %s", err); + goto out; + } + ret = 0; + +out: + free(err); + free(json); + return ret; +} + +static int oci_list_all_images(imagetool_images_list *images_list) +{ + int ret = 0; + size_t i = 0; + oci_image_t **images_info = NULL; + size_t images_num = 0; + + ret = oci_images_store_list(&images_info, &images_num); + if (ret != 0) { + ERROR("query all oci images info failed"); + return -1; + } + + if (images_num == 0) { + ret = 0; + goto out; + } + + if (images_num > (SIZE_MAX / sizeof(imagetool_image *))) { + ERROR("Get too many images:%d", images_num); + ret = -1; + goto out; + } + + images_list->images = util_common_calloc_s(images_num * sizeof(imagetool_image *)); + if (images_list->images == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + for (i = 0; i < images_num; i++) { + ret = dup_oci_image_info(images_info[i]->info, &images_list->images[i]); + if (ret != 0) { + ERROR("Failed to dup oci image %s info", images_info[i]->info->id); + ret = -1; + goto out; + } + oci_image_unref(images_info[i]); + images_list->images_len++; + } +out: + if (ret != 0) { + for (; i < images_num; i++) { + oci_image_unref(images_info[i]); + } + } + + free(images_info); + return ret; +} + +static int oci_list_images_by_filter(const char *filter, imagetool_images_list *images_list) +{ + int ret = 0; + char *tmp = NULL; + oci_image_t *image_info = NULL; + + tmp = oci_resolve_image_name(filter); + if (tmp == NULL) { + ERROR("Failed to resolve image name"); + ret = -1; + goto out; + } + + image_info = oci_images_store_get(tmp); + if (image_info == NULL) { + ret = 0; + goto out; + } + + images_list->images = util_common_calloc_s(1 * sizeof(imagetool_image *)); + if (images_list->images == NULL) { + oci_image_unref(image_info); + ERROR("Out of memory"); + ret = -1; + goto out; + } + + ret = dup_oci_image_info(image_info->info, &images_list->images[0]); + oci_image_unref(image_info); + if (ret != 0) { + ERROR("Failed to dup oci image %s info", tmp); + ret = -1; + goto out; + } + images_list->images_len++; + +out: + free(tmp); + return ret; +} + +int oci_list_images(im_list_request *request, imagetool_images_list **images) +{ + int ret = 0; + char *filter = NULL; + + if (request != NULL && request->filter.image.image != NULL) { + filter = request->filter.image.image; + } + + *images = util_common_calloc_s(sizeof(imagetool_images_list)); + if (*images == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (filter != NULL) { + ret = oci_list_images_by_filter(filter, *images); + } else { + ret = oci_list_all_images(*images); + } + + if (ret != 0) { + goto out; + } + +out: + if (ret != 0) { + free_imagetool_images_list(*images); + *images = NULL; + } + return ret; +} + +int oci_status_image(oci_image_status_request *request, oci_image_status_response **response) +{ + int ret = 0; + imagetool_image_status *image = NULL; + char *image_ref = NULL; + oci_image_t *image_info = NULL; + char *resolved_name = NULL; + + *response = util_common_calloc_s(sizeof(oci_image_status_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + image = util_common_calloc_s(sizeof(imagetool_image_status)); + if (image == NULL) { + ERROR("Out of memory"); + ret = -1; + goto pack_response; + } + (*response)->image_info = image; + + image_ref = request->image.image; + if (image_ref == NULL) { + ERROR("Inspect image requires image ref"); + lcrd_set_error_message("Inspect image requires image ref"); + ret = -1; + goto pack_response; + } + + resolved_name = oci_resolve_image_name(image_ref); + if (resolved_name == NULL) { + ERROR("Failed to reslove image name %s", image_ref); + lcrd_set_error_message("Failed to reslove image name %s", image_ref); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: statusing image}", resolved_name); + + image_info = oci_images_store_get(resolved_name); + if (image_info == NULL) { + ERROR("No such image:%s", resolved_name); + lcrd_set_error_message("No such image:%s", resolved_name); + ret = -1; + goto pack_response; + } + + ret = dup_oci_image_info(image_info->info, &((*response)->image_info->image)); + oci_image_unref(image_info); + if (ret != 0) { + ERROR("Failed to dup image info:%s", resolved_name); + lcrd_set_error_message("Failed to dup image info:%s", resolved_name); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: statused image}", resolved_name); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + free(resolved_name); + + return ret; +} + +int oci_inspect_image(struct bim *bim, char **inspected_json) +{ + int ret = 0; + oci_image_status_request request; + oci_image_status_response *response = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + + if (bim == NULL || inspected_json == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + ret = memset_s(&request, sizeof(oci_image_status_request), 0x00, sizeof(oci_image_status_request)); + if (ret != EOK) { + ERROR("Failed to set memory"); + return -1; + } + request.image.image = bim->image_name; + + ret = oci_status_image(&request, &response); + if (ret != 0) { + goto out; + } + + *inspected_json = imagetool_image_status_generate_json(response->image_info, &ctx, &err); + if (*inspected_json == NULL) { + ERROR("Failed to generate image status request json:%s", err); + ret = -1; + goto out; + } + +out: + free(err); + free_oci_image_status_response(response); + return ret; +} + +static int im_request_to_oci_request(const im_pull_request *req, image_pull_request **oci_req) +{ + *oci_req = util_common_calloc_s(sizeof(image_pull_request)); + if (*oci_req == NULL) { + ERROR("Out of memory"); + return -1; + } + (*oci_req)->image.image = util_strdup_s(req->image); + + if (req->username != NULL) { + (*oci_req)->auth.username = util_strdup_s(req->username); + } + if (req->password != NULL) { + (*oci_req)->auth.password = util_strdup_s(req->password); + } + if (req->auth != NULL) { + (*oci_req)->auth.auth = util_strdup_s(req->auth); + } + if (req->server_address != NULL) { + (*oci_req)->auth.server_address = util_strdup_s(req->server_address); + } + if (req->identity_token != NULL) { + (*oci_req)->auth.identity_token = util_strdup_s(req->identity_token); + } + if (req->registry_token != NULL) { + (*oci_req)->auth.registry_token = util_strdup_s(req->registry_token); + } + + return 0; +} + +int oci_pull_image(const im_pull_request *request, im_pull_response **response) +{ + int ret = -1; + image_pull_response *oci_resp = NULL; + image_pull_request *oci_req = NULL; + + ret = im_request_to_oci_request(request, &oci_req); + if (ret != 0) { + goto free_out; + } + + ret = pull_image(oci_req, &oci_resp); + if (ret != 0) { + ERROR("Pull image failed: %s", oci_resp->errmsg); + goto free_out; + } + *response = util_common_calloc_s(sizeof(im_pull_response)); + if (*response == NULL) { + ret = -1; + ERROR("Out of memory"); + goto free_out; + } + (*response)->image_ref = util_strdup_s(oci_resp->image_ref); + (*response)->errmsg = util_strdup_s(oci_resp->errmsg); + +free_out: + free_image_pull_request(oci_req); + free_image_pull_response(oci_resp); + return ret; +} + +int oci_init(const char *rootpath) +{ + int ret = 0; + + ret = oci_images_store_init(); + if (ret != 0) { + ERROR("Failed to init oci images store"); + goto out; + } + + ret = image_name_id_init(); + if (ret != 0) { + ERROR("Failed to init oci name id store"); + goto out; + } + + ret = load_all_oci_images(); + +out: + return ret; +} diff --git a/src/image/oci/oci_image.h b/src/image/oci/oci_image.h new file mode 100644 index 0000000..5dc0cf3 --- /dev/null +++ b/src/image/oci/oci_image.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide image function definition + ******************************************************************************/ +#ifndef __OCI_IMAGE_H +#define __OCI_IMAGE_H + +#include +#include "image.h" +#include "oci_image_spec.h" +#include "oci_image_status.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool oci_detect(const char *image_name); +int oci_filesystem_usage(struct bim *bim, imagetool_fs_info **fs_usage); + +int oci_prepare_rf(struct bim *bim, const json_map_string_string *storage_opt, char **real_rootfs); +int oci_mount_rf(struct bim *bim); +int oci_umount_rf(struct bim *bim); +int oci_delete_rf(struct bim *bim); +char *oci_resolve_image_name(const char *name); +char *oci_normalize_image_name(const char *name); + +int oci_merge_conf(oci_runtime_spec *oci_spec, const host_config *host_spec, container_custom_config *custom_spec, + struct bim *bim, char **real_rootfs); +int oci_get_user_conf(const char *basefs, host_config *hc, const char *userstr, oci_runtime_spec_process_user *puser); +int oci_list_images(im_list_request *request, imagetool_images_list **images); +int oci_remove_image(im_remove_request *request); +int oci_status_image(oci_image_status_request *request, oci_image_status_response **response); +int oci_inspect_image(struct bim *bim, char **inspected_json); +int oci_pull_image(const im_pull_request *request, im_pull_response **response); + +int oci_init(const char *rootpath); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_image_load.c b/src/image/oci/oci_image_load.c new file mode 100644 index 0000000..51d7f45 --- /dev/null +++ b/src/image/oci/oci_image_load.c @@ -0,0 +1,170 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-04-09 + * Description: provide oci load image functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include "oci_image_load.h" +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" +#include "oci_images_store.h" +#include "image.h" + +const int stdout_buffer_max_size = 10 * SIZE_MB; /* 10M */ + +static char *get_ref_from_stdout_buffer(const char *stdout_buffer) +{ + char *ref = NULL; + size_t len = 0; + char *key_word = "Loaded image: "; + + ref = strstr(stdout_buffer, key_word); + if (ref == NULL) { + return NULL; + } + + /* skip key word */ + ref += strlen(key_word); + + /* +1 for '\n' and +1 for terminator */ + len = strnlen(ref, (size_t)(MAX_IMAGE_REF_LEN + 2)); + if (len == 0 || len > MAX_IMAGE_REF_LEN) { + return NULL; + } + + /* strip '\n' */ + ref[len - 1] = 0; + + return util_strdup_s(ref); +} + +static bool do_load(im_load_request *request, char **ref) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + char *tmp_stdout_buffer = NULL; + + command_ret = util_exec_cmd(execute_load_image, request, NULL, &stdout_buffer, &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to load image with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to load image with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec load image command"); + lcrd_set_error_message("Failed to exec load image command"); + } + goto free_out; + } + + if (stdout_buffer == NULL) { + ERROR("Failed to load image because can not get stdoutput"); + lcrd_set_error_message("Failed to load image because can not get stdoutput"); + goto free_out; + } + + if (strnlen(stdout_buffer, (size_t)(stdout_buffer_max_size + 1)) > (size_t)stdout_buffer_max_size) { + ERROR("Failed to load image because stdoutput exceeded max size"); + lcrd_set_error_message("Failed to load image because stdoutput exceeded max size"); + goto free_out; + } + + /* get_ref_from_stdout_buffer will modify stdout_buffer, get a copy to do this because + we want to print original buffer if get reference failed. */ + tmp_stdout_buffer = util_strdup_s(stdout_buffer); + *ref = get_ref_from_stdout_buffer(tmp_stdout_buffer); + if (*ref == NULL) { + ERROR("Failed to load image because cann't get image reference from stdout buffer." + "stdout buffer is [%s]", + stdout_buffer); + lcrd_set_error_message("Failed to load image because cann't get image reference from stdout buffer"); + goto free_out; + } + + ret = true; + +free_out: + free(stderr_buffer); + free(stdout_buffer); + free(tmp_stdout_buffer); + return ret; +} + +static int check_load_request_valid(const im_load_request *request) +{ + int ret = -1; + + if (request == NULL) { + ERROR("invalid load request"); + lcrd_set_error_message("invalid load request"); + goto out; + } + + if (request->file == NULL) { + ERROR("Load image requires input file path"); + lcrd_set_error_message("Load image requires input file path"); + goto out; + } + + if (request->tag != NULL) { + if (util_valid_image_name(request->tag) != true) { + ERROR("Invalid tag %s", request->tag); + lcrd_try_set_error_message("Invalid tag:%s", request->tag); + goto out; + } + } + + ret = 0; + +out: + return ret; +} + +int oci_load_image(im_load_request *request) +{ + int ret = 0; + char *ref = NULL; + + if (check_load_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + if (!do_load(request, &ref)) { + ERROR("Failed to load image"); + ret = -1; + goto pack_response; + } + + ret = register_new_oci_image_into_memory(ref); + if (ret != 0) { + ERROR("Failed to register new image to images store"); + ret = -1; + goto pack_response; + } + +pack_response: + free(ref); + ref = NULL; + + return ret; +} diff --git a/src/image/oci/oci_image_load.h b/src/image/oci/oci_image_load.h new file mode 100644 index 0000000..90bf632 --- /dev/null +++ b/src/image/oci/oci_image_load.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wujing + * Create: 2019-04-26 + * Description: provide image load function definition + ******************************************************************************/ +#ifndef __OCI_IMAGE_LOAD_H +#define __OCI_IMAGE_LOAD_H + +#include +#include "image.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int oci_load_image(im_load_request *request); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_image_pull.c b/src/image/oci/oci_image_pull.c new file mode 100644 index 0000000..c646ae8 --- /dev/null +++ b/src/image/oci/oci_image_pull.c @@ -0,0 +1,190 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image pull functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_images_store.h" +#include "oci_image_pull.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" +#include "oci_image.h" +#include "imagetool_auth_input.h" +#include "oci_auth.h" + +static bool do_pull(image_pull_request *request, char **image_ref) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + char *stdin_buffer = NULL; + + stdin_buffer = pack_input_auth_string(&request->auth); + if (stdin_buffer == NULL) { + ERROR("Failed to generate image auth info"); + lcrd_set_error_message("Failed to generate image auth info"); + goto free_out; + } + + command_ret = util_exec_cmd(execute_pull_image, request, stdin_buffer, &stdout_buffer, + &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to pull image with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to pull image with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec pull image command"); + lcrd_set_error_message("Failed to exec pull image command"); + } + goto free_out; + } + + if (stdout_buffer != NULL) { + INFO("Pulled image with ref: %s", stdout_buffer); + *image_ref = util_strdup_s(stdout_buffer); + } else { + ERROR("Failed to pull image ref becase can't get image ref"); + lcrd_set_error_message("Failed to pull image ref becase can't get image ref"); + goto free_out; + } + + ret = true; + +free_out: + free_sensitive_string(stdin_buffer); + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + +int pull_image(image_pull_request *request, image_pull_response **response) +{ + int ret = 0; + char *tmp = NULL; + char *image_ref = NULL; + char *normalized_ref = NULL; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + if (request->image.image == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(image_pull_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + /* Max length should include the ":latest" which may not provided when + * pulling image, so we need to normalize image name before checking it. */ + normalized_ref = oci_normalize_image_name(request->image.image); + if (!util_valid_image_name(normalized_ref)) { + ERROR("Invalid image name %s", normalized_ref); + ret = -1; + lcrd_try_set_error_message("Invalid image name:%s", normalized_ref); + goto pack_response; + } + + tmp = oci_resolve_image_name(request->image.image); + if (tmp == NULL) { + ERROR("Failed to resolve image name"); + ret = -1; + goto pack_response; + } + free(request->image.image); + request->image.image = tmp; + + set_log_prefix(request->image.image); + + EVENT("Event: {Object: %s, Type: Pulling}", request->image.image); + + if (!do_pull(request, &image_ref)) { + ERROR("Failed to pull image"); + ret = -1; + goto pack_response; + } + + ret = register_new_oci_image_into_memory(request->image.image); + if (ret != 0) { + ERROR("Failed to register new image to images store"); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: Pulled}", request->image.image); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + if (image_ref != NULL) { + (*response)->image_ref = util_strdup_s(image_ref); + free(image_ref); + } + + free(normalized_ref); + + free_log_prefix(); + return ret; +} + +void free_image_pull_request(image_pull_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->image.image); + ptr->image.image = NULL; + free_sensitive_string(ptr->auth.username); + ptr->auth.username = NULL; + free_sensitive_string(ptr->auth.password); + ptr->auth.password = NULL; + free_sensitive_string(ptr->auth.auth); + ptr->auth.auth = NULL; + free_sensitive_string(ptr->auth.server_address); + ptr->auth.server_address = NULL; + free_sensitive_string(ptr->auth.identity_token); + ptr->auth.identity_token = NULL; + free_sensitive_string(ptr->auth.registry_token); + ptr->auth.registry_token = NULL; + + free(ptr); +} + +void free_image_pull_response(image_pull_response *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->image_ref); + ptr->image_ref = NULL; + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + diff --git a/src/image/oci/oci_image_pull.h b/src/image/oci/oci_image_pull.h new file mode 100644 index 0000000..3b8fe87 --- /dev/null +++ b/src/image/oci/oci_image_pull.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image pull functions + ******************************************************************************/ + +#ifndef __OCI_IMAGE_PULL_H_ +#define __OCI_IMAGE_PULL_H_ + +#include "oci_image_type.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + image_spec image; + auth_config auth; +} image_pull_request; + +typedef struct { + // Reference to the image in use. For most runtimes, this should be an + // image ID or digest. + char *image_ref; + char *errmsg; +} image_pull_response; + +int pull_image(image_pull_request *request, image_pull_response **response); + +void free_image_pull_request(image_pull_request *ptr); + +void free_image_pull_response(image_pull_response *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_image_remove.c b/src/image/oci/oci_image_remove.c new file mode 100644 index 0000000..905cf4b --- /dev/null +++ b/src/image/oci/oci_image_remove.c @@ -0,0 +1,105 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image remove functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "utils.h" +#include "log.h" +#include "image.h" +#include "oci_images_store.h" +#include "isula_imtool_interface.h" +#include "oci_image.h" + +#define IMAGE_NOT_KNOWN_ERR "image not known" + +int oci_remove_image(im_remove_request *request) +{ + int ret = 0; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + char *tmp = NULL; + oci_image_t *image_info = NULL; + bool locked = false; + + if (request->image.image == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + tmp = oci_resolve_image_name(request->image.image); + if (tmp == NULL) { + ERROR("Failed to resolve image name"); + ret = -1; + goto free_out; + } + + image_info = oci_images_store_get(tmp); + if (image_info == NULL) { + INFO("No such image exist %s", tmp); + ret = 0; + goto free_out; + } + + /* isulad_kit does not support concurrent delete tags of the same image, + * so we should make sure we delete tags one by one. */ + oci_image_lock(image_info); + locked = true; + + free(request->image.image); + request->image.image = util_strdup_s(tmp); + + command_ret = util_exec_cmd(execute_remove_image, request, NULL, &stdout_buffer, &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + if (strstr(stderr_buffer, IMAGE_NOT_KNOWN_ERR) != NULL) { + DEBUG("Image %s may already removed", request->image.image); + ret = 0; + goto clean_memory; + } + ERROR("Failed to remove image with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to remove image with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec remove image command"); + lcrd_set_error_message("Failed to exec remove image command"); + } + ret = -1; + goto free_out; + } + +clean_memory: + ret = remove_oci_image_from_memory(tmp); + if (ret != 0) { + ERROR("Failed to remove image %s from memory", tmp); + ret = -1; + goto free_out; + } + +free_out: + if (locked) { + oci_image_unlock(image_info); + } + free(tmp); + free(stderr_buffer); + free(stdout_buffer); + oci_image_unref(image_info); + return ret; +} + diff --git a/src/image/oci/oci_image_status.c b/src/image/oci/oci_image_status.c new file mode 100644 index 0000000..48c8505 --- /dev/null +++ b/src/image/oci/oci_image_status.c @@ -0,0 +1,171 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image status functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_image_status.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" + +static bool do_status(oci_image_status_request *request, + imagetool_image_status **image) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + parser_error err = NULL; + + command_ret = util_exec_cmd(execute_status_image, request, NULL, &stdout_buffer, + &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to status image with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to status image with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec status image command"); + lcrd_set_error_message("Failed to exec status image command"); + } + goto free_out; + } + + if (stdout_buffer == NULL) { + ERROR("Failed to status image becase can not get stdoutput"); + lcrd_set_error_message("Failed to status image becase can not get stdoutput"); + goto free_out; + } + + *image = imagetool_image_status_parse_data(stdout_buffer, NULL, &err); + if (*image == NULL) { + ERROR("Failed to parse output json:%s", err); + lcrd_set_error_message("Failed to parse output json:%s", err); + goto free_out; + } + + ret = true; + +free_out: + free(err); + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + + +static int do_status_oci_image(oci_image_status_request *request, + oci_image_status_response **response) +{ + int ret = 0; + imagetool_image_status *image = NULL; + char *image_ref = NULL; + + image_ref = request->image.image; + + *response = util_common_calloc_s(sizeof(oci_image_status_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (image_ref == NULL) { + ERROR("Inspect image requires image ref"); + lcrd_set_error_message("Inspect image requires image ref"); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: inspecting image}", image_ref); + + if (!do_status(request, &image)) { + ERROR("Failed to status image: %s", image_ref); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: inspected image}", image_ref); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + (*response)->image_info = image; + + return ret; +} + +void free_oci_image_status_request(oci_image_status_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->image.image); + ptr->image.image = NULL; + + free(ptr); +} + +void free_oci_image_status_response(oci_image_status_response *ptr) +{ + if (ptr == NULL) { + return; + } + free_imagetool_image_status(ptr->image_info); + ptr->image_info = NULL; + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + +imagetool_image *oci_image_get_image_info_by_name(const char *image_name) +{ + oci_image_status_request *request = NULL; + oci_image_status_response *response = NULL; + imagetool_image *image = NULL; + + if (image_name == NULL) { + ERROR("Empty image name"); + return NULL; + } + + request = (oci_image_status_request *)util_common_calloc_s(sizeof(*request)); + if (request == NULL) { + ERROR("Out of memory"); + return NULL; + } + request->image.image = util_strdup_s(image_name); + + if (do_status_oci_image(request, &response)) { + goto cleanup; + } + + if (response->image_info != NULL) { + image = response->image_info->image; + response->image_info->image = NULL; + } + +cleanup: + free_oci_image_status_request(request); + free_oci_image_status_response(response); + return image; +} diff --git a/src/image/oci/oci_image_status.h b/src/image/oci/oci_image_status.h new file mode 100644 index 0000000..b0e8473 --- /dev/null +++ b/src/image/oci/oci_image_status.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci image status functions + ******************************************************************************/ + +#ifndef __OCI_IMAGE_STATUS_H_ +#define __OCI_IMAGE_STATUS_H_ + +#include "image.h" +#include "oci_image_type.h" +#include "imagetool_image_status.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + // Spec of the image. + image_spec image; + // Verbose indicates whether to return extra information about the image. + bool verbose; +} oci_image_status_request; + +typedef struct { + imagetool_image_status *image_info; + char *errmsg; +} oci_image_status_response; + +void free_oci_image_status_request(oci_image_status_request *ptr); + +void free_oci_image_status_response(oci_image_status_response *ptr); + +imagetool_image *oci_image_get_image_info_by_name(const char *image_name); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_image_type.h b/src/image/oci/oci_image_type.h new file mode 100644 index 0000000..0aa1fb6 --- /dev/null +++ b/src/image/oci/oci_image_type.h @@ -0,0 +1,60 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide image type definition + ******************************************************************************/ + +#ifndef __OCI_IMAGE_TYPE_H_ +#define __OCI_IMAGE_TYPE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ENABLE_OCI_IMAGE +/* + * ImageSpec is an internal representation of an image. Currently, it wraps the + * value of a Container's Image field (e.g. imageID or imageDigest), but in the + * future it will include more detailed information about the different image types. + */ +typedef struct { + char *image; +} image_spec; +#endif + +/* AuthConfig contains authorization information for connecting to a registry */ +typedef struct { + char *username; + char *password; + char *auth; + char *server_address; + + // IdentityToken is used to authenticate the user and get + // an access token for the registry. + char *identity_token; + + // RegistryToken is a bearer token to be sent to a registry + char *registry_token; +} auth_config; + +#ifdef ENABLE_OCI_IMAGE +typedef struct { + image_spec image; +} image_filter; +#endif + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_image_unix.c b/src/image/oci/oci_image_unix.c new file mode 100644 index 0000000..ec4fd05 --- /dev/null +++ b/src/image/oci/oci_image_unix.c @@ -0,0 +1,107 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container unix functions + ******************************************************************************/ +#include +#include +#include +#include + +#include "oci_image_unix.h" +#include "log.h" +#include "utils.h" + + +oci_image_t *oci_image_new(imagetool_image *image_info) +{ + oci_image_t *image = NULL; + + if (image_info == NULL) { + return NULL; + } + + image = util_common_calloc_s(sizeof(oci_image_t)); + if (image == NULL) { + ERROR("Out of memory"); + return NULL; + } + + atomic_int_set_image(&image->refcnt, 1); + + image->info = image_info; + + return image; +} + +/* oci_image free */ +void oci_image_free(oci_image_t *image) +{ + if (image == NULL) { + return; + } + if (image->info != NULL) { + free_imagetool_image(image->info); + image->info = NULL; + } + + free(image); +} + +/* oci_image refinc */ +void oci_image_refinc(oci_image_t *image) +{ + if (image == NULL) { + return; + } + atomic_int_inc_image(&image->refcnt); +} + +/* oci_image unref */ +void oci_image_unref(oci_image_t *image) +{ + bool is_zero = false; + if (image == NULL) { + return; + } + + is_zero = atomic_int_dec_test_image(&image->refcnt); + if (!is_zero) { + return; + } + + oci_image_free(image); +} + +/* oci_image lock */ +void oci_image_lock(oci_image_t *image) +{ + if (image == NULL) { + return; + } + + if (pthread_mutex_lock(&image->mutex) != 0) { + ERROR("Failed to lock image '%s'", image->info->id); + } +} + +/* oci_image unlock */ +void oci_image_unlock(oci_image_t *image) +{ + if (image == NULL) { + return; + } + + if (pthread_mutex_unlock(&image->mutex) != 0) { + ERROR("Failed to unlock image '%s'", image->info->id); + } +} diff --git a/src/image/oci/oci_image_unix.h b/src/image/oci/oci_image_unix.h new file mode 100644 index 0000000..273c178 --- /dev/null +++ b/src/image/oci/oci_image_unix.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container unix functions + ******************************************************************************/ +#ifndef __ISULAD_IMAGE_UNIX_H__ +#define __ISULAD_IMAGE_UNIX_H__ + +#include + +#include "liblcrd.h" +#include "util_atomic.h" +#include "imagetool_image_status.h" + + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +typedef struct _oci_image_t_ { + pthread_mutex_t mutex; + uint64_t refcnt; + imagetool_image *info; +} oci_image_t; + +void oci_image_refinc(oci_image_t *image); + +void oci_image_unref(oci_image_t *image); + +oci_image_t *oci_image_new(imagetool_image *image_info); + +void oci_image_free(oci_image_t *image); + +void oci_image_lock(oci_image_t *image); + +void oci_image_unlock(oci_image_t *image); + + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* __ISULAD_IMAGE_UNIX_H__ */ diff --git a/src/image/oci/oci_images_list.c b/src/image/oci/oci_images_list.c new file mode 100644 index 0000000..1c6e927 --- /dev/null +++ b/src/image/oci/oci_images_list.c @@ -0,0 +1,112 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci images list functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include "oci_images_list.h" +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "log.h" +#include "utils.h" +#include "liblcrd.h" +#include "image.h" +#include "isula_imtool_interface.h" + +static void set_char_to_terminator(char *p) +{ + *p = '\0'; +} + +static void util_log_output(char *output) +{ + size_t len = 0; + char *tmp_start = NULL; + char *tmp_end = NULL; + + if (output == NULL) { + return; + } + + len = strlen(output); + + for (tmp_start = output; tmp_start < (output + len) && tmp_start != NULL;) { + tmp_end = strchr(tmp_start, '\n'); + if (tmp_end == NULL) { + ERROR("%s", tmp_start); + break; + } + set_char_to_terminator(tmp_end); + ERROR("%s", tmp_start); + *tmp_end = '\n'; + tmp_start = tmp_end + 1; + } + + return; +} + +int do_list_oci_images(im_list_request *request, imagetool_images_list **images) +{ + int ret = -1; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + parser_error err = NULL; + + if (request == NULL || images == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + command_ret = util_exec_cmd(execute_list_images, request, NULL, &stdout_buffer, + &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to list images with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to list images with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec list images command"); + lcrd_set_error_message("Failed to exec list images command"); + } + goto free_out; + } + + if (request->check && stderr_buffer != NULL) { + util_log_output(stderr_buffer); + } + + if (stdout_buffer == NULL) { + ERROR("Failed to list images becase can not get stdoutput"); + lcrd_set_error_message("Failed to list images becase can not get stdoutput"); + goto free_out; + } + + *images = imagetool_images_list_parse_data(stdout_buffer, NULL, &err); + if (*images == NULL) { + ERROR("Failed to parse output json:%s", err); + lcrd_set_error_message("Failed to parse output json:%s", err); + goto free_out; + } + + ret = 0; + +free_out: + free(err); + free(stderr_buffer); + free(stdout_buffer); + return ret; +} diff --git a/src/image/oci/oci_images_list.h b/src/image/oci/oci_images_list.h new file mode 100644 index 0000000..7780c1d --- /dev/null +++ b/src/image/oci/oci_images_list.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide oci images list functions + ******************************************************************************/ + +#ifndef __OCI_LIST_ALL_IMAGES_INFO_H_ +#define __OCI_LIST_ALL_IMAGES_INFO_H_ + +#include "oci_image_type.h" +#include "imagetool_images_list.h" +#include "image.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int do_list_oci_images(im_list_request *request, imagetool_images_list **images); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/image/oci/oci_images_store.c b/src/image/oci/oci_images_store.c new file mode 100644 index 0000000..c93d993 --- /dev/null +++ b/src/image/oci/oci_images_store.c @@ -0,0 +1,813 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide containers store definition + ******************************************************************************/ +#include +#include + +#include "oci_images_store.h" +#include "oci_image_status.h" +#include "oci_images_list.h" + +#include "log.h" +#include "utils.h" +#include "lcrd_config.h" +#include "constants.h" + +pthread_rwlock_t g_image_memory_rwlock; + +typedef struct image_memory_store_t { + map_t *map; // map id oci_image_t + pthread_rwlock_t rwlock; +} image_memory_store; + +typedef struct image_name_id_t { + map_t *map; // map name id + pthread_rwlock_t rwlock; +} image_name_id; + +static image_memory_store *g_images_store = NULL; + +static image_name_id *g_images_ids = NULL; + +/* image_name_id_add */ +static bool image_name_id_add(const char *name, const char *id) +{ + bool ret = false; + if (pthread_rwlock_wrlock(&g_images_ids->rwlock) != 0) { + ERROR("lock name index failed"); + return false; + } + ret = map_replace(g_images_ids->map, (void *)name, (void *)id); + if (pthread_rwlock_unlock(&g_images_ids->rwlock) != 0) { + ERROR("unlock name index failed"); + return false; + } + return ret; +} + +/* image_name_id_remove */ +static bool image_name_id_remove(const char *name) +{ + bool ret = false; + if (pthread_rwlock_wrlock(&g_images_ids->rwlock) != 0) { + ERROR("lock name index failed"); + return false; + } + ret = map_remove(g_images_ids->map, (void *)name); + if (pthread_rwlock_unlock(&g_images_ids->rwlock) != 0) { + ERROR("unlock name index failed"); + return false; + } + return ret; +} + +static int remove_all_names(oci_image_t *image) +{ + int ret = 0; + size_t i = 0; + + for (i = 0; i < image->info->repo_tags_len; i++) { + if (!image_name_id_remove(image->info->repo_tags[i])) { + ERROR("Failed to remove image name %s", image->info->repo_tags[i]); + ret = -1; + } + } + + return ret; +} + +static int register_all_names(imagetool_image *image_info) +{ + int ret = 0; + size_t i = 0; + + for (i = 0; i < image_info->repo_tags_len; i++) { + if (!image_name_id_add(image_info->repo_tags[i], image_info->id)) { + size_t j; + for (j = 0; j < i; j++) { + (void)image_name_id_remove(image_info->repo_tags[j]); + } + ERROR("Failed to register all names of image %s", image_info->id); + ret = -1; + goto out; + } + } + +out: + return ret; +} + +/* image name id free */ +static void image_name_id_free(image_name_id *images_ids) +{ + if (images_ids == NULL) { + return; + } + map_free(images_ids->map); + images_ids->map = NULL; + pthread_rwlock_destroy(&(images_ids->rwlock)); + free(images_ids); +} + +/* image name id new */ +static image_name_id *image_name_id_new(void) +{ + int ret; + image_name_id *tmp = NULL; + + tmp = util_common_calloc_s(sizeof(image_name_id)); + if (tmp == NULL) { + ERROR("Out of memory"); + return NULL; + } + ret = pthread_rwlock_init(&(tmp->rwlock), NULL); + if (ret != 0) { + ERROR("Failed to init name image name id rwlock"); + free(tmp); + return NULL; + } + tmp->map = map_new(MAP_STR_STR, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC); + if (tmp->map == NULL) { + ERROR("Out of memory"); + goto error_out; + } + return tmp; +error_out: + image_name_id_free(tmp); + return NULL; +} + +/* get image id by name */ +char *image_id_get_by_name(const char *name) +{ + char *id = NULL; + if (name == NULL) { + return id; + } + if (pthread_rwlock_rdlock(&g_images_ids->rwlock) != 0) { + ERROR("lock name index failed"); + return id; + } + id = map_search(g_images_ids->map, (void *)name); + if (pthread_rwlock_unlock(&g_images_ids->rwlock) != 0) { + ERROR("unlock name index failed"); + } + return id; +} + +/* image_name_id init */ +int image_name_id_init(void) +{ + g_images_ids = image_name_id_new(); + if (g_images_ids == NULL) { + return -1; + } + return 0; +} + +/* memory store map kvfree */ +static void oci_image_memory_store_map_kvfree(void *key, void *value) +{ + free(key); + + oci_image_unref((oci_image_t *)value); +} + +/* memory store free */ +static void oci_images_memory_store_free(image_memory_store *store) +{ + if (store == NULL) { + return; + } + map_free(store->map); + store->map = NULL; + pthread_rwlock_destroy(&(store->rwlock)); + free(store); +} + +/* memory store new */ +static image_memory_store *oci_images_memory_store_new(void) +{ + int ret; + image_memory_store *store = NULL; + + store = util_common_calloc_s(sizeof(image_memory_store)); + if (store == NULL) { + ERROR("Out of memory"); + return NULL; + } + ret = pthread_rwlock_init(&(store->rwlock), NULL); + if (ret != 0) { + ERROR("Failed to init memory store rwlock"); + free(store); + return NULL; + } + store->map = map_new(MAP_STR_PTR, MAP_DEFAULT_CMP_FUNC, oci_image_memory_store_map_kvfree); + if (store->map == NULL) { + ERROR("Out of memory"); + goto error_out; + } + return store; +error_out: + oci_images_memory_store_free(store); + return NULL; +} + +/* oci image store add or update */ +static bool oci_image_store_add_or_update(const char *id, imagetool_image *image_info) +{ + bool ret = false; + oci_image_t *image = NULL; + + if (pthread_rwlock_wrlock(&g_images_store->rwlock)) { + ERROR("lock memory store failed"); + free_imagetool_image(image_info); + return false; + } + + /* Replace only but not allocate a new one if exist.If allocate a + * new object,locker will be invalid and cannot lock image anymore. */ + image = map_search(g_images_store->map, (void *)id); + if (image == NULL) { + image = oci_image_new(image_info); + if (image == NULL) { + free_imagetool_image(image_info); + ERROR("oci image new failed"); + goto out; + } + ret = map_replace(g_images_store->map, (void *)id, (void *)image); + if (!ret) { + oci_image_free(image); + ERROR("oci image new failed"); + goto out; + } + } else { + free_imagetool_image(image->info); + image->info = image_info; + ret = true; + } + +out: + + if (pthread_rwlock_unlock(&g_images_store->rwlock)) { + ERROR("unlock memory store failed"); + return false; + } + return ret; +} + +/* oci images store remove */ +static bool oci_images_store_remove(const char *id) +{ + bool ret = false; + if (pthread_rwlock_wrlock(&g_images_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return false; + } + ret = map_remove(g_images_store->map, (void *)id); + if (pthread_rwlock_unlock(&g_images_store->rwlock) != 0) { + ERROR("unlock memory store failed"); + return false; + } + return ret; +} + +static int register_new_oci_image(imagetool_image *image_info) +{ + int ret = 0; + + if (!oci_image_store_add_or_update(image_info->id, image_info)) { + ret = -1; + goto out; + } + + ret = register_all_names(image_info); + if (ret != 0) { + oci_images_store_remove(image_info->id); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int update_old_image_by_id(const char *id) +{ + int ret = 0; + imagetool_image *image_info = NULL; + + image_info = oci_image_get_image_info_by_name(id); + if (image_info == NULL) { + WARN("Failed to status old oci image %s, may be remeved", id); + ret = 0; + goto out; + } + + ret = register_new_oci_image(image_info); + +out: + return ret; +} + +static void remove_graph_root() +{ + int ret = 0; + char *graph_root = NULL; + + graph_root = conf_get_graph_rootpath(); + if (graph_root == NULL) { + ERROR("Failed to get image graph root path"); + return; + } + + ret = util_recursive_rmdir(graph_root, 0); + if (ret != 0) { + ERROR("Failed to delete image graph root directory %s: %s", graph_root, strerror(errno)); + } + + free(graph_root); +} + +/* WARNING:This function may free all memory of *all_images if failed + * or change value of *all_images if success. */ +static int try_list_oci_images(const char *check_file, imagetool_images_list **all_images) +{ + int ret = 0; + im_list_request *im_request = NULL; + int retry_count = 0; + int max_retry = 3; + bool list_images_ok = false; + bool need_check = false; + + if (util_file_exists(check_file) && conf_get_image_layer_check_flag()) { + INFO("OCI image checked flag %s exist, need to check image integrity", check_file); + need_check = true; + } + + im_request = util_common_calloc_s(sizeof(im_list_request)); + if (im_request == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + im_request->check = need_check; + + do { + ret = do_list_oci_images(im_request, all_images); + if (ret != 0 || *all_images == NULL) { + list_images_ok = false; + if (retry_count < max_retry) { + remove_graph_root(); /* remove graph root */ + retry_count++; + free_imagetool_images_list(*all_images); + *all_images = NULL; + } + } else { + list_images_ok = true; + } + } while (!list_images_ok && retry_count < max_retry); + if (!list_images_ok) { + ERROR("Failed to load all oci images and retry %d times", retry_count); + ret = -1; + goto out; + } + +out: + free_im_list_request(im_request); + + return ret; +} + +int load_all_oci_images() +{ + int ret = 0; + int fd = -1; + size_t i = 0; + char *check_file = NULL; + imagetool_images_list *all_images = NULL; + imagetool_image *tmp = NULL; + + check_file = conf_get_graph_check_flag_file(); + if (check_file == NULL) { + ERROR("Failed to get oci image checked flag"); + ret = -1; + goto out; + } + + ret = try_list_oci_images(check_file, &all_images); + if (ret < 0) { + goto out; + } + + ret = util_build_dir(check_file); + if (ret) { + ERROR("Failed to create directory for checked flag file: %s", check_file); + ret = -1; + goto out; + } + + fd = util_open(check_file, O_RDWR | O_CREAT, DEFAULT_SECURE_FILE_MODE); + if (fd < 0) { + ERROR("Failed to create checked file: %s", check_file); + ret = -1; + goto out; + } + + for (i = 0; i < all_images->images_len; i++) { + tmp = all_images->images[i]; + all_images->images[i] = NULL; + ret = register_new_oci_image(tmp); + if (ret != 0) { + ERROR("Failed to register oci image"); + ret = -1; + goto out; + } + } + +out: + if (fd >= 0) { + close(fd); + } + free(check_file); + free_imagetool_images_list(all_images); + return ret; +} + +/* oci images store list */ +int oci_images_store_list(oci_image_t ***out, size_t *size) +{ + int ret = -1; + size_t i; + oci_image_t **images = NULL; + map_itor *itor = NULL; + + if (out == NULL || size == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + if (pthread_rwlock_rdlock(&g_images_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return -1; + } + + *size = map_size(g_images_store->map); + if (*size == 0) { + ret = 0; + goto unlock; + } + if (*size > SIZE_MAX / sizeof(oci_image_t *)) { + ERROR("Containers store list is too long!"); + goto unlock; + } + images = util_common_calloc_s(sizeof(oci_image_t *) * (*size)); + if (images == NULL) { + ERROR("Out of memory"); + goto unlock; + } + + itor = map_itor_new(g_images_store->map); + if (itor == NULL) { + ERROR("Out of memory"); + goto unlock; + } + + for (i = 0; map_itor_valid(itor) && i < *size; map_itor_next(itor), i++) { + images[i] = map_itor_value(itor); + oci_image_refinc(images[i]); + } + ret = 0; +unlock: + if (pthread_rwlock_unlock(&g_images_store->rwlock)) { + ERROR("unlock memory store failed"); + } + map_itor_free(itor); + if (ret != 0) { + free(images); + *size = 0; + images = NULL; + } + *out = images; + return ret; +} + +/* oci images store size */ +size_t oci_images_store_size(void) +{ + size_t count = 0; + if (pthread_rwlock_rdlock(&g_images_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return count; + } + count = map_size(g_images_store->map); + if (pthread_rwlock_unlock(&g_images_store->rwlock) != 0) { + ERROR("unlock memory store failed"); + return count; + } + return count; +} + +/* oci images store init */ +int oci_images_store_init(void) +{ + if (pthread_rwlock_init(&g_image_memory_rwlock, NULL) != 0) { + ERROR("Failed to init image memory rwlock"); + return -1; + } + + g_images_store = oci_images_memory_store_new(); + if (g_images_store == NULL) { + pthread_rwlock_destroy(&g_image_memory_rwlock); + return -1; + } + return 0; +} + +/* oci images store get by image id */ +oci_image_t *oci_images_store_get_by_id(const char *id) +{ + oci_image_t *image = NULL; + if (id == NULL) { + return NULL; + } + if (pthread_rwlock_rdlock(&g_images_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return image; + } + image = map_search(g_images_store->map, (void *)id); + oci_image_refinc(image); + if (pthread_rwlock_unlock(&g_images_store->rwlock) != 0) { + ERROR("unlock memory store failed"); + return image; + } + return image; +} + +/* oci images store get image by image name*/ +oci_image_t *oci_images_store_get_by_name(const char *name) +{ + char *id = NULL; + + if (name == NULL) { + ERROR("No image name supplied"); + return NULL; + } + + id = image_id_get_by_name(name); + if (id == NULL) { + INFO("Could not find entity for %s", name); + return NULL; + } + + return oci_images_store_get_by_id(id); +} + +/* oci images store get image by prefix*/ +oci_image_t *oci_images_store_get_by_prefix(const char *prefix) +{ + oci_image_t *image = NULL; + map_itor *itor = NULL; + bool ret = false; + char *id = NULL; + + if (prefix == NULL) { + return NULL; + } + if (pthread_rwlock_rdlock(&g_images_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return NULL; + } + + itor = map_itor_new(g_images_store->map); + if (itor == NULL) { + ERROR("Out of memory"); + ret = false; + goto unlock; + } + + for (; map_itor_valid(itor); map_itor_next(itor)) { + id = map_itor_key(itor); + if (id == NULL) { + ERROR("Out of memory"); + ret = false; + goto unlock; + } + if (strncmp(id, prefix, strlen(prefix)) == 0) { + if (image != NULL) { + ERROR("Multiple IDs found with provided prefix: %s", prefix); + ret = false; + goto unlock; + } else { + image = map_itor_value(itor); + } + } + } + + ret = true; + oci_image_refinc(image); + +unlock: + if (pthread_rwlock_unlock(&g_images_store->rwlock) != 0) { + ERROR("unlock memory store failed"); + } + map_itor_free(itor); + if (!ret) { + image = NULL; + } + return image; +} + +// oci_images_store_get_nolock looks for a image using the provided information, which could be +// one of the following inputs from the caller: +// - A full image ID, which will exact match a image in daemon's list +// - A image name, which will only exact match via the oci_images_store_get_by_name() function +// - A partial image ID prefix (e.g. short ID) of any length that is +// unique enough to only return a single container object +// If none of these searches succeed, an error is returned +oci_image_t *oci_images_store_get_nolock(const char *id_or_name) +{ + oci_image_t *image = NULL; + + if (id_or_name == NULL) { + ERROR("No container name or ID supplied"); + return NULL; + } + + // A full image ID, which will exact match a container in daemon's list + image = oci_images_store_get_by_id(id_or_name); + if (image != NULL) { + return image; + } + + // A image name, which will only exact match via the oci_images_store_get_by_name() function + image = oci_images_store_get_by_name(id_or_name); + if (image != NULL) { + return image; + } + + // A partial container ID prefix + image = oci_images_store_get_by_prefix(id_or_name); + if (image != NULL) { + return image; + } + + return NULL; +} + +/* Always do lock when get images from images store. */ +oci_image_t *oci_images_store_get(const char *id_or_name) +{ + oci_image_t *img = NULL; + + if (id_or_name == NULL) { + ERROR("No container name or ID supplied"); + return NULL; + } + + if (pthread_rwlock_wrlock(&g_image_memory_rwlock) != 0) { + ERROR("lock image memory failed"); + return NULL; + } + + img = oci_images_store_get_nolock(id_or_name); + + if (pthread_rwlock_unlock(&g_image_memory_rwlock) != 0) { + ERROR("unlock memory store failed"); + } + + return img; +} + +int register_new_oci_image_into_memory(const char *name) +{ + int ret = 0; + imagetool_image *image_info = NULL; + oci_image_t *old_image = NULL; + + if (name == NULL) { + ERROR("Empty image name"); + return -1; + } + + if (pthread_rwlock_wrlock(&g_image_memory_rwlock) != 0) { + ERROR("lock image memory failed"); + return -1; + } + + image_info = oci_image_get_image_info_by_name(name); + if (image_info == NULL) { + ERROR("Failed to get oci image %s informations", name); + ret = -1; + goto out; + } + + old_image = oci_images_store_get_nolock(name); + if (old_image == NULL) { + ret = register_new_oci_image(image_info); + if (ret != 0) { + ERROR("Failed to register oci image %s", name); + ret = -1; + } + goto out; + } + + if (strcmp(old_image->info->id, image_info->id) != 0) { + ret = register_new_oci_image(image_info); + if (ret != 0) { + ERROR("Failed to register oci image %s", name); + ret = -1; + goto out; + } + + ret = update_old_image_by_id(old_image->info->id); + if (ret != 0) { + ERROR("Failed to update old oci image %s", old_image->info->id); + ret = -1; + goto out; + } + } else { + /* we have the same image already, just free the image_info memory */ + free_imagetool_image(image_info); + } + +out: + oci_image_unref(old_image); + + if (pthread_rwlock_unlock(&g_image_memory_rwlock) != 0) { + ERROR("unlock memory store failed"); + ret = -1; + } + + return ret; +} + +int remove_oci_image_from_memory(const char *name_or_id) +{ + int ret = 0; + oci_image_t *image = NULL; + imagetool_image *image_info = NULL; + + if (name_or_id == NULL) { + ERROR("No container name or ID supplied"); + return -1; + } + + if (pthread_rwlock_wrlock(&g_image_memory_rwlock) != 0) { + ERROR("lock image memory failed"); + return -1; + } + + image = oci_images_store_get_nolock(name_or_id); + if (image == NULL) { + INFO("No such image exist %s", name_or_id); + ret = 0; + goto free_out; + } + + ret = remove_all_names(image); + if (ret != 0) { + ERROR("Failed to remove all names of image %s from name-id store", image->info->id); + ret = -1; + goto free_out; + } + + image_info = oci_image_get_image_info_by_name(image->info->id); + if (image_info == NULL) { + WARN("Failed to status old oci image %s, may be remeved", image->info->id); + + if (oci_images_store_remove(image->info->id) != true) { + ERROR("Failed to remove image %s from store", image->info->id); + ret = -1; + goto free_out; + } + + ret = 0; + goto free_out; + } + + ret = register_new_oci_image(image_info); + +free_out: + oci_image_unref(image); + + if (pthread_rwlock_unlock(&g_image_memory_rwlock) != 0) { + ERROR("unlock memory store failed"); + ret = -1; + } + + return ret; +} + diff --git a/src/image/oci/oci_images_store.h b/src/image/oci/oci_images_store.h new file mode 100644 index 0000000..7d7c785 --- /dev/null +++ b/src/image/oci/oci_images_store.h @@ -0,0 +1,46 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide containers store definition + ******************************************************************************/ +#ifndef __ISULAD_IMAGE_STORE_H__ +#define __ISULAD_IMAGE_STORE_H__ + +#include "oci_image_unix.h" +#include "map.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +int oci_images_store_init(void); + +int load_all_oci_images(); + +oci_image_t *oci_images_store_get(const char *id_or_name); + +size_t oci_images_store_size(void); + +int oci_images_store_list(oci_image_t ***out, size_t *size); + +int image_name_id_init(void); + +int register_new_oci_image_into_memory(const char *name); + +int remove_oci_image_from_memory(const char *name_or_id); + + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* __LCRD_MEMORY_STORE_H__ */ diff --git a/src/image/oci/oci_login.c b/src/image/oci/oci_login.c new file mode 100644 index 0000000..6ac033b --- /dev/null +++ b/src/image/oci/oci_login.c @@ -0,0 +1,118 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-06-18 + * Description: provide oci login image functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" +#include "image.h" +#include "oci_auth.h" +#include "oci_login.h" + +static bool do_login(im_login_request *request) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + char *stdin_buffer = NULL; + auth_config auth = {0}; + + auth.username = request->username; + auth.password = request->password; + stdin_buffer = pack_input_auth_string(&auth); + if (stdin_buffer == NULL) { + ERROR("Failed to generate image auth info"); + lcrd_set_error_message("Failed to generate image auth info"); + goto free_out; + } + + command_ret = util_exec_cmd(execute_login, request, stdin_buffer, + &stdout_buffer, &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to login with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to login with error: %s", + stderr_buffer); + } else { + ERROR("Failed to exec login command"); + lcrd_set_error_message("Failed to exec login command"); + } + goto free_out; + } + + ret = true; + +free_out: + free_sensitive_string(stdin_buffer); + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + +static int check_login_request_valid(const im_login_request *request) +{ + int ret = -1; + + if (request == NULL) { + ERROR("invalid login request"); + lcrd_set_error_message("invalid login request"); + goto out; + } + + if (request->server == NULL) { + ERROR("Login requires server address"); + lcrd_set_error_message("Login requires server address"); + goto out; + } + + if (request->username == NULL || request->password == NULL) { + ERROR("Missing username or password"); + lcrd_set_error_message("Missing username or password"); + goto out; + } + + ret = 0; + +out: + return ret; +} + + +int oci_login(im_login_request *request) +{ + int ret = 0; + + if (check_login_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + if (!do_login(request)) { + ret = -1; + goto pack_response; + } + +pack_response: + + return ret; +} diff --git a/src/image/oci/oci_login.h b/src/image/oci/oci_login.h new file mode 100644 index 0000000..6fe6ed0 --- /dev/null +++ b/src/image/oci/oci_login.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-06-18 + * Description: provide login function definition + ******************************************************************************/ +#ifndef __OCI_LOGIN_H +#define __OCI_LOGIN_H + +#include +#include "image.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int oci_login(im_login_request *request); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_logout.c b/src/image/oci/oci_logout.c new file mode 100644 index 0000000..fa0e506 --- /dev/null +++ b/src/image/oci/oci_logout.c @@ -0,0 +1,99 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-06-18 + * Description: provide oci logout image functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" +#include "image.h" +#include "oci_logout.h" + +static bool do_logout(im_logout_request *request) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + + command_ret = util_exec_cmd(execute_logout, request, NULL, &stdout_buffer, + &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to logout with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to logout with error: %s", + stderr_buffer); + } else { + ERROR("Failed to exec logout command"); + lcrd_set_error_message("Failed to exec logout command"); + } + goto free_out; + } + + ret = true; + +free_out: + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + +static int check_logout_request_valid(const im_logout_request *request) +{ + int ret = -1; + + if (request == NULL) { + ERROR("invalid logout request"); + lcrd_set_error_message("invalid logout request"); + goto out; + } + + if (request->server == NULL) { + ERROR("Logout requires server address"); + lcrd_set_error_message("Logout requires server address"); + goto out; + } + + ret = 0; + +out: + return ret; +} + + +int oci_logout(im_logout_request *request) +{ + int ret = 0; + + if (check_logout_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + if (!do_logout(request)) { + ret = -1; + goto pack_response; + } + +pack_response: + + return ret; +} diff --git a/src/image/oci/oci_logout.h b/src/image/oci/oci_logout.h new file mode 100644 index 0000000..1776909 --- /dev/null +++ b/src/image/oci/oci_logout.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-06-18 + * Description: provide logout function definition + ******************************************************************************/ +#ifndef __OCI_LOGOUT_H +#define __OCI_LOGOUT_H + +#include +#include "image.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int oci_logout(im_logout_request *request); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_rootfs_export.c b/src/image/oci/oci_rootfs_export.c new file mode 100644 index 0000000..46c8ed8 --- /dev/null +++ b/src/image/oci/oci_rootfs_export.c @@ -0,0 +1,146 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-04-06 + * Description: provide oci export rootfs functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_rootfs_export.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" + +static bool do_export(rootfs_export_request *request) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + + command_ret = util_exec_cmd(execute_export_rootfs, request, NULL, &stdout_buffer, + &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to export rootfs with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to export rootfs with error: %s", + stderr_buffer); + } else { + ERROR("Failed to exec export rootfs command"); + lcrd_set_error_message("Failed to exec export rootfs command"); + } + goto free_out; + } + + ret = true; + +free_out: + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + +static int check_export_request_valid(rootfs_export_request *request) +{ + int ret = -1; + + if (request == NULL) { + ERROR("unvalid export request"); + lcrd_set_error_message("unvalid export request"); + goto out; + } + + if (request->id == NULL) { + ERROR("Export rootfs requires container name"); + lcrd_set_error_message("Export rootfs requires container name"); + goto out; + } + + if (request->file == NULL) { + ERROR("Export rootfs requires output file path"); + lcrd_set_error_message("Export rootfs requires output file path"); + goto out; + } + + ret = 0; + +out: + return ret; +} + + +int export_rootfs(rootfs_export_request *request, + rootfs_export_response **response) +{ + int ret = 0; + + if (response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(rootfs_export_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (check_export_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + if (!do_export(request)) { + ERROR("Failed to export rootfs"); + ret = -1; + goto pack_response; + } + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + return ret; +} + +void free_rootfs_export_request(rootfs_export_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->id); + ptr->id = NULL; + free(ptr->file); + ptr->file = NULL; + + free(ptr); +} + +void free_rootfs_export_response(rootfs_export_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + diff --git a/src/image/oci/oci_rootfs_export.h b/src/image/oci/oci_rootfs_export.h new file mode 100644 index 0000000..27b67e5 --- /dev/null +++ b/src/image/oci/oci_rootfs_export.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wangfengtu + * Create: 2019-04-06 + * Description: provide oci export rootfs functions + ******************************************************************************/ + +#ifndef __OCI_EXPORT_ROOTFS_H_ +#define __OCI_EXPORT_ROOTFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *file; + char *id; +} rootfs_export_request; + +typedef struct { + char *errmsg; +} rootfs_export_response; + +int export_rootfs(rootfs_export_request *request, + rootfs_export_response **response); + +void free_rootfs_export_request(rootfs_export_request *ptr); + +void free_rootfs_export_response(rootfs_export_response *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_rootfs_mount.c b/src/image/oci/oci_rootfs_mount.c new file mode 100644 index 0000000..ffaf62c --- /dev/null +++ b/src/image/oci/oci_rootfs_mount.c @@ -0,0 +1,154 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: 李峰 + * Create: 2018-11-08 + * Description: provide oci mount rootfs functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_rootfs_mount.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" + +static bool do_mount(rootfs_mount_request *request, char **mounted_rootfs) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + + command_ret = util_exec_cmd(execute_mount_rootfs, request, NULL, &stdout_buffer, + &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to mount rootfs with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to mount rootfs with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec mount rootfs command"); + lcrd_set_error_message("Failed to exec mount rootfs command"); + } + goto free_out; + } + + if (stdout_buffer == NULL) { + ERROR("Failed to mount rootfs becase can not get stdoutput"); + lcrd_set_error_message("Failed to mount rootfs becase can not get stdoutput"); + goto free_out; + } + + *mounted_rootfs = util_strdup_s(stdout_buffer); + + ret = true; + +free_out: + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + +static int check_mount_request_valid(rootfs_mount_request *request) +{ + int ret = -1; + + if (request == NULL) { + ERROR("unvalid mount request"); + lcrd_set_error_message("unvalid mount request"); + goto out; + } + + if (request->name_id == NULL) { + ERROR("Mount rootfs requires container name or id"); + lcrd_set_error_message("Mount rootfs requires container name or id"); + goto out; + } + + ret = 0; + +out: + return ret; +} + + +int mount_rootfs(rootfs_mount_request *request, + rootfs_mount_response **response) +{ + int ret = 0; + char *name_id = NULL; + char *mounted_rootfs = NULL; + + if (response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(rootfs_mount_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (check_mount_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + name_id = request->name_id; + + EVENT("Event: {Object: %s, Type: mounting rootfs}", name_id); + + if (!do_mount(request, &mounted_rootfs)) { + ERROR("Failed to mount rootfs"); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: mounted rootfs}", name_id); + +pack_response: + free(mounted_rootfs); + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + return ret; +} + +void free_rootfs_mount_request(rootfs_mount_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->name_id); + ptr->name_id = NULL; + + free(ptr); +} + +void free_rootfs_mount_response(rootfs_mount_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + diff --git a/src/image/oci/oci_rootfs_mount.h b/src/image/oci/oci_rootfs_mount.h new file mode 100644 index 0000000..fcad187 --- /dev/null +++ b/src/image/oci/oci_rootfs_mount.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: 李峰 + * Create: 2018-11-08 + * Description: provide oci mount rootfs functions + ******************************************************************************/ + +#ifndef __OCI_MOUNT_ROOTFS_H_ +#define __OCI_MOUNT_ROOTFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *name_id; +} rootfs_mount_request; + +typedef struct { + char *errmsg; +} rootfs_mount_response; + +int mount_rootfs(rootfs_mount_request *request, + rootfs_mount_response **response); + +void free_rootfs_mount_request(rootfs_mount_request *ptr); + +void free_rootfs_mount_response(rootfs_mount_response *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_rootfs_prepare.c b/src/image/oci/oci_rootfs_prepare.c new file mode 100644 index 0000000..b275130 --- /dev/null +++ b/src/image/oci/oci_rootfs_prepare.c @@ -0,0 +1,230 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: 李峰 + * Create: 2018-11-08 + * Description: provide oci prepare rootfs functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_rootfs_prepare.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" + +static bool do_prepare(rootfs_prepare_request *request, imagetool_prepare_response **response) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + parser_error err = NULL; + + command_ret = util_exec_cmd(execute_prepare_rootfs, request, NULL, &stdout_buffer, + &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to prepare rootfs with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to prepare rootfs with error: %s", + stderr_buffer); + } else { + ERROR("Failed to exec prepare rootfs command"); + lcrd_set_error_message("Failed to exec prepare rootfs command"); + } + goto free_out; + } + + if (stdout_buffer == NULL) { + ERROR("Failed to prepare rootfs becase can not get stdoutput"); + lcrd_set_error_message("Failed to prepare rootfs becase can not get stdoutput"); + goto free_out; + } + + *response = imagetool_prepare_response_parse_data(stdout_buffer, NULL, &err); + if (*response == NULL) { + ERROR("Failed to parse isulad-kit output: %s", stdout_buffer); + lcrd_set_error_message("Failed to parse isulad-kit output"); + goto free_out; + } + + ret = true; + +free_out: + free(err); + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + +static int check_prepare_request_valid(rootfs_prepare_request *request) +{ + int ret = -1; + + if (request == NULL) { + ERROR("unvalid prepare request"); + lcrd_set_error_message("unvalid prepare request"); + goto out; + } + + if (request->image == NULL) { + ERROR("Prepare rootfs requires an image"); + lcrd_set_error_message("Prepare rootfs requires an image"); + goto out; + } + + if (request->name == NULL) { + ERROR("Prepare rootfs requires container name"); + lcrd_set_error_message("Prepare rootfs requires container name"); + goto out; + } + + ret = 0; + +out: + return ret; +} + +int prepare_rootfs_and_get_image_conf(rootfs_prepare_request *request, + rootfs_prepare_and_get_image_conf_response **response) +{ + int ret = 0; + char *name = NULL; + char *image = NULL; + imagetool_prepare_response *tool_response = NULL; + + if (response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(rootfs_prepare_and_get_image_conf_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (check_prepare_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + image = request->image; + name = request->name; + + EVENT("Event: {Object: %s, Type: preparing rootfs with image %s}", name, image); + + if (!do_prepare(request, &tool_response)) { + ERROR("Failed to prepare rootfs"); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: prepared rootfs with image %s}", name, image); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + (*response)->raw_response = tool_response; + + return ret; +} + +int prepare_rootfs(rootfs_prepare_request *request, + rootfs_prepare_response **response) +{ + int ret = 0; + char *name = NULL; + char *image = NULL; + imagetool_prepare_response *tool_response = NULL; + + if (response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(rootfs_prepare_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (check_prepare_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + image = request->image; + name = request->name; + + EVENT("Event: {Object: %s, Type: preparing rootfs with image %s}", name, image); + + if (!do_prepare(request, &tool_response)) { + ERROR("Failed to prepare rootfs"); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: prepared rootfs with image %s}", name, image); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + if (tool_response != NULL) { + (*response)->rootfs = tool_response->mount_point; + tool_response->mount_point = NULL; + free_imagetool_prepare_response(tool_response); + } + + return ret; +} + +void free_rootfs_prepare_request(rootfs_prepare_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->image); + ptr->image = NULL; + free(ptr->name); + ptr->name = NULL; + free(ptr->id); + ptr->id = NULL; + util_free_array(ptr->storage_opts); + ptr->storage_opts = NULL; + ptr->storage_opts_len = 0; + + free(ptr); +} + +void free_rootfs_prepare_and_get_image_conf_response(rootfs_prepare_and_get_image_conf_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free_imagetool_prepare_response(ptr->raw_response); + ptr->raw_response = NULL; + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} diff --git a/src/image/oci/oci_rootfs_prepare.h b/src/image/oci/oci_rootfs_prepare.h new file mode 100644 index 0000000..8ab9681 --- /dev/null +++ b/src/image/oci/oci_rootfs_prepare.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: 李峰 + * Create: 2018-11-08 + * Description: provide oci prepare rootfs functions + ******************************************************************************/ + +#ifndef __OCI_PREPARE_ROOTFS_H_ +#define __OCI_PREPARE_ROOTFS_H_ + +#include "imagetool_prepare_response.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *image; + char *name; + char *id; + char **storage_opts; + size_t storage_opts_len; +} rootfs_prepare_request; + +typedef struct { + char *errmsg; + char *rootfs; +} rootfs_prepare_response; + +typedef struct { + char *errmsg; + imagetool_prepare_response *raw_response; +} rootfs_prepare_and_get_image_conf_response; + +int prepare_rootfs(rootfs_prepare_request *request, + rootfs_prepare_response **response); + +void free_rootfs_prepare_request(rootfs_prepare_request *ptr); + +int prepare_rootfs_and_get_image_conf(rootfs_prepare_request *request, + rootfs_prepare_and_get_image_conf_response **response); +void free_rootfs_prepare_and_get_image_conf_response(rootfs_prepare_and_get_image_conf_response *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_rootfs_remove.c b/src/image/oci/oci_rootfs_remove.c new file mode 100644 index 0000000..8a0b3a3 --- /dev/null +++ b/src/image/oci/oci_rootfs_remove.c @@ -0,0 +1,151 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: 李峰 + * Create: 2018-11-08 + * Description: provide oci remove rootfs functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_rootfs_remove.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" + +#define CONTAINER_NOT_KNOWN_ERR "container not known" + +static bool do_remove(rootfs_remove_request *request) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + + command_ret = util_exec_cmd(execute_remove_rootfs, request, NULL, &stdout_buffer, + &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + if (strstr(stderr_buffer, CONTAINER_NOT_KNOWN_ERR) != NULL) { + DEBUG("Container %s may already removed", request->name_id); + ret = true; + goto free_out; + } + ERROR("Failed to remove rootfs with error: %s", stderr_buffer); + lcrd_try_set_error_message("Failed to remove rootfs with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec remove rootfs command"); + lcrd_try_set_error_message("Failed to exec remove rootfs command"); + } + goto free_out; + } + + ret = true; + +free_out: + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + +static int check_remove_request_valid(rootfs_remove_request *request) +{ + int ret = -1; + + if (request == NULL) { + ERROR("unvalid remove request"); + lcrd_set_error_message("unvalid remove request"); + goto out; + } + + if (request->name_id == NULL) { + ERROR("remove rootfs requires container name or id"); + lcrd_set_error_message("remove rootfs requires container name or id"); + goto out; + } + + ret = 0; + +out: + return ret; +} + + +int remove_rootfs(rootfs_remove_request *request, + rootfs_remove_response **response) +{ + int ret = 0; + char *name_id = NULL; + + if (response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(rootfs_remove_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (check_remove_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + name_id = request->name_id; + + EVENT("Event: {Object: %s, Type: removeing rootfs}", name_id); + + if (!do_remove(request)) { + ERROR("Failed to remove rootfs"); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: removed rootfs}", name_id); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + return ret; +} + +void free_rootfs_remove_request(rootfs_remove_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->name_id); + ptr->name_id = NULL; + + free(ptr); +} + +void free_rootfs_remove_response(rootfs_remove_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + diff --git a/src/image/oci/oci_rootfs_remove.h b/src/image/oci/oci_rootfs_remove.h new file mode 100644 index 0000000..7e3fb0d --- /dev/null +++ b/src/image/oci/oci_rootfs_remove.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: 李峰 + * Create: 2018-11-08 + * Description: provide oci remove rootfs functions + ******************************************************************************/ + +#ifndef __OCI_REMOVE_ROOTFS_H_ +#define __OCI_REMOVE_ROOTFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *name_id; +} rootfs_remove_request; + +typedef struct { + char *errmsg; +} rootfs_remove_response; + +int remove_rootfs(rootfs_remove_request *request, + rootfs_remove_response **response); + +void free_rootfs_remove_request(rootfs_remove_request *ptr); + +void free_rootfs_remove_response(rootfs_remove_response *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/image/oci/oci_rootfs_umount.c b/src/image/oci/oci_rootfs_umount.c new file mode 100644 index 0000000..5c6d9a4 --- /dev/null +++ b/src/image/oci/oci_rootfs_umount.c @@ -0,0 +1,143 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: 李峰 + * Create: 2018-11-08 + * Description: provide oci umount rootfs functions + ******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include + +#include "securec.h" +#include "oci_rootfs_umount.h" +#include "utils.h" +#include "log.h" +#include "liblcrd.h" +#include "isula_imtool_interface.h" + +static bool do_umount(rootfs_umount_request *request) +{ + bool ret = false; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + + command_ret = util_exec_cmd(execute_umount_rootfs, request, NULL, &stdout_buffer, &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to umount rootfs with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to umount rootfs with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec umount rootfs command"); + lcrd_set_error_message("Failed to exec umount rootfs command"); + } + goto free_out; + } + + ret = true; + +free_out: + free(stderr_buffer); + free(stdout_buffer); + return ret; +} + +static int check_umount_request_valid(rootfs_umount_request *request) +{ + int ret = -1; + + if (request == NULL) { + ERROR("unvalid umount request"); + lcrd_set_error_message("unvalid umount request"); + goto out; + } + + if (request->name_id == NULL) { + ERROR("Umount rootfs requires container name or id"); + lcrd_set_error_message("Umount rootfs requires container name or id"); + goto out; + } + + ret = 0; + +out: + return ret; +} + + +int umount_rootfs(rootfs_umount_request *request, + rootfs_umount_response **response) +{ + int ret = 0; + char *name_id = NULL; + + if (response == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + *response = util_common_calloc_s(sizeof(rootfs_umount_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (check_umount_request_valid(request) != 0) { + ret = -1; + goto pack_response; + } + + name_id = request->name_id; + + EVENT("Event: {Object: %s, Type: umounting rootfs}", name_id); + + if (!do_umount(request)) { + ERROR("Failed to umount rootfs"); + ret = -1; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: umounted rootfs}", name_id); + +pack_response: + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + } + + return ret; +} + +void free_rootfs_umount_request(rootfs_umount_request *ptr) +{ + if (ptr == NULL) { + return; + } + free(ptr->name_id); + ptr->name_id = NULL; + + free(ptr); +} + +void free_rootfs_umount_response(rootfs_umount_response *ptr) +{ + if (ptr == NULL) { + return; + } + + free(ptr->errmsg); + ptr->errmsg = NULL; + + free(ptr); +} + diff --git a/src/image/oci/oci_rootfs_umount.h b/src/image/oci/oci_rootfs_umount.h new file mode 100644 index 0000000..1761861 --- /dev/null +++ b/src/image/oci/oci_rootfs_umount.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: 李峰 + * Create: 2018-11-08 + * Description: provide oci umount rootfs functions + ******************************************************************************/ + +#ifndef __OCI_UMOUNT_ROOTFS_H_ +#define __OCI_UMOUNT_ROOTFS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char *name_id; +} rootfs_umount_request; + +typedef struct { + char *errmsg; +} rootfs_umount_response; + +int umount_rootfs(rootfs_umount_request *request, + rootfs_umount_response **response); + +void free_rootfs_umount_request(rootfs_umount_request *ptr); + +void free_rootfs_umount_response(rootfs_umount_response *ptr); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/json/CMakeLists.txt b/src/json/CMakeLists.txt new file mode 100644 index 0000000..bfd9a9c --- /dev/null +++ b/src/json/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/schema) diff --git a/src/json/oci_runtime_hooks.c b/src/json/oci_runtime_hooks.c new file mode 100644 index 0000000..b3ac856 --- /dev/null +++ b/src/json/oci_runtime_hooks.c @@ -0,0 +1,63 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2018-11-07 + * Description: provide oci runtime hooks functions + *******************************************************************************/ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include "oci_runtime_hooks.h" + +#include "log.h" +#include "utils.h" + +#define PARSE_ERR_BUFFER_SIZE 1024 + +oci_runtime_spec_hooks *oci_runtime_spec_hooks_parse_file(const char *filename, + const struct parser_context *ctx, + parser_error *err) +{ + yajl_val tree; + size_t filesize; + char *content = NULL; + char errbuf[PARSE_ERR_BUFFER_SIZE] = { 0 }; + struct parser_context tmp_ctx = { 0 }; + + if (filename == NULL || err == NULL) { + return NULL; + } + + *err = NULL; + if (ctx == NULL) { + ctx = &tmp_ctx; + } + content = read_file(filename, &filesize); + if (content == NULL) { + if (asprintf(err, "cannot read the file: %s", filename) < 0) { + *err = util_strdup_s("error allocating memory"); + } + return NULL; + } + tree = yajl_tree_parse(content, errbuf, sizeof(errbuf)); + free(content); + if (tree == NULL) { + if (asprintf(err, "cannot parse the file: %s", errbuf) < 0) { + *err = util_strdup_s("error allocating memory"); + } + return NULL; + } + oci_runtime_spec_hooks *ptr = make_oci_runtime_spec_hooks(tree, ctx, err); + yajl_tree_free(tree); + return ptr; +} diff --git a/src/json/oci_runtime_hooks.h b/src/json/oci_runtime_hooks.h new file mode 100644 index 0000000..3c86126 --- /dev/null +++ b/src/json/oci_runtime_hooks.h @@ -0,0 +1,24 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container oci runtime hooks functions definition + *******************************************************************************/ + +#ifndef _CONTAINER_HOOKS_H +# define _CONTAINER_HOOKS_H + +# include "oci_runtime_spec.h" + +oci_runtime_spec_hooks *oci_runtime_spec_hooks_parse_file(const char *filename, + const struct parser_context *ctx, parser_error *err); + +#endif diff --git a/src/json/parse_common.c b/src/json/parse_common.c new file mode 100644 index 0000000..b44ef08 --- /dev/null +++ b/src/json/parse_common.c @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wujing + * Create: 2019-02-22 + * Description: provide common parse definition + ******************************************************************************/ +#include "parse_common.h" +#include "log.h" + +docker_seccomp *get_seccomp_security_opt_spec(const char *file) +{ + docker_seccomp *seccomp_spec = NULL; + parser_error err = NULL; + + /* parse the input seccomp file */ + seccomp_spec = docker_seccomp_parse_file(file, NULL, &err); + if (seccomp_spec == NULL) { + ERROR("Can not parse seccomp file: %s", err); + goto out; + } + +out: + free(err); + return seccomp_spec; +} diff --git a/src/json/parse_common.h b/src/json/parse_common.h new file mode 100644 index 0000000..00ca45a --- /dev/null +++ b/src/json/parse_common.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wujing + * Create: 2019-02-22 + * Description: provide common parse definition + ******************************************************************************/ + +#ifndef __PARSE_COMMON_H +#define __PARSE_COMMON_H + +#include "docker_seccomp.h" +#ifdef __cplusplus +extern "C" { +#endif + +docker_seccomp *get_seccomp_security_opt_spec(const char *file); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/json/schema/CMakeLists.txt b/src/json/schema/CMakeLists.txt new file mode 100644 index 0000000..66cb000 --- /dev/null +++ b/src/json/schema/CMakeLists.txt @@ -0,0 +1,15 @@ +set(cmdpath python) +set(pysrcpath ${CMAKE_CURRENT_SOURCE_DIR}/src/generate.py) +set(schemapath ${CMAKE_CURRENT_SOURCE_DIR}/schema) +set(outputpath ${CMAKE_BINARY_DIR}/json) + +message("-- Generate .c and .h file into: " ${outputpath}) +add_subdirectory(src) + +execute_process(COMMAND ${cmdpath} ${pysrcpath} --gen-common --gen-ref -r --root=${schemapath} --out=${outputpath} ${schemapath} + ERROR_VARIABLE err + ) + +if (err) + message(FATAL_ERROR "ERROR: " ${err}) +endif() diff --git a/src/json/schema/schema/container/attach-request.json b/src/json/schema/schema/container/attach-request.json new file mode 100644 index 0000000..b391039 --- /dev/null +++ b/src/json/schema/schema/container/attach-request.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "container_id": { + "type": "string" + }, + "attach_stdin": { + "type": "boolean" + }, + "attach_stdout": { + "type": "boolean" + }, + "attach_stderr": { + "type": "boolean" + }, + "stdin": { + "type": "string" + }, + "stdout": { + "type": "string" + }, + "stderr": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/attach-response.json b/src/json/schema/schema/container/attach-response.json new file mode 100644 index 0000000..8c398e8 --- /dev/null +++ b/src/json/schema/schema/container/attach-response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/conf-request.json b/src/json/schema/schema/container/conf-request.json new file mode 100644 index 0000000..fa86505 --- /dev/null +++ b/src/json/schema/schema/container/conf-request.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "container_id": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/conf-response.json b/src/json/schema/schema/container/conf-response.json new file mode 100644 index 0000000..2dfbc25 --- /dev/null +++ b/src/json/schema/schema/container/conf-response.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "container_logpath": { + "type": "string" + }, + "container_logsize": { + "type": "string" + }, + "container_logrotate": { + "type": "uint32" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/config-v2.json b/src/json/schema/schema/container/config-v2.json new file mode 100644 index 0000000..66a79a3 --- /dev/null +++ b/src/json/schema/schema/container/config-v2.json @@ -0,0 +1,161 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Huawei docker image save and load config Specification", + "properties": { + "CommonConfig": { + "properties": { + "Path": { + "type": "string" + }, + "Args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "Config": { + "$ref": "config.json" + }, + "Created": { + "type": "string" + }, + "HasBeenManuallyStopped": { + "type": "boolean" + }, + "HasBeenStartedBefore": { + "type": "boolean" + }, + "Image": { + "type": "string" + }, + "ImageType": { + "type": "string" + }, + "HostnamePath": { + "type": "string" + }, + "HostsPath": { + "type": "string" + }, + "ResolvConfPath": { + "type": "string" + }, + "LogPath": { + "type": "string" + }, + "BaseFs": { + "type": "string" + }, + "MountPoints": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "object", + "properties": { + "Destination": { + "type": "string" + }, + "Driver": { + "type": "string" + }, + "Key": { + "type": "string" + }, + "Name": { + "type": "string" + }, + "Named": { + "type": "boolean" + }, + "Propagation": { + "type": "string" + }, + "RW": { + "type": "boolean" + }, + "Relabel": { + "type": "string" + }, + "Source": { + "type": "string" + } + } + } + } + }, + "Name": { + "type": "string" + }, + "RestartCount": { + "type": "integer" + }, + "id": { + "type": "string" + } + }, + "required": [ + "id", + "Name" + ], + "type": "object" + }, + "State": { + "properties": { + "Dead": { + "type": "boolean" + }, + "Error": { + "type": "string" + }, + "ExitCode": { + "type": "integer" + }, + "FinishedAt": { + "type": "string" + }, + "OOMKilled": { + "type": "boolean" + }, + "Paused": { + "type": "boolean" + }, + "Pid": { + "type": "integer" + }, + "PPid": { + "type": "integer" + }, + "StartTime": { + "type": "uint64" + }, + "PStartTime": { + "type": "uint64" + }, + "RemovalInprogress": { + "type": "boolean" + }, + "Restarting": { + "type": "boolean" + }, + "Running": { + "type": "boolean" + }, + "StartedAt": { + "type": "string" + }, + "Starting": { + "type": "boolean" + }, + "Health": { + "$ref": "../defs.json#/definitions/Health" + } + }, + "type": "object" + } + }, + "required": [ + "CommonConfig", + "State" + ], + "type": "object" +} diff --git a/src/json/schema/schema/container/config.json b/src/json/schema/schema/container/config.json new file mode 100644 index 0000000..61f071b --- /dev/null +++ b/src/json/schema/schema/container/config.json @@ -0,0 +1,95 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "Hostname": { + "type": "string" + }, + "Domainname": { + "type": "string" + }, + "User": { + "type": "string" + }, + "AttachStdin": { + "type": "boolean" + }, + "AttachStdout": { + "type": "boolean" + }, + "AttachStderr": { + "type": "boolean" + }, + "ExposedPorts": { + "$ref": "../defs.json#/definitions/mapStringObject" + }, + "PublishService": { + "type": "string" + }, + "Tty": { + "type": "boolean" + }, + "OpenStdin": { + "type": "boolean" + }, + "StdinOnce": { + "type": "boolean" + }, + "Env": { + "type": "array", + "items": { + "type": "string" + } + }, + "Cmd": { + "type": "array", + "items": { + "type": "string" + } + }, + "ArgsEscaped": { + "type": "boolean" + }, + "Image": { + "type": "string" + }, + "Volumes": { + "$ref": "../defs.json#/definitions/mapStringObject" + }, + "WorkingDir": { + "type": "string" + }, + "Entrypoint": { + "type": "array", + "items": { + "type": "string" + } + }, + "NetworkDisabled": { + "type": "boolean" + }, + "MacAddress": { + "type": "string" + }, + "Onbuild": { + "type": "array", + "items": { + "type": "string" + } + }, + "Labels": { + "$ref": "../defs.json#/definitions/mapStringString" + }, + "Annotations": { + "$ref": "../defs.json#/definitions/mapStringString" + }, + "StopSignal": { + "type": "string" + }, + "HealthCheck": { + "$ref": "../defs.json#/definitions/HealthCheck" + } + }, + "required": [ + ] +} diff --git a/src/json/schema/schema/container/container.json b/src/json/schema/schema/container/container.json new file mode 100644 index 0000000..12e1e49 --- /dev/null +++ b/src/json/schema/schema/container/container.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "pid": { + "type": "int32" + }, + "status": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "command": { + "type": "string" + }, + "ram": { + "type": "double" + }, + "swap": { + "type": "double" + }, + "exit_code": { + "type": "uint32" + }, + "restartcount": { + "type": "uint64" + }, + "Created": { + "type": "int64" + }, + "startat": { + "type": "string" + }, + "finishat": { + "type": "string" + }, + "runtime": { + "type": "string" + }, + "HealthState": { + "type": "string" + }, + "Labels": { + "$ref": "../defs.json#/definitions/mapStringString" + }, + "Annotations": { + "$ref": "../defs.json#/definitions/mapStringString" + } + } +} diff --git a/src/json/schema/schema/container/copy-to-request.json b/src/json/schema/schema/container/copy-to-request.json new file mode 100644 index 0000000..0652d00 --- /dev/null +++ b/src/json/schema/schema/container/copy-to-request.json @@ -0,0 +1,24 @@ +{ + "$schema":"http://json-schema.org/draft-04/schema#", + "type":"object", + "properties":{ + "id":{ + "type": "string" + }, + "runtime":{ + "type": "string" + }, + "srcPath":{ + "type": "string" + }, + "srcIsdir":{ + "type": "boolean" + }, + "srcRebaseName":{ + "type": "string" + }, + "dstPath":{ + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/create-request.json b/src/json/schema/schema/container/create-request.json new file mode 100644 index 0000000..4029540 --- /dev/null +++ b/src/json/schema/schema/container/create-request.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "rootfs": { + "type": "string" + }, + "image": { + "type": "string" + }, + "runtime": { + "type": "string" + }, + "restart": { + "type": "string" + }, + "hostconfig": { + "type": "string" + }, + "customconfig": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/create-response.json b/src/json/schema/schema/container/create-response.json new file mode 100644 index 0000000..346afff --- /dev/null +++ b/src/json/schema/schema/container/create-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "pid": { + "type": "int32" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/custom-config.json b/src/json/schema/schema/container/custom-config.json new file mode 100644 index 0000000..eaee5c0 --- /dev/null +++ b/src/json/schema/schema/container/custom-config.json @@ -0,0 +1,83 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "Env": { + "type": "array", + "items": { + "type": "string" + } + }, + "Hostname": { + "type": "string" + }, + "User": { + "type": "string" + }, + "AttachStdin": { + "type": "boolean" + }, + "AttachStdout": { + "type": "boolean" + }, + "AttachStderr": { + "type": "boolean" + }, + "Tty": { + "type": "boolean" + }, + "OpenStdin": { + "type": "boolean" + }, + "SystemContainer": { + "type": "boolean" + }, + "NsChangeOpt": { + "type": "string" + }, + "Mounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "Entrypoint": { + "type": "array", + "items": { + "type": "string" + } + }, + "Labels": { + "$ref": "../defs.json#/definitions/mapStringString" + }, + "Cmd": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + }, + "annotations": { + "$ref": "../defs.json#/definitions/mapStringString" + }, + "WorkingDir": { + "type": "string" + }, + "Entrypoint": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + }, + "LogConfig": { + "type": "object", + "properties": { + "log_file": { + "type": "string" + }, + "log_file_size": { + "type": "string" + }, + "log_file_rotate": { + "type": "uint64" + } + } + }, + "HealthCheck": { + "$ref": "../defs.json#/definitions/HealthCheck" + } + } +} diff --git a/src/json/schema/schema/container/delete-request.json b/src/json/schema/schema/container/delete-request.json new file mode 100644 index 0000000..d07fd1d --- /dev/null +++ b/src/json/schema/schema/container/delete-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "force": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/container/delete-response.json b/src/json/schema/schema/container/delete-response.json new file mode 100644 index 0000000..517d1f8 --- /dev/null +++ b/src/json/schema/schema/container/delete-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "exit_status": { + "type": "uint32" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/exec-request.json b/src/json/schema/schema/container/exec-request.json new file mode 100644 index 0000000..173365a --- /dev/null +++ b/src/json/schema/schema/container/exec-request.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "container_id": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "attach_stdin": { + "type": "boolean" + }, + "attach_stdout": { + "type": "boolean" + }, + "attach_stderr": { + "type": "boolean" + }, + "stdin": { + "type": "string" + }, + "stdout": { + "type": "string" + }, + "stderr": { + "type": "string" + }, + "argv": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + }, + "env": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + }, + "timeout": { + "type": "int64" + } + } +} diff --git a/src/json/schema/schema/container/exec-response.json b/src/json/schema/schema/container/exec-response.json new file mode 100644 index 0000000..4f06af4 --- /dev/null +++ b/src/json/schema/schema/container/exec-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "pid": { + "type": "int32" + }, + "exit_code": { + "type": "uint32" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/export-request.json b/src/json/schema/schema/container/export-request.json new file mode 100644 index 0000000..90eb44a --- /dev/null +++ b/src/json/schema/schema/container/export-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "file": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/export-response.json b/src/json/schema/schema/container/export-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/export-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/garbage-config.json b/src/json/schema/schema/container/garbage-config.json new file mode 100644 index 0000000..9d2714f --- /dev/null +++ b/src/json/schema/schema/container/garbage-config.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "GcContainers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "runtime": { + "type": "string" + }, + "pid": { + "type": "integer" + }, + "StartTime": { + "type": "uint64" + }, + "ppid": { + "type": "integer" + }, + "PStartTime": { + "type": "uint64" + } + } + } + } + } +} diff --git a/src/json/schema/schema/container/get-id-request.json b/src/json/schema/schema/container/get-id-request.json new file mode 100644 index 0000000..95a2aab --- /dev/null +++ b/src/json/schema/schema/container/get-id-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id_or_name": { + "type": "string" + }, + "label": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/get-id-response.json b/src/json/schema/schema/container/get-id-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/get-id-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/info.json b/src/json/schema/schema/container/info.json new file mode 100644 index 0000000..2ac7d0a --- /dev/null +++ b/src/json/schema/schema/container/info.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "image_type": { + "type": "string" + }, + "pid": { + "type": "int32" + }, + "has_pid": { + "type": "boolean" + }, + "status": { + "type": "integer" + }, + "pids_current": { + "type": "uint64" + }, + "cpu_use_nanos": { + "type": "uint64" + }, + "cpu_use_user": { + "type": "uint64" + }, + "cpu_use_kernel": { + "type": "uint64" + }, + "cpu_system_use": { + "type": "uint64" + }, + "online_cpus": { + "type": "uint32" + }, + "blkio_read": { + "type": "uint64" + }, + "blkio_write": { + "type": "uint64" + }, + "mem_used": { + "type": "uint64" + }, + "mem_limit": { + "type": "uint64" + }, + "kmem_used": { + "type": "uint64" + }, + "kmem_limit": { + "type": "uint64" + } + } +} diff --git a/src/json/schema/schema/container/inspect-request.json b/src/json/schema/schema/container/inspect-request.json new file mode 100644 index 0000000..6811cc5 --- /dev/null +++ b/src/json/schema/schema/container/inspect-request.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "timeout":{ + "type":"integer" + }, + "bformat": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/container/inspect-response.json b/src/json/schema/schema/container/inspect-response.json new file mode 100644 index 0000000..b0fce41 --- /dev/null +++ b/src/json/schema/schema/container/inspect-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "container_json": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/inspect.json b/src/json/schema/schema/container/inspect.json new file mode 100644 index 0000000..48ab66f --- /dev/null +++ b/src/json/schema/schema/container/inspect.json @@ -0,0 +1,139 @@ +{ + "description":"Container Inspect Specification", + "$schema":"http://json-schema.org/draft-04/schema#", + "title":"container inspect", + "type":"object", + "properties":{ + "Id":{ + "type":"string" + }, + "Created":{ + "type":"string" + }, + "Path":{ + "type":"string" + }, + "Args":{ + "type":"array", + "items":{ + "type":"string" + } + }, + "State":{ + "type":"object", + "properties":{ + "Status":{ + "type":"string" + }, + "Running":{ + "type":"boolean" + }, + "Paused":{ + "type":"boolean" + }, + "Restarting":{ + "type":"boolean" + }, + "Pid":{ + "type":"integer" + }, + "ExitCode":{ + "type":"integer" + }, + "Error":{ + "type":"string" + }, + "StartedAt":{ + "type":"string" + }, + "FinishedAt":{ + "type":"string" + }, + "Health": { + "$ref": "../defs.json#/definitions/Health" + } + } + }, + "Image":{ + "type":"string" + }, + "ResolvConfPath": { + "type":"string" + }, + "HostnamePath": { + "type":"string" + }, + "HostsPath": { + "type":"string" + }, + "LogPath":{ + "type":"string" + }, + "Name":{ + "type":"string" + }, + "RestartCount":{ + "type":"integer" + }, + "HostConfig":{ + "$ref": "../host-config.json" + }, + "Mounts": { + "type": "array", + "items": { + "$ref": "../docker/types/mount-point.json" + } + }, + "Config": { + "type":"object", + "properties":{ + "Hostname": { + "type": "string" + }, + "User": { + "type": "string" + }, + "Env": { + "type": "array", + "items": { + "type": "string" + } + }, + "Tty": { + "type": "boolean" + }, + "Cmd": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + }, + "Entrypoint": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + }, + "Labels": { + "$ref": "../defs.json#/definitions/mapStringString" + }, + "Annotations": { + "$ref": "../defs.json#/definitions/mapStringString" + }, + "HealthCheck": { + "$ref": "../defs.json#/definitions/HealthCheck" + } + } + }, + "NetworkSettings":{ + "type":"object", + "properties":{ + "IPAddress":{ + "type":"string" + } + } + } + }, + "required":[ + "Id", + "Name", + "State", + "RestartCount", + "HostConfig", + "NetworkSettings" + ] +} diff --git a/src/json/schema/schema/container/kill-request.json b/src/json/schema/schema/container/kill-request.json new file mode 100644 index 0000000..2f15f84 --- /dev/null +++ b/src/json/schema/schema/container/kill-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "signal": { + "type": "uint32" + } + } +} diff --git a/src/json/schema/schema/container/kill-response.json b/src/json/schema/schema/container/kill-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/kill-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/list-request.json b/src/json/schema/schema/container/list-request.json new file mode 100644 index 0000000..9f512ae --- /dev/null +++ b/src/json/schema/schema/container/list-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "filters": { + "$ref": "../defs.json#/definitions/filters" + }, + "all": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/container/list-response.json b/src/json/schema/schema/container/list-response.json new file mode 100644 index 0000000..0b79c60 --- /dev/null +++ b/src/json/schema/schema/container/list-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "containers": { + "type": "array", + "items": { + "$ref": "container.json" + } + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/logs-request.json b/src/json/schema/schema/container/logs-request.json new file mode 100644 index 0000000..4550b84 --- /dev/null +++ b/src/json/schema/schema/container/logs-request.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "since": { + "type": "string" + }, + "until": { + "type": "string" + }, + "timestamps": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + }, + "tail": { + "type": "uint64" + }, + "details": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/container/logs-response.json b/src/json/schema/schema/container/logs-response.json new file mode 100644 index 0000000..8c398e8 --- /dev/null +++ b/src/json/schema/schema/container/logs-response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/path-stat.json b/src/json/schema/schema/container/path-stat.json new file mode 100644 index 0000000..86b7e1e --- /dev/null +++ b/src/json/schema/schema/container/path-stat.json @@ -0,0 +1,21 @@ +{ + "$schema":"http://json-schema.org/draft-04/schema#", + "type":"object", + "properties":{ + "Name":{ + "type": "string" + }, + "Size":{ + "type": "int64" + }, + "Mode":{ + "type": "uint32" + }, + "Mtime":{ + "$ref": "../timestamp.json" + }, + "LinkTarget":{ + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/pause-request.json b/src/json/schema/schema/container/pause-request.json new file mode 100644 index 0000000..fb7330d --- /dev/null +++ b/src/json/schema/schema/container/pause-request.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/pause-response.json b/src/json/schema/schema/container/pause-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/pause-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/restart-request.json b/src/json/schema/schema/container/restart-request.json new file mode 100644 index 0000000..97efa4f --- /dev/null +++ b/src/json/schema/schema/container/restart-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "timeout": { + "type": "int32" + } + } +} diff --git a/src/json/schema/schema/container/restart-response.json b/src/json/schema/schema/container/restart-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/restart-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/resume-request.json b/src/json/schema/schema/container/resume-request.json new file mode 100644 index 0000000..fb7330d --- /dev/null +++ b/src/json/schema/schema/container/resume-request.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/resume-response.json b/src/json/schema/schema/container/resume-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/resume-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/start-generate-config.json b/src/json/schema/schema/container/start-generate-config.json new file mode 100644 index 0000000..b650ac1 --- /dev/null +++ b/src/json/schema/schema/container/start-generate-config.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "uid": { + "id": "https://opencontainers.org/schema/bundle/process/user/uid", + "$ref": "../defs.json#/definitions/UID" + }, + "gid": { + "id": "https://opencontainers.org/schema/bundle/process/user/gid", + "$ref": "../defs.json#/definitions/GID" + }, + "additionalGids": { + "id": "https://opencontainers.org/schema/bundle/process/user/additionalGids", + "$ref": "../defs.json#/definitions/ArrayOfGIDs" + } + } +} diff --git a/src/json/schema/schema/container/start-request.json b/src/json/schema/schema/container/start-request.json new file mode 100644 index 0000000..16f9462 --- /dev/null +++ b/src/json/schema/schema/container/start-request.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "stdin": { + "type": "string" + }, + "attach-stdin": { + "type": "boolean" + }, + "stdout": { + "type": "string" + }, + "attach-stdout": { + "type": "boolean" + }, + "stderr": { + "type": "string" + }, + "attach-stderr": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/container/start-response.json b/src/json/schema/schema/container/start-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/start-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/stats-request.json b/src/json/schema/schema/container/stats-request.json new file mode 100644 index 0000000..f6e7e45 --- /dev/null +++ b/src/json/schema/schema/container/stats-request.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "filters": { + "$ref": "../defs.json#/definitions/filters" + }, + "runtime": { + "type": "string" + }, + "containers": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + }, + "all": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/container/stats-response.json b/src/json/schema/schema/container/stats-response.json new file mode 100644 index 0000000..52b9fa3 --- /dev/null +++ b/src/json/schema/schema/container/stats-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "container_stats": { + "type": "array", + "items": { + "$ref": "info.json" + } + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/stop-request.json b/src/json/schema/schema/container/stop-request.json new file mode 100644 index 0000000..57ec2b8 --- /dev/null +++ b/src/json/schema/schema/container/stop-request.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "force": { + "type": "boolean" + }, + "timeout": { + "type": "int32" + } + } +} diff --git a/src/json/schema/schema/container/stop-response.json b/src/json/schema/schema/container/stop-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/stop-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/top-request.json b/src/json/schema/schema/container/top-request.json new file mode 100644 index 0000000..3a05c27 --- /dev/null +++ b/src/json/schema/schema/container/top-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "args": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + } + } +} diff --git a/src/json/schema/schema/container/top-response.json b/src/json/schema/schema/container/top-response.json new file mode 100644 index 0000000..be9fa42 --- /dev/null +++ b/src/json/schema/schema/container/top-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "titles": { + "type": "string" + }, + "processes": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/update-request.json b/src/json/schema/schema/container/update-request.json new file mode 100644 index 0000000..56c7a02 --- /dev/null +++ b/src/json/schema/schema/container/update-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "HostConfig": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/update-response.json b/src/json/schema/schema/container/update-response.json new file mode 100644 index 0000000..048f43d --- /dev/null +++ b/src/json/schema/schema/container/update-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/version-request.json b/src/json/schema/schema/container/version-request.json new file mode 100644 index 0000000..8084880 --- /dev/null +++ b/src/json/schema/schema/container/version-request.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + } +} diff --git a/src/json/schema/schema/container/version-response.json b/src/json/schema/schema/container/version-response.json new file mode 100644 index 0000000..38b1ac5 --- /dev/null +++ b/src/json/schema/schema/container/version-response.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "git_commit": { + "type": "string" + }, + "build_time": { + "type": "string" + }, + "root_path": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/container/wait-request.json b/src/json/schema/schema/container/wait-request.json new file mode 100644 index 0000000..d69a5ac --- /dev/null +++ b/src/json/schema/schema/container/wait-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "condition": { + "type": "uint32" + } + } +} diff --git a/src/json/schema/schema/container/wait-response.json b/src/json/schema/schema/container/wait-response.json new file mode 100644 index 0000000..5288b9d --- /dev/null +++ b/src/json/schema/schema/container/wait-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "cc": { + "type": "uint32" + }, + "exit_code": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/cri/checkpoint.json b/src/json/schema/schema/cri/checkpoint.json new file mode 100644 index 0000000..f8e4714 --- /dev/null +++ b/src/json/schema/schema/cri/checkpoint.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "ns": { + "type": "string" + }, + "data": { + "$ref": "checkpoint_data.json" + }, + "checksum": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/cri/checkpoint_data.json b/src/json/schema/schema/cri/checkpoint_data.json new file mode 100644 index 0000000..ac64366 --- /dev/null +++ b/src/json/schema/schema/cri/checkpoint_data.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "port_mappings": { + "type": "array", + "items": { + "$ref": "port_mapping.json" + } + }, + "host_network": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/cri/pod_network.json b/src/json/schema/schema/cri/pod_network.json new file mode 100644 index 0000000..7f57888 --- /dev/null +++ b/src/json/schema/schema/cri/pod_network.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "Interface": { + "type": "string" + } + } + } +} diff --git a/src/json/schema/schema/cri/port_mapping.json b/src/json/schema/schema/cri/port_mapping.json new file mode 100644 index 0000000..4d45b85 --- /dev/null +++ b/src/json/schema/schema/cri/port_mapping.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "protocol": { + "$ref": "../defs.json#/definitions/stringPointer" + }, + "container_port": { + "$ref": "../defs.json#/definitions/int32Pointer" + }, + "host_port": { + "$ref": "../defs.json#/definitions/int32Pointer" + } + } +} diff --git a/src/json/schema/schema/defs.json b/src/json/schema/schema/defs.json new file mode 100644 index 0000000..72bdad7 --- /dev/null +++ b/src/json/schema/schema/defs.json @@ -0,0 +1,308 @@ +{ + "description": "Definitions used throughout the OpenContainer Specification", + "definitions": { + "int8": { + "type": "integer", + "minimum": -128, + "maximum": 127 + }, + "int16": { + "type": "integer", + "minimum": -32768, + "maximum": 32767 + }, + "int32": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "int64": { + "type": "integer", + "minimum": -9223372036854776000, + "maximum": 9223372036854776000 + }, + "uint8": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "uint16": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "uint32": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "uint64": { + "type": "integer", + "minimum": 0, + "maximum": 18446744073709552000 + }, + "int32Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/int32" + }, + { + "type": "null" + } + ] + }, + "uint16Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/uint16" + }, + { + "type": "null" + } + ] + }, + "uint64Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/uint64" + }, + { + "type": "null" + } + ] + }, + "stringPointer": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "percent": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "UID": { + "$ref": "#/definitions/uint32" + }, + "GID": { + "$ref": "#/definitions/uint32" + }, + "ArrayOfGIDs": { + "type": "array", + "items": { + "$ref": "#/definitions/GID" + } + }, + "FilePath": { + "type": "string" + }, + "Env": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "Hook": { + "type": "object", + "properties": { + "path": { + "$ref": "#/definitions/FilePath" + }, + "args": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "env": { + "$ref": "#/definitions/Env" + }, + "timeout": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "path" + ] + }, + "ArrayOfHooks": { + "type": "array", + "items": { + "$ref": "#/definitions/Hook" + } + }, + "IDMapping": { + "type": "object", + "properties": { + "hostID": { + "$ref": "#/definitions/uint32" + }, + "containerID": { + "$ref": "#/definitions/uint32" + }, + "size": { + "$ref": "#/definitions/uint32" + } + }, + "required": [ + "hostID", + "containerID", + "size" + ] + }, + "Mount": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/FilePath" + }, + "destination": { + "$ref": "#/definitions/FilePath" + }, + "options": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "type": { + "type": "string" + } + }, + "required": [ + "destination" + ] + }, + "ArrayOfStrings": { + "type": "array", + "items": { + "type": "string" + } + }, + "mapStringString": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "string" + } + } + }, + "mapStringInt": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "integer" + } + } + }, + "mapStringBool": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "boolean" + } + } + }, + "mapIntString": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "string" + } + } + }, + "mapIntInt": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "integer" + } + } + }, + "mapIntBool": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "boolean" + } + } + }, + "mapStringObject": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "object" + } + } + }, + "ociVersion": { + "description": "The version of Open Container Runtime Specification that the document complies with", + "type": "string" + }, + "annotations": { + "$ref": "#/definitions/mapStringString" + }, + "HealthCheck": { + "type": "object", + "properties": { + "Test": { + "type": "array", + "items": { + "type": "string" + } + }, + "Interval": { + "type": "int64" + }, + "Timeout": { + "type": "int64" + }, + "StartPeriod": { + "type": "int64" + }, + "Retries": { + "type": "integer" + }, + "ExitOnUnhealthy": { + "type": "boolean" + } + } + }, + "Health": { + "type": "object", + "properties": { + "Status": { + "type": "string" + }, + "FailingStreak": { + "type": "integer" + }, + "Log": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Start": { + "type": "string" + }, + "End": { + "type": "string" + }, + "ExitCode": { + "type": "integer" + }, + "Output": { + "type": "string" + } + } + } + } + } + }, + "filters": { + "type": "object", + "patternProperties": { + ".{1,}": { + "$ref": "#/definitions/mapStringBool" + } + } + } + } +} diff --git a/src/json/schema/schema/docker/image/config-v2.json b/src/json/schema/schema/docker/image/config-v2.json new file mode 100644 index 0000000..6c42bf9 --- /dev/null +++ b/src/json/schema/schema/docker/image/config-v2.json @@ -0,0 +1,70 @@ +{ + "description": "Huawei docker image save and load config Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "created": { + "type": "string" + }, + "container": { + "type": "string" + }, + "container_config": { + "$ref": "../../container/config.json" + }, + "docker_version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "config": { + "$ref": "../../container/config.json" + }, + "architecture": { + "type": "string" + }, + "os": { + "type": "string" + }, + "Size": { + "type": "int64" + }, + "parent": { + "type": "string" + }, + "From": { + "type": "string" + }, + "rootfs": { + "$ref": "rootfs.json" + }, + "history": { + "type": "array", + "items": { + "$ref": "history.json" + } + }, + "rawJSON": { + "type": "array", + "items": { + "type": "byte" + } + }, + "computedID": { + "type": "string" + } + }, + "required": [ + "config" + ] +} diff --git a/src/json/schema/schema/docker/image/history.json b/src/json/schema/schema/docker/image/history.json new file mode 100644 index 0000000..a337c89 --- /dev/null +++ b/src/json/schema/schema/docker/image/history.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "created": { + "type": "string" + }, + "author": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "empty_layer": { + "type": "boolean" + } + }, + "required": [ + ] +} diff --git a/src/json/schema/schema/docker/image/rootfs.json b/src/json/schema/schema/docker/image/rootfs.json new file mode 100644 index 0000000..8699d22 --- /dev/null +++ b/src/json/schema/schema/docker/image/rootfs.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "diff_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + ] +} diff --git a/src/json/schema/schema/docker/seccomp.json b/src/json/schema/schema/docker/seccomp.json new file mode 100644 index 0000000..dff02b3 --- /dev/null +++ b/src/json/schema/schema/docker/seccomp.json @@ -0,0 +1,116 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "defaultAction": { + "id": "https://opencontainers.org/schema/bundle/linux/seccomp/defaultAction", + "type": "string" + }, + "archMap": { + "id": "https://opencontainers.org/schema/bundle/linux/seccomp/architectures", + "type": "array", + "items": { + "type": "object", + "properties": { + "architecture": { + "$ref": "../oci/runtime/defs-linux.json#/definitions/SeccompArch" + }, + "subArchitectures": { + "type": "array", + "items": { + "$ref": "../oci/runtime/defs-linux.json#/definitions/SeccompArch" + } + } + } + } + }, + "syscalls": { + "id": "https://opencontainers.org/schema/bundle/linux/seccomp/syscalls", + "type": "array", + "items": { + "type": "object", + "properties": { + "names": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "action": { + "$ref": "../oci/runtime/defs-linux.json#/definitions/SeccompAction" + }, + "args": { + "type": "array", + "items": { + "type": "object", + "properties": { + "index": { + "$ref": "../oci/runtime/defs.json#/definitions/uint32" + }, + "value": { + "$ref": "../oci/runtime/defs.json#/definitions/uint64" + }, + "valueTwo": { + "$ref": "../oci/runtime/defs.json#/definitions/uint64" + }, + "op": { + "$ref": "../oci/runtime/defs-linux.json#/definitions/SeccompOperators" + } + }, + "required": [ + "index", + "value", + "op" + ] + } + }, + "comment": { + "type": "string" + }, + "includes": { + "type": "object", + "properties": { + "arches": { + "type": "array", + "items": { + "$ref": "../oci/runtime/defs-linux.json#/definitions/SeccompArch" + } + }, + "caps": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "excludes": { + "type": "object", + "properties": { + "arches": { + "type": "array", + "items": { + "$ref": "../oci/runtime/defs-linux.json#/definitions/SeccompArch" + } + }, + "caps": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "required": [ + "names", + "action" + ] + } + } + }, + "required": [ + "defaultAction" + ] +} diff --git a/src/json/schema/schema/docker/types/mount-point.json b/src/json/schema/schema/docker/types/mount-point.json new file mode 100644 index 0000000..33b7768 --- /dev/null +++ b/src/json/schema/schema/docker/types/mount-point.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "Name":{ + "type":"string" + }, + "Source":{ + "type":"string" + }, + "Destination":{ + "type":"string" + }, + "Driver":{ + "type":"string" + }, + "Mode":{ + "type":"string" + }, + "RW":{ + "type":"boolean" + }, + "Propagation":{ + "type":"string" + } + } +} diff --git a/src/json/schema/schema/embedded/config.json b/src/json/schema/schema/embedded/config.json new file mode 100644 index 0000000..ec7431c --- /dev/null +++ b/src/json/schema/schema/embedded/config.json @@ -0,0 +1,24 @@ +{ + "description": "Huawei docker search Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ENV": { + "type": "array", + "items": { + "type": "string" + } + }, + "ENTRYPOINT": { + "type": "array", + "items": { + "type": "string" + } + }, + "WORKDIR": { + "type": "string" + } + }, + "required": [ + ] +} diff --git a/src/json/schema/schema/embedded/layers.json b/src/json/schema/schema/embedded/layers.json new file mode 100644 index 0000000..cdb3e9a --- /dev/null +++ b/src/json/schema/schema/embedded/layers.json @@ -0,0 +1,24 @@ +{ + "description": "Huawei docker search Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "mediaType": { + "type": "string" + }, + "size": { + "type": "int64" + }, + "digest": { + "type": "string" + }, + "pathInHost": { + "type": "string" + }, + "pathInContainer": { + "type": "string" + } + }, + "required": [ + ] +} diff --git a/src/json/schema/schema/embedded/manifest.json b/src/json/schema/schema/embedded/manifest.json new file mode 100644 index 0000000..09d9921 --- /dev/null +++ b/src/json/schema/schema/embedded/manifest.json @@ -0,0 +1,30 @@ +{ + "description": "Huawei docker search Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "schemaVersion": { + "type": "uint32" + }, + "imageName": { + "type": "string" + }, + "mediaType": { + "type": "string" + }, + "created": { + "type": "string" + }, + "config": { + "$ref": "config.json" + }, + "layers": { + "type": "array", + "items": { + "$ref": "layers.json" + } + } + }, + "required": [ + ] +} diff --git a/src/json/schema/schema/host-config.json b/src/json/schema/schema/host-config.json new file mode 100644 index 0000000..9f8d44b --- /dev/null +++ b/src/json/schema/schema/host-config.json @@ -0,0 +1,283 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "Binds": { + "$ref": "defs.json#/definitions/ArrayOfStrings" + }, + "NetworkMode": { + "type": "string" + }, + "GroupAdd": { + "$ref": "defs.json#/definitions/ArrayOfStrings" + }, + "IpcMode": { + "type": "string" + }, + "PidMode": { + "type": "string" + }, + "Privileged": { + "type": "boolean" + }, + "SystemContainer": { + "type": "boolean" + }, + "NsChangeFiles": { + "type": "array", + "items": { + "type": "string" + } + }, + "UserRemap": { + "type": "string" + }, + "ShmSize": { + "type": "int64" + }, + "AutoRemove": { + "type": "boolean" + }, + "AutoRemoveBak": { + "type": "boolean" + }, + "ReadonlyRootfs": { + "type": "boolean" + }, + "UTSMode": { + "type": "string" + }, + "UsernsMode": { + "type": "string" + }, + "Sysctls": { + "$ref": "defs.json#/definitions/mapStringString" + }, + "Runtime":{ + "type":"string" + }, + "RestartPolicy": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "MaximumRetryCount": { + "type": "integer" + } + } + }, + "CapAdd": { + "type": "array", + "items": { + "type": "string" + } + }, + "CapDrop": { + "type": "array", + "items": { + "type": "string" + } + }, + "Dns": { + "type": "array", + "items": { + "type": "string" + } + }, + "DnsOptions": { + "type": "array", + "items": { + "type": "string" + } + }, + "DnsSearch": { + "type": "array", + "items": { + "type": "string" + } + }, + "ExtraHosts": { + "type": "array", + "items": { + "type": "string" + } + }, + "HookSpec": { + "type": "string" + }, + "CPUShares": { + "type": "int64" + }, + "Memory": { + "type": "int64" + }, + "OomScoreAdj": { + "type": "integer" + }, + "BlkioWeight": { + "type": "uint16" + }, + "BlkioWeightDevice": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Path": { + "type": "string" + }, + "Weight": { + "type": "uint16" + } + } + } + }, + "BlkioDeviceReadBps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Path": { + "type": "string" + }, + "Rate": { + "type": "uint64" + } + } + } + }, + "BlkioDeviceWriteBps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Path": { + "type": "string" + }, + "Rate": { + "type": "uint64" + } + } + } + }, + "CPUPeriod": { + "type": "int64" + }, + "CPUQuota": { + "type": "int64" + }, + "CPURealtimePeriod": { + "type": "int64" + }, + "CPURealtimeRuntime": { + "type": "int64" + }, + "CpusetCpus": { + "type": "string" + }, + "CpusetMems": { + "type": "string" + }, + "Devices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "CgroupPermissions": { + "type": "string" + }, + "PathInContainer": { + "type": "string" + }, + "PathOnHost": { + "type": "string" + } + } + } + }, + "SecurityOpt": { + "type": "array", + "items": { + "type": "string" + } + }, + "StorageOpt": { + "$ref": "defs.json#/definitions/mapStringString" + }, + "KernelMemory": { + "type": "int64" + }, + "MemoryReservation": { + "type": "int64" + }, + "MemorySwap": { + "type": "int64" + }, + "OomKillDisable": { + "type": "boolean" + }, + "PidsLimit": { + "type": "int64" + }, + "FilesLimit": { + "type": "int64" + }, + "Ulimits": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "Hard": { + "type": "int64" + }, + "Soft": { + "type": "int64" + } + } + } + }, + "Hugetlbs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "PageSize": { + "type": "string" + }, + "Limit": { + "type": "uint64" + } + } + } + }, + "HostChannel": { + "type": "object", + "properties": { + "PathOnHost": { + "type": "string" + }, + "PathInContainer": { + "type": "string" + }, + "Permissions": { + "type": "string" + }, + "Size": { + "type": "uint64" + } + } + }, + "EnvTargetFile": { + "type": "string" + }, + "ExternalRootfs": { + "type": "string" + }, + "CgroupParent": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/host/info-request.json b/src/json/schema/schema/host/info-request.json new file mode 100644 index 0000000..8084880 --- /dev/null +++ b/src/json/schema/schema/host/info-request.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + } +} diff --git a/src/json/schema/schema/host/info-response.json b/src/json/schema/schema/host/info-response.json new file mode 100644 index 0000000..92a498f --- /dev/null +++ b/src/json/schema/schema/host/info-response.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "containers_num":{ + "type":"integer" + }, + "cRunning":{ + "type":"integer" + }, + "cPaused":{ + "type":"integer" + }, + "cStopped":{ + "type":"integer" + }, + "images_num":{ + "type":"integer" + }, + "kversion": { + "type": "string" + }, + "os_type": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "nodename": { + "type": "string" + }, + "cpus": { + "type": "integer" + }, + "operating_system": { + "type": "string" + }, + "cgroup_driver": { + "type": "string" + }, + "logging_driver": { + "type": "string" + }, + "huge_page_size": { + "type": "string" + }, + "isulad_root_dir": { + "type": "string" + }, + "total_mem": { + "type": "uint32" + }, + "http_proxy": { + "type": "string" + }, + "https_proxy": { + "type": "string" + }, + "no_proxy": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/delete-image-request.json b/src/json/schema/schema/image/delete-image-request.json new file mode 100644 index 0000000..bcfc511 --- /dev/null +++ b/src/json/schema/schema/image/delete-image-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "image_name": { + "type": "string" + }, + "force": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/image/delete-image-response.json b/src/json/schema/schema/image/delete-image-response.json new file mode 100644 index 0000000..04130c9 --- /dev/null +++ b/src/json/schema/schema/image/delete-image-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/descriptor.json b/src/json/schema/schema/image/descriptor.json new file mode 100644 index 0000000..81fd618 --- /dev/null +++ b/src/json/schema/schema/image/descriptor.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "media_type": { + "type": "string" + }, + "digest": { + "type": "string" + }, + "size": { + "type": "int64" + } + } +} diff --git a/src/json/schema/schema/image/image.json b/src/json/schema/schema/image/image.json new file mode 100644 index 0000000..de49986 --- /dev/null +++ b/src/json/schema/schema/image/image.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "labels": { + "$ref": "../defs.json#/definitions/mapStringString" + }, + "target": { + "$ref": "descriptor.json" + }, + "created_at": { + "$ref": "../timestamp.json" + }, + "updated_at": { + "$ref": "../timestamp.json" + } + } +} diff --git a/src/json/schema/schema/image/inspect-request.json b/src/json/schema/schema/image/inspect-request.json new file mode 100644 index 0000000..6811cc5 --- /dev/null +++ b/src/json/schema/schema/image/inspect-request.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "timeout":{ + "type":"integer" + }, + "bformat": { + "type": "boolean" + } + } +} diff --git a/src/json/schema/schema/image/inspect-response.json b/src/json/schema/schema/image/inspect-response.json new file mode 100644 index 0000000..6f70039 --- /dev/null +++ b/src/json/schema/schema/image/inspect-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "image_json": { + "type": "string" + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/list-images-request.json b/src/json/schema/schema/image/list-images-request.json new file mode 100644 index 0000000..09283e3 --- /dev/null +++ b/src/json/schema/schema/image/list-images-request.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "filters": { + "$ref": "../defs.json#/definitions/ArrayOfStrings" + } + } +} diff --git a/src/json/schema/schema/image/list-images-response.json b/src/json/schema/schema/image/list-images-response.json new file mode 100644 index 0000000..af0a78f --- /dev/null +++ b/src/json/schema/schema/image/list-images-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "$ref": "image.json" + } + }, + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/load-image-request.json b/src/json/schema/schema/image/load-image-request.json new file mode 100644 index 0000000..faffb00 --- /dev/null +++ b/src/json/schema/schema/image/load-image-request.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "file": { + "type": "string" + }, + "type": { + "type": "string" + }, + "tag": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/load-image-response.json b/src/json/schema/schema/image/load-image-response.json new file mode 100644 index 0000000..8c398e8 --- /dev/null +++ b/src/json/schema/schema/image/load-image-response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/login-request.json b/src/json/schema/schema/image/login-request.json new file mode 100644 index 0000000..82c6676 --- /dev/null +++ b/src/json/schema/schema/image/login-request.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "server": { + "type": "string" + }, + "type": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/login-response.json b/src/json/schema/schema/image/login-response.json new file mode 100644 index 0000000..8c398e8 --- /dev/null +++ b/src/json/schema/schema/image/login-response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/logout-request.json b/src/json/schema/schema/image/logout-request.json new file mode 100644 index 0000000..d8cc2f6 --- /dev/null +++ b/src/json/schema/schema/image/logout-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "server": { + "type": "string" + }, + "type": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/logout-response.json b/src/json/schema/schema/image/logout-response.json new file mode 100644 index 0000000..8c398e8 --- /dev/null +++ b/src/json/schema/schema/image/logout-response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "cc": { + "type": "uint32" + }, + "errmsg": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/image/manifest-items.json b/src/json/schema/schema/image/manifest-items.json new file mode 100644 index 0000000..69fd054 --- /dev/null +++ b/src/json/schema/schema/image/manifest-items.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "manifest item set", + "type": "array", + "items": { + "title": "manifest item", + "type": "object", + "description": "manifest item for image save and load", + "properties": { + "Config": { + "description": "", + "id": "", + "type": "string" + }, + "Layers": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "RepoTags": { + "type": "array", + "minItems": 1, + "items": { + "type": "string" + } + }, + "Parent": { + "description": "", + "id": "", + "type": "string" + } + }, + "required": [ + "Config", + "Layers" + ] + } +} diff --git a/src/json/schema/schema/image/manifest-v1-compatibility.json b/src/json/schema/schema/image/manifest-v1-compatibility.json new file mode 100644 index 0000000..2c592e9 --- /dev/null +++ b/src/json/schema/schema/image/manifest-v1-compatibility.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "parent": { + "type": "string" + }, + "created": { + "type": "string" + }, + "throwaway": { + "type": "boolean" + }, + "container_config": { + "type": "object", + "properties": { + "Cmd" : { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/src/json/schema/schema/image/manifest-v1.json b/src/json/schema/schema/image/manifest-v1.json new file mode 100644 index 0000000..641ea3c --- /dev/null +++ b/src/json/schema/schema/image/manifest-v1.json @@ -0,0 +1,65 @@ +{ + "description": "Huawei docker Manifest Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "docker/schema/image/manifest", + "type": "object", + "properties": { + "schemaVersion": { + "description": "SchemaVersion is the image manifest schema that this image follows", + "id": "docker/schema/image/manifest/schemaVersion", + "type": "integer", + "minimum": 1, + "maximum": 1 + }, + "name": { + "description": "name is the name of the image's repository", + "id": "docker/schema/image/manifest/name", + "type": "string" + }, + "tag": { + "description": "", + "id": "", + "type": "string" + }, + "architecture": { + "description": "", + "id": "", + "type": "string" + }, + "fsLayers": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "blobSum": { + "type": "string" + } + } + } + }, + "history": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "properties": { + "v1Compatibility": { + "type" : "string" + } + } + } + }, + "signatures": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "../web-signature.json" + } + } + }, + "required": [ + "schemaVersion", + "fsLayers" + ] +} diff --git a/src/json/schema/schema/imagetool/auth_input.json b/src/json/schema/schema/imagetool/auth_input.json new file mode 100644 index 0000000..9347597 --- /dev/null +++ b/src/json/schema/schema/imagetool/auth_input.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "auth": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/imagetool/fs-info.json b/src/json/schema/schema/imagetool/fs-info.json new file mode 100644 index 0000000..48d604c --- /dev/null +++ b/src/json/schema/schema/imagetool/fs-info.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "image_filesystems": { + "type": "array", + "items": { + "type": "object", + "properties": { + "timestamp": { + "type": "int64" + }, + "fs_id": { + "type": "object", + "properties": { + "mountpoint": { + "type": "string" + } + } + }, + "used_bytes": { + "type": "object", + "properties": { + "value": { + "type": "uint64" + } + } + }, + "inodes_used": { + "type": "object", + "properties": { + "value": { + "type": "uint64" + } + } + } + } + } + } + } +} diff --git a/src/json/schema/schema/imagetool/image-status.json b/src/json/schema/schema/imagetool/image-status.json new file mode 100644 index 0000000..10fd8d8 --- /dev/null +++ b/src/json/schema/schema/imagetool/image-status.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "image": { + "$ref": "image.json" + }, + "info": { + "$ref": "../defs.json#/definitions/mapStringString" + } + } +} diff --git a/src/json/schema/schema/imagetool/image.json b/src/json/schema/schema/imagetool/image.json new file mode 100644 index 0000000..2832be2 --- /dev/null +++ b/src/json/schema/schema/imagetool/image.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "repo_tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "repo_digests": { + "type": "array", + "items": { + "type": "string" + } + }, + "size": { + "type": "uint64" + }, + "created": { + "type": "string" + }, + "Loaded": { + "type": "string" + }, + "uid": { + "type": "object", + "properties": { + "value": { + "type": "int64" + } + } + }, + "username": { + "type": "string" + }, + "Spec": { + "$ref": "../oci/image/spec.json" + }, + "Healthcheck": { + "$ref": "../defs.json#/definitions/HealthCheck" + } + } +} diff --git a/src/json/schema/schema/imagetool/images-list.json b/src/json/schema/schema/imagetool/images-list.json new file mode 100644 index 0000000..b5b2946 --- /dev/null +++ b/src/json/schema/schema/imagetool/images-list.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "$ref": "image.json" + } + } + } +} diff --git a/src/json/schema/schema/imagetool/prepare-response.json b/src/json/schema/schema/imagetool/prepare-response.json new file mode 100644 index 0000000..c87ef99 --- /dev/null +++ b/src/json/schema/schema/imagetool/prepare-response.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "mount_point": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/isulad-daemon-configs.json b/src/json/schema/schema/isulad-daemon-configs.json new file mode 100644 index 0000000..c67a191 --- /dev/null +++ b/src/json/schema/schema/isulad-daemon-configs.json @@ -0,0 +1,143 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "hosts": { + "type": "ArrayOfStrings" + }, + "group": { + "type": "string" + }, + "graph": { + "type": "string" + }, + "state": { + "type": "string" + }, + "log-level": { + "type": "string" + }, + "log-driver": { + "type": "string" + }, + "log-opts": { + "$ref": "defs.json#/definitions/mapStringString" + }, + "storage-driver": { + "type": "string" + }, + "storage-opts": { + "type": "ArrayOfStrings" + }, + "pidfile": { + "type": "string" + }, + "engine": { + "type": "string" + }, + "hook-spec": { + "type": "string" + }, + "start-timeout": { + "type": "string" + }, + "enable-plugins": { + "type": "string" + }, + "cpu-rt-period": { + "type": "int64" + }, + "cpu-rt-runtime": { + "type": "int64" + }, + "registry-mirrors": { + "type": "ArrayOfStrings" + }, + "insecure-registries": { + "type": "ArrayOfStrings" + }, + "native.umask": { + "type": "string" + }, + "im_opt_timeout": { + "type": "string" + }, + "pod-sandbox-image": { + "type": "string" + }, + "image_service": { + "type": "boolean" + }, + "rootfsmntdir": { + "type": "string" + }, + "network-plugin": { + "type": "string" + }, + "cni-bin-dir": { + "type": "string" + }, + "cni-conf-dir": { + "type": "string" + }, + "image-layer-check": { + "type": "boolean" + }, + "use-decrypted-key": { + "type": "booleanPointer" + }, + "insecure-skip-verify-enforce": { + "type": "boolean" + }, + "tls": { + "type": "boolean" + }, + "tls-verify": { + "type": "boolean" + }, + "tls-config": { + "type": "object", + "properties": { + "CAFile": { + "type": "string" + }, + "CertFile": { + "type": "string" + }, + "KeyFile": { + "type": "string" + } + } + }, + "authorization-plugin": { + "type": "string" + }, + "cgroup-parent": { + "type": "string" + }, + "default-ulimits": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "object", + "required": [ + "Name", + "Hard", + "Soft" + ], + "properties": { + "Name": { + "type": "string" + }, + "Hard": { + "type": "int64" + }, + "Soft": { + "type": "int64" + } + } + } + } + } + } +} diff --git a/src/json/schema/schema/logger/json-file.json b/src/json/schema/schema/logger/json-file.json new file mode 100644 index 0000000..8defb65 --- /dev/null +++ b/src/json/schema/schema/logger/json-file.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "log":{ + "type": "array", + "items": { + "type": "byte" + } + }, + "stream": { + "type": "string" + }, + "time": { + "type": "string" + }, + "attrs": { + "type": "array", + "items": { + "type": "byte" + } + } + } +} diff --git a/src/json/schema/schema/oci/image/content-descriptor.json b/src/json/schema/schema/oci/image/content-descriptor.json new file mode 100644 index 0000000..69fcea9 --- /dev/null +++ b/src/json/schema/schema/oci/image/content-descriptor.json @@ -0,0 +1,33 @@ +{ + "description": "OpenContainer Content Descriptor Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://opencontainers.org/schema/descriptor", + "type": "object", + "properties": { + "mediaType": { + "description": "the mediatype of the referenced object", + "$ref": "defs-descriptor.json#/definitions/mediaType" + }, + "size": { + "description": "the size in bytes of the referenced object", + "$ref": "defs.json#/definitions/int64" + }, + "digest": { + "description": "the cryptographic checksum digest of the object, in the pattern ':'", + "$ref": "defs-descriptor.json#/definitions/digest" + }, + "urls": { + "description": "a list of urls from which this object may be downloaded", + "$ref": "defs-descriptor.json#/definitions/urls" + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/descriptor/annotations", + "$ref": "defs-descriptor.json#/definitions/annotations" + } + }, + "required": [ + "mediaType", + "size", + "digest" + ] +} diff --git a/src/json/schema/schema/oci/image/defs-descriptor.json b/src/json/schema/schema/oci/image/defs-descriptor.json new file mode 100644 index 0000000..feaea00 --- /dev/null +++ b/src/json/schema/schema/oci/image/defs-descriptor.json @@ -0,0 +1,27 @@ +{ + "description": "Definitions particular to OpenContainer Descriptor Specification", + "definitions": { + "mediaType": { + "id": "https://opencontainers.org/schema/image/descriptor/mediaType", + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}$" + }, + "digest": { + "description": "the cryptographic checksum digest of the object, in the pattern ':'", + "type": "string", + "pattern": "^[a-z0-9]+(?:[+._-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$" + }, + "urls": { + "description": "a list of urls from which this object may be downloaded", + "type": "array", + "items": { + "type": "string", + "format": "uri" + } + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/descriptor/annotations", + "$ref": "defs.json#/definitions/mapStringString" + } + } +} diff --git a/src/json/schema/schema/oci/image/defs.json b/src/json/schema/schema/oci/image/defs.json new file mode 100644 index 0000000..72bdad7 --- /dev/null +++ b/src/json/schema/schema/oci/image/defs.json @@ -0,0 +1,308 @@ +{ + "description": "Definitions used throughout the OpenContainer Specification", + "definitions": { + "int8": { + "type": "integer", + "minimum": -128, + "maximum": 127 + }, + "int16": { + "type": "integer", + "minimum": -32768, + "maximum": 32767 + }, + "int32": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "int64": { + "type": "integer", + "minimum": -9223372036854776000, + "maximum": 9223372036854776000 + }, + "uint8": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "uint16": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "uint32": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "uint64": { + "type": "integer", + "minimum": 0, + "maximum": 18446744073709552000 + }, + "int32Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/int32" + }, + { + "type": "null" + } + ] + }, + "uint16Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/uint16" + }, + { + "type": "null" + } + ] + }, + "uint64Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/uint64" + }, + { + "type": "null" + } + ] + }, + "stringPointer": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "percent": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "UID": { + "$ref": "#/definitions/uint32" + }, + "GID": { + "$ref": "#/definitions/uint32" + }, + "ArrayOfGIDs": { + "type": "array", + "items": { + "$ref": "#/definitions/GID" + } + }, + "FilePath": { + "type": "string" + }, + "Env": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "Hook": { + "type": "object", + "properties": { + "path": { + "$ref": "#/definitions/FilePath" + }, + "args": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "env": { + "$ref": "#/definitions/Env" + }, + "timeout": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "path" + ] + }, + "ArrayOfHooks": { + "type": "array", + "items": { + "$ref": "#/definitions/Hook" + } + }, + "IDMapping": { + "type": "object", + "properties": { + "hostID": { + "$ref": "#/definitions/uint32" + }, + "containerID": { + "$ref": "#/definitions/uint32" + }, + "size": { + "$ref": "#/definitions/uint32" + } + }, + "required": [ + "hostID", + "containerID", + "size" + ] + }, + "Mount": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/FilePath" + }, + "destination": { + "$ref": "#/definitions/FilePath" + }, + "options": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "type": { + "type": "string" + } + }, + "required": [ + "destination" + ] + }, + "ArrayOfStrings": { + "type": "array", + "items": { + "type": "string" + } + }, + "mapStringString": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "string" + } + } + }, + "mapStringInt": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "integer" + } + } + }, + "mapStringBool": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "boolean" + } + } + }, + "mapIntString": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "string" + } + } + }, + "mapIntInt": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "integer" + } + } + }, + "mapIntBool": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "boolean" + } + } + }, + "mapStringObject": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "object" + } + } + }, + "ociVersion": { + "description": "The version of Open Container Runtime Specification that the document complies with", + "type": "string" + }, + "annotations": { + "$ref": "#/definitions/mapStringString" + }, + "HealthCheck": { + "type": "object", + "properties": { + "Test": { + "type": "array", + "items": { + "type": "string" + } + }, + "Interval": { + "type": "int64" + }, + "Timeout": { + "type": "int64" + }, + "StartPeriod": { + "type": "int64" + }, + "Retries": { + "type": "integer" + }, + "ExitOnUnhealthy": { + "type": "boolean" + } + } + }, + "Health": { + "type": "object", + "properties": { + "Status": { + "type": "string" + }, + "FailingStreak": { + "type": "integer" + }, + "Log": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Start": { + "type": "string" + }, + "End": { + "type": "string" + }, + "ExitCode": { + "type": "integer" + }, + "Output": { + "type": "string" + } + } + } + } + } + }, + "filters": { + "type": "object", + "patternProperties": { + ".{1,}": { + "$ref": "#/definitions/mapStringBool" + } + } + } + } +} diff --git a/src/json/schema/schema/oci/image/index.json b/src/json/schema/schema/oci/image/index.json new file mode 100644 index 0000000..8a962aa --- /dev/null +++ b/src/json/schema/schema/oci/image/index.json @@ -0,0 +1,89 @@ +{ + "description": "OpenContainer Image Index Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://opencontainers.org/schema/image/index", + "type": "object", + "properties": { + "schemaVersion": { + "description": "This field specifies the image index schema version as an integer", + "id": "https://opencontainers.org/schema/image/index/schemaVersion", + "type": "integer", + "minimum": 2, + "maximum": 2 + }, + "manifests": { + "type": "array", + "items": { + "id": "https://opencontainers.org/schema/image/manifestDescriptor", + "type": "object", + "required": [ + "mediaType", + "size", + "digest" + ], + "properties": { + "mediaType": { + "description": "the mediatype of the referenced object", + "$ref": "defs-descriptor.json#/definitions/mediaType" + }, + "size": { + "description": "the size in bytes of the referenced object", + "$ref": "defs.json#/definitions/int64" + }, + "digest": { + "description": "the cryptographic checksum digest of the object, in the pattern ':'", + "$ref": "defs-descriptor.json#/definitions/digest" + }, + "urls": { + "description": "a list of urls from which this object may be downloaded", + "$ref": "defs-descriptor.json#/definitions/urls" + }, + "platform": { + "id": "https://opencontainers.org/schema/image/platform", + "type": "object", + "required": [ + "architecture", + "os" + ], + "properties": { + "architecture": { + "id": "https://opencontainers.org/schema/image/platform/architecture", + "type": "string" + }, + "os": { + "id": "https://opencontainers.org/schema/image/platform/os", + "type": "string" + }, + "os.version": { + "id": "https://opencontainers.org/schema/image/platform/os.version", + "type": "string" + }, + "os.features": { + "id": "https://opencontainers.org/schema/image/platform/os.features", + "type": "array", + "items": { + "type": "string" + } + }, + "variant": { + "type": "string" + } + } + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/descriptor/annotations", + "$ref": "defs-descriptor.json#/definitions/annotations" + } + } + } + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/index/annotations", + "$ref": "defs-descriptor.json#/definitions/annotations" + } + }, + "required": [ + "schemaVersion", + "manifests" + ] +} diff --git a/src/json/schema/schema/oci/image/layout.json b/src/json/schema/schema/oci/image/layout.json new file mode 100644 index 0000000..874d217 --- /dev/null +++ b/src/json/schema/schema/oci/image/layout.json @@ -0,0 +1,18 @@ +{ + "description": "OpenContainer Image Layout Schema", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://opencontainers.org/schema/image/layout", + "type": "object", + "properties": { + "imageLayoutVersion": { + "description": "version of the OCI Image Layout (in the oci-layout file)", + "type": "string", + "enum": [ + "1.0.0" + ] + } + }, + "required": [ + "imageLayoutVersion" + ] +} diff --git a/src/json/schema/schema/oci/image/manifest.json b/src/json/schema/schema/oci/image/manifest.json new file mode 100644 index 0000000..ec00748 --- /dev/null +++ b/src/json/schema/schema/oci/image/manifest.json @@ -0,0 +1,34 @@ +{ + "description": "OpenContainer Image Manifest Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://opencontainers.org/schema/image/manifest", + "type": "object", + "properties": { + "schemaVersion": { + "description": "This field specifies the image manifest schema version as an integer", + "id": "https://opencontainers.org/schema/image/manifest/schemaVersion", + "type": "integer", + "minimum": 2, + "maximum": 2 + }, + "config": { + "$ref": "content-descriptor.json" + }, + "layers": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "content-descriptor.json" + } + }, + "annotations": { + "id": "https://opencontainers.org/schema/image/manifest/annotations", + "$ref": "defs-descriptor.json#/definitions/annotations" + } + }, + "required": [ + "schemaVersion", + "config", + "layers" + ] +} diff --git a/src/json/schema/schema/oci/image/spec.json b/src/json/schema/schema/oci/image/spec.json new file mode 100644 index 0000000..15bccd0 --- /dev/null +++ b/src/json/schema/schema/oci/image/spec.json @@ -0,0 +1,140 @@ +{ + "description": "OpenContainer Config Specification", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://opencontainers.org/schema/image/config", + "type": "object", + "properties": { + "created": { + "type": "string", + "format": "date-time" + }, + "author": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "os": { + "type": "string" + }, + "config": { + "type": "object", + "properties": { + "User": { + "type": "string" + }, + "ExposedPorts": { + "$ref": "defs.json#/definitions/mapStringObject" + }, + "Env": { + "type": "array", + "items": { + "type": "string" + } + }, + "Entrypoint": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, + "Cmd": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + }, + "Volumes": { + "oneOf": [ + { + "$ref": "defs.json#/definitions/mapStringObject" + }, + { + "type": "null" + } + ] + }, + "WorkingDir": { + "type": "string" + }, + "Labels": { + "oneOf": [ + { + "$ref": "defs.json#/definitions/mapStringString" + }, + { + "type": "null" + } + ] + }, + "StopSignal": { + "type": "string" + } + } + }, + "rootfs": { + "type": "object", + "properties": { + "diff_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "layers" + ] + } + }, + "required": [ + "diff_ids", + "type" + ] + }, + "history": { + "type": "array", + "items": { + "type": "object", + "properties": { + "created": { + "type": "string", + "format": "date-time" + }, + "author": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "empty_layer": { + "type": "boolean" + } + } + } + } + }, + "required": [ + "architecture", + "os", + "rootfs" + ] +} diff --git a/src/json/schema/schema/oci/runtime/config-linux.json b/src/json/schema/schema/oci/runtime/config-linux.json new file mode 100644 index 0000000..95935f5 --- /dev/null +++ b/src/json/schema/schema/oci/runtime/config-linux.json @@ -0,0 +1,271 @@ +{ + "linux": { + "description": "Linux platform-specific configurations", + "id": "https://opencontainers.org/schema/bundle/linux", + "type": "object", + "properties": { + "devices": { + "id": "https://opencontainers.org/schema/bundle/linux/devices", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/Device" + } + }, + "uidMappings": { + "id": "https://opencontainers.org/schema/bundle/linux/uidMappings", + "type": "array", + "items": { + "$ref": "defs.json#/definitions/IDMapping" + } + }, + "gidMappings": { + "id": "https://opencontainers.org/schema/bundle/linux/gidMappings", + "type": "array", + "items": { + "$ref": "defs.json#/definitions/IDMapping" + } + }, + "namespaces": { + "id": "https://opencontainers.org/schema/bundle/linux/namespaces", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "defs-linux.json#/definitions/NamespaceReference" + } + ] + } + }, + "resources": { + "id": "https://opencontainers.org/schema/bundle/linux/resources", + "type": "object", + "properties": { + "devices": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/devices", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/DeviceCgroup" + } + }, + "pids": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/pids", + "type": "object", + "properties": { + "limit": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/pids/limit", + "$ref": "defs.json#/definitions/int64" + } + }, + "required": [ + "limit" + ] + }, + "blockIO": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/blockIO", + "type": "object", + "properties": { + "weight": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/blockIO/weight", + "$ref": "defs-linux.json#/definitions/weight" + }, + "leafWeight": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/blockIO/leafWeight", + "$ref": "defs-linux.json#/definitions/weight" + }, + "throttleReadBpsDevice": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/blockIO/throttleReadBpsDevice", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/blockIODeviceThrottle" + } + }, + "throttleWriteBpsDevice": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/blockIO/throttleWriteBpsDevice", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/blockIODeviceThrottle" + } + }, + "throttleReadIOPSDevice": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/blockIO/throttleReadIOPSDevice", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/blockIODeviceThrottle" + } + }, + "throttleWriteIOPSDevice": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/blockIO/throttleWriteIOPSDevice", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/blockIODeviceThrottle" + } + }, + "weightDevice": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/blockIO/weightDevice", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/blockIODeviceWeight" + } + } + } + }, + "cpu": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/cpu", + "type": "object", + "properties": { + "cpus": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/cpu/cpus", + "type": "string" + }, + "mems": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/cpu/mems", + "type": "string" + }, + "period": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/cpu/period", + "$ref": "defs.json#/definitions/uint64" + }, + "quota": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/cpu/quota", + "$ref": "defs.json#/definitions/int64" + }, + "realtimePeriod": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/cpu/realtimePeriod", + "$ref": "defs.json#/definitions/uint64" + }, + "realtimeRuntime": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/cpu/realtimeRuntime", + "$ref": "defs.json#/definitions/int64" + }, + "shares": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/cpu/shares", + "$ref": "defs.json#/definitions/uint64" + } + } + }, + "hugepageLimits": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/hugepageLimits", + "type": "array", + "items": { + "type": "object", + "properties": { + "pageSize": { + "type": "string" + }, + "limit": { + "$ref": "defs.json#/definitions/uint64" + } + }, + "required": [ + "pageSize", + "limit" + ] + } + }, + "memory": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/memory", + "type": "object", + "properties": { + "kernel": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/memory/kernel", + "$ref": "defs.json#/definitions/int64" + }, + "kernelTCP": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/memory/kernelTCP", + "$ref": "defs.json#/definitions/int64" + }, + "limit": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/memory/limit", + "$ref": "defs.json#/definitions/int64" + }, + "reservation": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/memory/reservation", + "$ref": "defs.json#/definitions/int64" + }, + "swap": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/memory/swap", + "$ref": "defs.json#/definitions/int64" + }, + "swappiness": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/memory/swappiness", + "$ref": "defs.json#/definitions/uint64" + }, + "disableOOMKiller": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/memory/disableOOMKiller", + "type": "boolean" + } + } + }, + "network": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/network", + "type": "object", + "properties": { + "classID": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/network/classId", + "$ref": "defs.json#/definitions/uint32" + }, + "priorities": { + "id": "https://opencontainers.org/schema/bundle/linux/resources/network/priorities", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/NetworkInterfacePriority" + } + } + } + } + } + }, + "cgroupsPath": { + "id": "https://opencontainers.org/schema/bundle/linux/cgroupsPath", + "type": "string" + }, + "rootfsPropagation": { + "id": "https://opencontainers.org/schema/bundle/linux/rootfsPropagation", + "$ref": "defs-linux.json#/definitions/RootfsPropagation" + }, + "seccomp": { + "id": "https://opencontainers.org/schema/bundle/linux/seccomp", + "type": "object", + "properties": { + "defaultAction": { + "id": "https://opencontainers.org/schema/bundle/linux/seccomp/defaultAction", + "type": "string" + }, + "architectures": { + "id": "https://opencontainers.org/schema/bundle/linux/seccomp/architectures", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/SeccompArch" + } + }, + "syscalls": { + "id": "https://opencontainers.org/schema/bundle/linux/seccomp/syscalls", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/Syscall" + } + } + }, + "required": [ + "defaultAction" + ] + }, + "sysctl": { + "id": "https://opencontainers.org/schema/bundle/linux/sysctl", + "$ref": "defs.json#/definitions/mapStringString" + }, + "maskedPaths": { + "id": "https://opencontainers.org/schema/bundle/linux/maskedPaths", + "$ref": "defs.json#/definitions/ArrayOfStrings" + }, + "readonlyPaths": { + "id": "https://opencontainers.org/schema/bundle/linux/readonlyPaths", + "$ref": "defs.json#/definitions/ArrayOfStrings" + }, + "mountLabel": { + "id": "https://opencontainers.org/schema/bundle/linux/mountLabel", + "type": "string" + } + } + } +} diff --git a/src/json/schema/schema/oci/runtime/defs-linux.json b/src/json/schema/schema/oci/runtime/defs-linux.json new file mode 100644 index 0000000..4d9620a --- /dev/null +++ b/src/json/schema/schema/oci/runtime/defs-linux.json @@ -0,0 +1,270 @@ +{ + "definitions": { + "RootfsPropagation": { + "type": "string", + "enum": [ + "private", + "shared", + "slave", + "unbindable" + ] + }, + "SeccompArch": { + "type": "string", + "enum": [ + "SCMP_ARCH_X86", + "SCMP_ARCH_X86_64", + "SCMP_ARCH_X32", + "SCMP_ARCH_ARM", + "SCMP_ARCH_AARCH64", + "SCMP_ARCH_MIPS", + "SCMP_ARCH_MIPS64", + "SCMP_ARCH_MIPS64N32", + "SCMP_ARCH_MIPSEL", + "SCMP_ARCH_MIPSEL64", + "SCMP_ARCH_MIPSEL64N32", + "SCMP_ARCH_PPC", + "SCMP_ARCH_PPC64", + "SCMP_ARCH_PPC64LE", + "SCMP_ARCH_S390", + "SCMP_ARCH_S390X", + "SCMP_ARCH_PARISC", + "SCMP_ARCH_PARISC64" + ] + }, + "SeccompAction": { + "type": "string", + "enum": [ + "SCMP_ACT_KILL", + "SCMP_ACT_TRAP", + "SCMP_ACT_ERRNO", + "SCMP_ACT_TRACE", + "SCMP_ACT_ALLOW" + ] + }, + "SeccompOperators": { + "type": "string", + "enum": [ + "SCMP_CMP_NE", + "SCMP_CMP_LT", + "SCMP_CMP_LE", + "SCMP_CMP_EQ", + "SCMP_CMP_GE", + "SCMP_CMP_GT", + "SCMP_CMP_MASKED_EQ" + ] + }, + "SyscallArg": { + "type": "object", + "properties": { + "index": { + "$ref": "defs.json#/definitions/uint32" + }, + "value": { + "$ref": "defs.json#/definitions/uint64" + }, + "valueTwo": { + "$ref": "defs.json#/definitions/uint64" + }, + "op": { + "$ref": "#/definitions/SeccompOperators" + } + }, + "required": [ + "index", + "value", + "op" + ] + }, + "Syscall": { + "type": "object", + "properties": { + "names": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "action": { + "$ref": "#/definitions/SeccompAction" + }, + "args": { + "type": "array", + "items": { + "$ref": "#/definitions/SyscallArg" + } + } + }, + "required": [ + "names", + "action" + ] + }, + "Major": { + "description": "major device number", + "$ref": "defs.json#/definitions/int64" + }, + "Minor": { + "description": "minor device number", + "$ref": "defs.json#/definitions/int64" + }, + "FileMode": { + "description": "File permissions mode (typically an octal value)", + "type": "integer", + "minimum": 0, + "maximum": 512 + }, + "FileType": { + "description": "Type of a block or special character device", + "type": "string", + "pattern": "^[cbup]$" + }, + "Device": { + "type": "object", + "required": [ + "type", + "path" + ], + "properties": { + "type": { + "$ref": "#/definitions/FileType" + }, + "path": { + "$ref": "defs.json#/definitions/FilePath" + }, + "fileMode": { + "$ref": "#/definitions/FileMode" + }, + "major": { + "$ref": "#/definitions/Major" + }, + "minor": { + "$ref": "#/definitions/Minor" + }, + "uid": { + "$ref": "defs.json#/definitions/UID" + }, + "gid": { + "$ref": "defs.json#/definitions/GID" + } + } + }, + "weight": { + "type": "integer" + }, + "blockIODevice": { + "type": "object", + "properties": { + "major": { + "$ref": "#/definitions/Major" + }, + "minor": { + "$ref": "#/definitions/Minor" + } + }, + "required": [ + "major", + "minor" + ] + }, + "blockIODeviceWeight": { + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/blockIODevice" + }, + { + "type": "object", + "properties": { + "weight": { + "$ref": "#/definitions/weight" + }, + "leafWeight": { + "$ref": "#/definitions/weight" + } + } + } + ] + }, + "blockIODeviceThrottle": { + "allOf": [ + { + "$ref": "#/definitions/blockIODevice" + }, + { + "type": "object", + "properties": { + "rate": { + "$ref": "defs.json#/definitions/uint64" + } + } + } + ] + }, + "DeviceCgroup": { + "type": "object", + "properties": { + "allow": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "major": { + "$ref": "#/definitions/Major" + }, + "minor": { + "$ref": "#/definitions/Minor" + }, + "access": { + "type": "string" + } + }, + "required": [ + "allow" + ] + }, + "NetworkInterfacePriority": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "priority": { + "$ref": "defs.json#/definitions/uint32" + } + }, + "required": [ + "name", + "priority" + ] + }, + "NamespaceType": { + "type": "string", + "enum": [ + "mount", + "pid", + "network", + "uts", + "ipc", + "user", + "cgroup" + ] + }, + "NamespaceReference": { + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/NamespaceType" + }, + "path": { + "$ref": "defs.json#/definitions/FilePath" + } + }, + "required": [ + "type" + ] + } + } +} diff --git a/src/json/schema/schema/oci/runtime/defs.json b/src/json/schema/schema/oci/runtime/defs.json new file mode 100644 index 0000000..72bdad7 --- /dev/null +++ b/src/json/schema/schema/oci/runtime/defs.json @@ -0,0 +1,308 @@ +{ + "description": "Definitions used throughout the OpenContainer Specification", + "definitions": { + "int8": { + "type": "integer", + "minimum": -128, + "maximum": 127 + }, + "int16": { + "type": "integer", + "minimum": -32768, + "maximum": 32767 + }, + "int32": { + "type": "integer", + "minimum": -2147483648, + "maximum": 2147483647 + }, + "int64": { + "type": "integer", + "minimum": -9223372036854776000, + "maximum": 9223372036854776000 + }, + "uint8": { + "type": "integer", + "minimum": 0, + "maximum": 255 + }, + "uint16": { + "type": "integer", + "minimum": 0, + "maximum": 65535 + }, + "uint32": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "uint64": { + "type": "integer", + "minimum": 0, + "maximum": 18446744073709552000 + }, + "int32Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/int32" + }, + { + "type": "null" + } + ] + }, + "uint16Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/uint16" + }, + { + "type": "null" + } + ] + }, + "uint64Pointer": { + "oneOf": [ + { + "$ref": "#/definitions/uint64" + }, + { + "type": "null" + } + ] + }, + "stringPointer": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "percent": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "UID": { + "$ref": "#/definitions/uint32" + }, + "GID": { + "$ref": "#/definitions/uint32" + }, + "ArrayOfGIDs": { + "type": "array", + "items": { + "$ref": "#/definitions/GID" + } + }, + "FilePath": { + "type": "string" + }, + "Env": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "Hook": { + "type": "object", + "properties": { + "path": { + "$ref": "#/definitions/FilePath" + }, + "args": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "env": { + "$ref": "#/definitions/Env" + }, + "timeout": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "path" + ] + }, + "ArrayOfHooks": { + "type": "array", + "items": { + "$ref": "#/definitions/Hook" + } + }, + "IDMapping": { + "type": "object", + "properties": { + "hostID": { + "$ref": "#/definitions/uint32" + }, + "containerID": { + "$ref": "#/definitions/uint32" + }, + "size": { + "$ref": "#/definitions/uint32" + } + }, + "required": [ + "hostID", + "containerID", + "size" + ] + }, + "Mount": { + "type": "object", + "properties": { + "source": { + "$ref": "#/definitions/FilePath" + }, + "destination": { + "$ref": "#/definitions/FilePath" + }, + "options": { + "$ref": "#/definitions/ArrayOfStrings" + }, + "type": { + "type": "string" + } + }, + "required": [ + "destination" + ] + }, + "ArrayOfStrings": { + "type": "array", + "items": { + "type": "string" + } + }, + "mapStringString": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "string" + } + } + }, + "mapStringInt": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "integer" + } + } + }, + "mapStringBool": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "boolean" + } + } + }, + "mapIntString": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "string" + } + } + }, + "mapIntInt": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "integer" + } + } + }, + "mapIntBool": { + "type": "object", + "patternProperties": { + ".{2,}": { + "type": "boolean" + } + } + }, + "mapStringObject": { + "type": "object", + "patternProperties": { + ".{1,}": { + "type": "object" + } + } + }, + "ociVersion": { + "description": "The version of Open Container Runtime Specification that the document complies with", + "type": "string" + }, + "annotations": { + "$ref": "#/definitions/mapStringString" + }, + "HealthCheck": { + "type": "object", + "properties": { + "Test": { + "type": "array", + "items": { + "type": "string" + } + }, + "Interval": { + "type": "int64" + }, + "Timeout": { + "type": "int64" + }, + "StartPeriod": { + "type": "int64" + }, + "Retries": { + "type": "integer" + }, + "ExitOnUnhealthy": { + "type": "boolean" + } + } + }, + "Health": { + "type": "object", + "properties": { + "Status": { + "type": "string" + }, + "FailingStreak": { + "type": "integer" + }, + "Log": { + "type": "array", + "items": { + "type": "object", + "properties": { + "Start": { + "type": "string" + }, + "End": { + "type": "string" + }, + "ExitCode": { + "type": "integer" + }, + "Output": { + "type": "string" + } + } + } + } + } + }, + "filters": { + "type": "object", + "patternProperties": { + ".{1,}": { + "$ref": "#/definitions/mapStringBool" + } + } + } + } +} diff --git a/src/json/schema/schema/oci/runtime/pspec.json b/src/json/schema/schema/oci/runtime/pspec.json new file mode 100644 index 0000000..cac6fd9 --- /dev/null +++ b/src/json/schema/schema/oci/runtime/pspec.json @@ -0,0 +1,34 @@ +{ + "description": "oci info used for plugin system", + "type": "object", + "properties": { + "annotations": { + "$ref": "defs.json#/definitions/annotations" + }, + "root": { + "$ref": "spec.json#/properties/root" + }, + "mounts": { + "id": "https://opencontainers.org/schema/bundle/mounts", + "type": "array", + "items": { + "$ref": "defs.json#/definitions/Mount" + } + }, + "env": { + "id": "https://opencontainers.org/schema/bundle/process/env", + "$ref": "defs.json#/definitions/Env" + }, + "devices": { + "id": "https://opencontainers.org/schema/bundle/linux/devices", + "type": "array", + "items": { + "$ref": "defs-linux.json#/definitions/Device" + } + }, + "resources": { + "id": "https://opencontainers.org/schema/bundle/linux/resources", + "$ref": "config-linux.json#/linux/properties/resources" + } + } +} diff --git a/src/json/schema/schema/oci/runtime/spec.json b/src/json/schema/schema/oci/runtime/spec.json new file mode 100644 index 0000000..d083274 --- /dev/null +++ b/src/json/schema/schema/oci/runtime/spec.json @@ -0,0 +1,216 @@ +{ + "description": "Open Container Runtime Specification Container Configuration Schema", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://opencontainers.org/schema/bundle", + "type": "object", + "properties": { + "ociVersion": { + "id": "https://opencontainers.org/schema/bundle/ociVersion", + "$ref": "defs.json#/definitions/ociVersion" + }, + "hooks": { + "id": "https://opencontainers.org/schema/bundle/hooks", + "type": "object", + "properties": { + "prestart": { + "$ref": "defs.json#/definitions/ArrayOfHooks" + }, + "poststart": { + "$ref": "defs.json#/definitions/ArrayOfHooks" + }, + "poststop": { + "$ref": "defs.json#/definitions/ArrayOfHooks" + } + } + }, + "annotations": { + "$ref": "defs.json#/definitions/annotations" + }, + "hostname": { + "id": "https://opencontainers.org/schema/bundle/hostname", + "type": "string" + }, + "mounts": { + "id": "https://opencontainers.org/schema/bundle/mounts", + "type": "array", + "items": { + "$ref": "defs.json#/definitions/Mount" + } + }, + "root": { + "description": "Configures the container's root filesystem.", + "id": "https://opencontainers.org/schema/bundle/root", + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "id": "https://opencontainers.org/schema/bundle/root/path", + "$ref": "defs.json#/definitions/FilePath" + }, + "readonly": { + "id": "https://opencontainers.org/schema/bundle/root/readonly", + "type": "boolean" + } + } + }, + "process": { + "id": "https://opencontainers.org/schema/bundle/process", + "type": "object", + "required": [ + "cwd", + "args" + ], + "properties": { + "args": { + "id": "https://opencontainers.org/schema/bundle/process/args", + "$ref": "defs.json#/definitions/ArrayOfStrings" + }, + "consoleSize": { + "id": "https://opencontainers.org/schema/bundle/process/consoleSize", + "type": "object", + "required": [ + "height", + "width" + ], + "properties": { + "height": { + "id": "https://opencontainers.org/schema/bundle/process/consoleSize/height", + "$ref": "defs.json#/definitions/uint64" + }, + "width": { + "id": "https://opencontainers.org/schema/bundle/process/consoleSize/width", + "$ref": "defs.json#/definitions/uint64" + } + } + }, + "cwd": { + "id": "https://opencontainers.org/schema/bundle/process/cwd", + "type": "string" + }, + "env": { + "id": "https://opencontainers.org/schema/bundle/process/env", + "$ref": "defs.json#/definitions/Env" + }, + "terminal": { + "id": "https://opencontainers.org/schema/bundle/process/terminal", + "type": "boolean" + }, + "user": { + "id": "https://opencontainers.org/schema/bundle/process/user", + "type": "object", + "properties": { + "uid": { + "id": "https://opencontainers.org/schema/bundle/process/user/uid", + "$ref": "defs.json#/definitions/UID" + }, + "gid": { + "id": "https://opencontainers.org/schema/bundle/process/user/gid", + "$ref": "defs.json#/definitions/GID" + }, + "additionalGids": { + "id": "https://opencontainers.org/schema/bundle/process/user/additionalGids", + "$ref": "defs.json#/definitions/ArrayOfGIDs" + }, + "username": { + "id": "https://opencontainers.org/schema/bundle/process/user/username", + "type": "string" + } + } + }, + "capabilities": { + "id": "https://opencontainers.org/schema/bundle/process/linux/capabilities", + "type": "object", + "properties": { + "bounding": { + "id": "https://opencontainers.org/schema/bundle/process/linux/capabilities/bounding", + "type": "array", + "items": { + "type": "string" + } + }, + "permitted": { + "id": "https://opencontainers.org/schema/bundle/process/linux/capabilities/permitted", + "type": "array", + "items": { + "type": "string" + } + }, + "effective": { + "id": "https://opencontainers.org/schema/bundle/process/linux/capabilities/effective", + "type": "array", + "items": { + "type": "string" + } + }, + "inheritable": { + "id": "https://opencontainers.org/schema/bundle/process/linux/capabilities/inheritable", + "type": "array", + "items": { + "type": "string" + } + }, + "ambient": { + "id": "https://opencontainers.org/schema/bundle/process/linux/capabilities/ambient", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "apparmorProfile": { + "id": "https://opencontainers.org/schema/bundle/process/linux/apparmorProfile", + "type": "string" + }, + "oomScoreAdj": { + "id": "https://opencontainers.org/schema/bundle/process/linux/oomScoreAdj", + "type": "integer" + }, + "selinuxLabel": { + "id": "https://opencontainers.org/schema/bundle/process/linux/selinuxLabel", + "type": "string" + }, + "noNewPrivileges": { + "id": "https://opencontainers.org/schema/bundle/process/linux/noNewPrivileges", + "type": "boolean" + }, + "rlimits": { + "id": "https://opencontainers.org/schema/bundle/linux/rlimits", + "type": "array", + "items": { + "id": "https://opencontainers.org/schema/bundle/linux/rlimits/0", + "type": "object", + "required": [ + "type", + "soft", + "hard" + ], + "properties": { + "hard": { + "id": "https://opencontainers.org/schema/bundle/linux/rlimits/0/hard", + "$ref": "defs.json#/definitions/uint64" + }, + "soft": { + "id": "https://opencontainers.org/schema/bundle/linux/rlimits/0/soft", + "$ref": "defs.json#/definitions/uint64" + }, + "type": { + "id": "https://opencontainers.org/schema/bundle/linux/rlimits/0/type", + "type": "string", + "pattern": "^RLIMIT_[A-Z]+$" + } + } + } + } + } + }, + "linux": { + "$ref": "config-linux.json#/linux" + } + }, + "required": [ + "ociVersion" + ] +} diff --git a/src/json/schema/schema/oci/runtime/state.json b/src/json/schema/schema/oci/runtime/state.json new file mode 100644 index 0000000..563a619 --- /dev/null +++ b/src/json/schema/schema/oci/runtime/state.json @@ -0,0 +1,45 @@ +{ + "description": "Open Container Runtime State Schema", + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "https://opencontainers.org/schema/state", + "type": "object", + "properties": { + "ociVersion": { + "id": "https://opencontainers.org/schema/runtime/state/ociVersion", + "$ref": "defs.json#/definitions/ociVersion" + }, + "id": { + "id": "https://opencontainers.org/schema/runtime/state/id", + "description": "the container's ID", + "type": "string" + }, + "status": { + "id": "https://opencontainers.org/schema/runtime/state/status", + "type": "string", + "enum": [ + "creating", + "created", + "running", + "stopped" + ] + }, + "pid": { + "id": "https://opencontainers.org/schema/runtime/state/pid", + "type": "integer", + "minimum": 0 + }, + "bundle": { + "id": "https://opencontainers.org/schema/runtime/state/bundle", + "type": "string" + }, + "annotations": { + "$ref": "defs.json#/definitions/annotations" + } + }, + "required": [ + "ociVersion", + "id", + "status", + "bundle" + ] +} diff --git a/src/json/schema/schema/plugin/activate-plugin-request.json b/src/json/schema/schema/plugin/activate-plugin-request.json new file mode 100644 index 0000000..60e11ee --- /dev/null +++ b/src/json/schema/schema/plugin/activate-plugin-request.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + } +} diff --git a/src/json/schema/schema/plugin/activate-plugin-response.json b/src/json/schema/schema/plugin/activate-plugin-response.json new file mode 100644 index 0000000..91d3278 --- /dev/null +++ b/src/json/schema/schema/plugin/activate-plugin-response.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "properties": { + "InitType": { + "type": "uint64" + }, + "WatchEvent": { + "type": "uint64" + }, + "ErrCode": { + "type": "integer" + }, + "ErrMessage": { + "Type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/event-post-remove-request.json b/src/json/schema/schema/plugin/event-post-remove-request.json new file mode 100644 index 0000000..79c5f0d --- /dev/null +++ b/src/json/schema/schema/plugin/event-post-remove-request.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ID": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/event-post-remove-response.json b/src/json/schema/schema/plugin/event-post-remove-response.json new file mode 100644 index 0000000..ea1e08b --- /dev/null +++ b/src/json/schema/schema/plugin/event-post-remove-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ErrCode": { + "type": "integer" + }, + "ErrMessage": { + "type": "string" + }, + "ID": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/event-post-stop-request.json b/src/json/schema/schema/plugin/event-post-stop-request.json new file mode 100644 index 0000000..79c5f0d --- /dev/null +++ b/src/json/schema/schema/plugin/event-post-stop-request.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ID": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/event-post-stop-response.json b/src/json/schema/schema/plugin/event-post-stop-response.json new file mode 100644 index 0000000..ea1e08b --- /dev/null +++ b/src/json/schema/schema/plugin/event-post-stop-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ErrCode": { + "type": "integer" + }, + "ErrMessage": { + "type": "string" + }, + "ID": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/event-pre-create-request.json b/src/json/schema/schema/plugin/event-pre-create-request.json new file mode 100644 index 0000000..2c5a14d --- /dev/null +++ b/src/json/schema/schema/plugin/event-pre-create-request.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "Pspec": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/event-pre-create-response.json b/src/json/schema/schema/plugin/event-pre-create-response.json new file mode 100644 index 0000000..c37eecf --- /dev/null +++ b/src/json/schema/schema/plugin/event-pre-create-response.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ErrCode": { + "type": "integer" + }, + "ErrMessage": { + "type": "string" + }, + "ID": { + "type": "string" + }, + "Pspec": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/event-pre-start-request.json b/src/json/schema/schema/plugin/event-pre-start-request.json new file mode 100644 index 0000000..79c5f0d --- /dev/null +++ b/src/json/schema/schema/plugin/event-pre-start-request.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ID": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/event-pre-start-response.json b/src/json/schema/schema/plugin/event-pre-start-response.json new file mode 100644 index 0000000..ea1e08b --- /dev/null +++ b/src/json/schema/schema/plugin/event-pre-start-response.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ErrCode": { + "type": "integer" + }, + "ErrMessage": { + "type": "string" + }, + "ID": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/plugin/init-plugin-request.json b/src/json/schema/schema/plugin/init-plugin-request.json new file mode 100644 index 0000000..016a77c --- /dev/null +++ b/src/json/schema/schema/plugin/init-plugin-request.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "Containers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "Status": { + "type": "integer" + }, + "Pspec": { + "type": "string" + } + } + } + } + } +} diff --git a/src/json/schema/schema/plugin/init-plugin-response.json b/src/json/schema/schema/plugin/init-plugin-response.json new file mode 100644 index 0000000..75ee810 --- /dev/null +++ b/src/json/schema/schema/plugin/init-plugin-response.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ErrCode": { + "type": "integer" + }, + "ErrMessage": { + "type": "string" + } + } +} diff --git a/src/json/schema/schema/timestamp.json b/src/json/schema/schema/timestamp.json new file mode 100644 index 0000000..1dbdb2a --- /dev/null +++ b/src/json/schema/schema/timestamp.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "seconds": { + "type": "int64" + }, + "nanos": { + "type": "int32" + } + } +} diff --git a/src/json/schema/schema/web-signature.json b/src/json/schema/schema/web-signature.json new file mode 100644 index 0000000..0b12293 --- /dev/null +++ b/src/json/schema/schema/web-signature.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "signature": { + "type": "string" + }, + "protected": { + "type": "string" + }, + "header": { + "type": "object", + "minItems": 1, + "properties": { + "alg": { + "type": "string" + }, + "jwk": { + "type": "object", + "properties": { + "crv": { + "type": "string" + }, + "kid": { + "type": "string" + }, + "kty": { + "type": "string" + }, + "x": { + "type": "string" + }, + "y": { + "type": "string" + }, + "d": { + "type": "string" + } + } + } + } + } + } +} diff --git a/src/json/schema/src/CMakeLists.txt b/src/json/schema/src/CMakeLists.txt new file mode 100644 index 0000000..8db178e --- /dev/null +++ b/src/json/schema/src/CMakeLists.txt @@ -0,0 +1,3 @@ +# get current directory sources files + +message("-- do nothing ") diff --git a/src/json/schema/src/common_c.py b/src/json/schema/src/common_c.py new file mode 100644 index 0000000..0279293 --- /dev/null +++ b/src/json/schema/src/common_c.py @@ -0,0 +1,1223 @@ +# -*- coding: utf-8 -*- +''' +Description: commom source file +Interface: None +History: 2019-06-17 +''' +# - Copyright (C) Huawei Technologies., Ltd. 2018-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +# - Description: generate json +# - Author: tanyifeng +# - Create: 2018-04-25 +#!/usr/bin/python -Es + +CODE = '''// Auto generated file. Do not edit! +# define _GNU_SOURCE +# include +# include +# include +# include "json_common.h" + +# define MAX_NUM_STR_LEN 21 + + + +yajl_gen_status map_uint(void *ctx, long long unsigned int num) { + char numstr[MAX_NUM_STR_LEN]; + int ret; + + ret = sprintf_s(numstr, sizeof(numstr), "%llu", num); + if (ret < 0) { + return yajl_gen_in_error_state; + } + return yajl_gen_number((yajl_gen)ctx, (const char *)numstr, strlen(numstr)); +} + +yajl_gen_status map_int(void *ctx, long long int num) { + char numstr[MAX_NUM_STR_LEN]; + int ret; + + ret = sprintf_s(numstr, sizeof(numstr), "%lld", num); + if (ret < 0) { + return yajl_gen_in_error_state; + } + return yajl_gen_number((yajl_gen)ctx, (const char *)numstr, strlen(numstr)); +} + + +bool json_gen_init(yajl_gen *g, const struct parser_context *ctx) { + *g = yajl_gen_alloc(NULL); + if (NULL == *g) { + return false; + + } + yajl_gen_config(*g, yajl_gen_beautify, (int)(!(ctx->options & OPT_GEN_SIMPLIFY))); + yajl_gen_config(*g, yajl_gen_validate_utf8, (int)(!(ctx->options & OPT_GEN_NO_VALIDATE_UTF8))); + return true; +} + +yajl_val get_val(yajl_val tree, const char *name, yajl_type type) { + const char *path[] = { name, NULL }; + return yajl_tree_get(tree, path, type); +} + +void *safe_malloc(size_t size) { + void *ret = NULL; + if (size == 0) { + abort(); + } + ret = calloc(1, size); + if (ret == NULL) { + abort(); + } + return ret; +} + +int common_safe_double(const char *numstr, double *converted) { + char *err_str = NULL; + double d; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + d = strtod(numstr, &err_str); + if (errno > 0) { + return -errno; + } + + if (err_str == NULL || err_str == numstr || *err_str != '\\0') { + return -EINVAL; + } + + *converted = d; + return 0; +} + +int common_safe_uint8(const char *numstr, uint8_t *converted) { + char *err = NULL; + unsigned long int uli; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + uli = strtoul(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + if (uli > UINT8_MAX) { + return -ERANGE; + } + + *converted = (uint8_t)uli; + return 0; +} + +int common_safe_uint16(const char *numstr, uint16_t *converted) { + char *err = NULL; + unsigned long int uli; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + uli = strtoul(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + if (uli > UINT16_MAX) { + return -ERANGE; + } + + *converted = (uint16_t)uli; + return 0; +} + +int common_safe_uint32(const char *numstr, uint32_t *converted) { + char *err = NULL; + unsigned long long int ull; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + ull = strtoull(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + if (ull > UINT32_MAX) { + return -ERANGE; + } + + *converted = (uint32_t)ull; + return 0; +} + +int common_safe_uint64(const char *numstr, uint64_t *converted) { + char *err = NULL; + unsigned long long int ull; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + ull = strtoull(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + *converted = (uint64_t)ull; + return 0; +} + +int common_safe_uint(const char *numstr, unsigned int *converted) { + char *err = NULL; + unsigned long long int ull; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + ull = strtoull(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + if (ull > UINT_MAX) { + return -ERANGE; + } + + *converted = (unsigned int)ull; + return 0; +} + +int common_safe_int8(const char *numstr, int8_t *converted) { + char *err = NULL; + long int li; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + li = strtol(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + if (li > INT8_MAX || li < INT8_MIN) { + return -ERANGE; + } + + *converted = (int8_t)li; + return 0; +} + +int common_safe_int16(const char *numstr, int16_t *converted) { + char *err = NULL; + long int li; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + li = strtol(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + if (li > INT16_MAX || li < INT16_MIN) { + return -ERANGE; + } + + *converted = (int16_t)li; + return 0; +} + +int common_safe_int32(const char *numstr, int32_t *converted) { + char *err = NULL; + long long int lli; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + lli = strtol(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + if (lli > INT32_MAX || lli < INT32_MIN) { + return -ERANGE; + } + + *converted = (int32_t)lli; + return 0; +} + +int common_safe_int64(const char *numstr, int64_t *converted) { + char *err = NULL; + long long int lli; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + lli = strtoll(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + *converted = (int64_t)lli; + return 0; +} + +int common_safe_int(const char *numstr, int *converted) { + char *err = NULL; + long long int lli; + + if (numstr == NULL) { + return -EINVAL; + } + + errno = 0; + lli = strtol(numstr, &err, 0); + if (errno > 0) { + return -errno; + } + + if (err == NULL || err == numstr || *err != '\\0') { + return -EINVAL; + } + + if (lli > INT_MAX || lli < INT_MIN) { + return -ERANGE; + } + + *converted = (int)lli; + return 0; +} + +char *safe_strdup(const char *src) +{ + char *dst = NULL; + + if (src == NULL) { + return NULL; + } + + dst = strdup(src); + if (dst == NULL) { + abort(); + } + + return dst; +} + + +yajl_gen_status gen_json_map_int_int(void *ctx, const json_map_int_int *map, const struct parser_context *ptx, parser_error *err) { + yajl_gen_status stat = yajl_gen_status_ok; + yajl_gen g = (yajl_gen) ctx; + size_t len = 0, i = 0; + if (map != NULL) { + len = map->len; + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 0); + } + stat = yajl_gen_map_open((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + + } + for (i = 0; i < len; i++) { + char numstr[MAX_NUM_STR_LEN]; + int nret; + nret = sprintf_s(numstr, sizeof(numstr), "%lld", (long long int)map->keys[i]); + if (nret < 0) { + if (!*err && asprintf(err, "Error to print string") < 0) { + *(err) = safe_strdup("error allocating memory"); + } + return yajl_gen_in_error_state; + } + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)numstr, strlen(numstr)); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + stat = map_int(g, map->values[i]); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + } + + stat = yajl_gen_map_close((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 1); + } + return yajl_gen_status_ok; +} + +void free_json_map_int_int(json_map_int_int *map) { + if (map != NULL) { + free(map->keys); + map->keys = NULL; + free(map->values); + map->values = NULL; + free(map); + } +} +json_map_int_int *make_json_map_int_int(yajl_val src, const struct parser_context *ctx, parser_error *err) { + json_map_int_int *ret = NULL; + if (src != NULL && YAJL_GET_OBJECT(src) != NULL) { + size_t i; + size_t len = YAJL_GET_OBJECT(src)->len; + ret = safe_malloc(sizeof(*ret)); + ret->len = len; + ret->keys = safe_malloc((len + 1) * sizeof(int)); + ret->values = safe_malloc((len + 1) * sizeof(int)); + for (i = 0; i < len; i++) { + const char *srckey = YAJL_GET_OBJECT(src)->keys[i]; + yajl_val srcval = YAJL_GET_OBJECT(src)->values[i]; + + if (srckey != NULL) { + int invalid; + invalid = common_safe_int(srckey, &(ret->keys[i])); + if (invalid) { + if (*err == NULL && asprintf(err, "Invalid key '%s' with type 'int': %s", srckey, strerror(-invalid)) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_int_int(ret); + return NULL; + } + } + + if (srcval != NULL) { + int invalid; + if (!YAJL_IS_NUMBER(srcval)) { + if (*err == NULL && asprintf(err, "Invalid value with type 'int' for key '%s'", srckey) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_int_int(ret); + return NULL; + } + invalid = common_safe_int(YAJL_GET_NUMBER(srcval), &(ret->values[i])); + if (invalid) { + if (*err == NULL && asprintf(err, "Invalid value with type 'int' for key '%s': %s", srckey, strerror(-invalid)) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_int_int(ret); + return NULL; + } + } + } + } + return ret; +} +int append_json_map_int_int(json_map_int_int *map, int key, int val) { + size_t len; + int *keys = NULL; + int *vals = NULL; + + if (map == NULL) { + return -1; + } + + if ((SIZE_MAX / sizeof(int) - 1) < map->len) { + return -1; + } + + len = map->len + 1; + keys = safe_malloc(len * sizeof(int)); + vals = safe_malloc(len * sizeof(int)); + + if (map->len) { + if (memcpy_s(keys, len * sizeof(int), map->keys, map->len * sizeof(int)) != EOK) { + free(keys); + free(vals); + return -1; + } + if (memcpy_s(vals, len * sizeof(int), map->values, map->len * sizeof(int)) != EOK) { + free(keys); + free(vals); + return -1; + } + } + free(map->keys); + map->keys = keys; + free(map->values); + map->values = vals; + map->keys[map->len] = key; + map->values[map->len] = val; + + map->len++; + return 0; +} + +yajl_gen_status gen_json_map_int_bool(void *ctx, const json_map_int_bool *map, const struct parser_context *ptx, parser_error *err) { + yajl_gen_status stat = yajl_gen_status_ok; + yajl_gen g = (yajl_gen) ctx; + size_t len = 0, i = 0; + if (map != NULL) { + len = map->len; + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 0); + } + stat = yajl_gen_map_open((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + + } + for (i = 0; i < len; i++) { + char numstr[MAX_NUM_STR_LEN]; + int nret; + nret = sprintf_s(numstr, sizeof(numstr), "%lld", (long long int)map->keys[i]); + if (nret < 0) { + if (!*err && asprintf(err, "Error to print string") < 0) { + *(err) = safe_strdup("error allocating memory"); + } + return yajl_gen_in_error_state; + } + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)numstr, strlen(numstr)); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + stat = yajl_gen_bool((yajl_gen)g, (int)(map->values[i])); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + } + + stat = yajl_gen_map_close((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 1); + } + return yajl_gen_status_ok; +} + +void free_json_map_int_bool(json_map_int_bool *map) { + if (map != NULL) { + size_t i; + for (i = 0; i < map->len; i++) { + // No need to free key for type int + // No need to free value for type bool + } + free(map->keys); + map->keys = NULL; + free(map->values); + map->values = NULL; + free(map); + } +} +json_map_int_bool *make_json_map_int_bool(yajl_val src, const struct parser_context *ctx, parser_error *err) { + json_map_int_bool *ret = NULL; + if (src != NULL && YAJL_GET_OBJECT(src) != NULL) { + size_t i; + size_t len = YAJL_GET_OBJECT(src)->len; + ret = safe_malloc(sizeof(*ret)); + ret->len = len; + ret->keys = safe_malloc((len + 1) * sizeof(int)); + ret->values = safe_malloc((len + 1) * sizeof(bool)); + for (i = 0; i < len; i++) { + const char *srckey = YAJL_GET_OBJECT(src)->keys[i]; + yajl_val srcval = YAJL_GET_OBJECT(src)->values[i]; + + if (srckey != NULL) { + int invalid; + invalid = common_safe_int(srckey, &(ret->keys[i])); + if (invalid) { + if (*err == NULL && asprintf(err, "Invalid key '%s' with type 'int': %s", srckey, strerror(-invalid)) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_int_bool(ret); + return NULL; + } + } + + if (srcval != NULL) { + if (YAJL_IS_TRUE(srcval)) { + ret->values[i] = true; + } else if (YAJL_IS_FALSE(srcval)) { + ret->values[i] = false; + } else { + if (*err == NULL && asprintf(err, "Invalid value with type 'bool' for key '%s'", srckey) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_int_bool(ret); + return NULL; + } + } + } + } + return ret; +} +int append_json_map_int_bool(json_map_int_bool *map, int key, bool val) { + size_t len; + int *keys = NULL; + bool *vals = NULL; + + if (map == NULL) { + return -1; + } + + if ((SIZE_MAX / sizeof(int) - 1) < map->len || (SIZE_MAX / sizeof(bool) - 1) < map->len) { + return -1; + } + + len = map->len + 1; + keys = safe_malloc(len * sizeof(int)); + vals = safe_malloc(len * sizeof(bool)); + + if (map->len) { + if (memcpy_s(keys, len * sizeof(int), map->keys, map->len * sizeof(int)) != EOK) { + free(keys); + free(vals); + return -1; + } + if (memcpy_s(vals, len * sizeof(bool), map->values, map->len * sizeof(bool)) != EOK) { + free(keys); + free(vals); + return -1; + } + } + free(map->keys); + map->keys = keys; + free(map->values); + map->values = vals; + map->keys[map->len] = key; + map->values[map->len] = val; + + map->len++; + return 0; +} + +yajl_gen_status gen_json_map_int_string(void *ctx, const json_map_int_string *map, const struct parser_context *ptx, parser_error *err) { + yajl_gen_status stat = yajl_gen_status_ok; + yajl_gen g = (yajl_gen) ctx; + size_t len = 0, i = 0; + if (map != NULL) { + len = map->len; + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 0); + } + stat = yajl_gen_map_open((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + + } + for (i = 0; i < len; i++) { + char numstr[MAX_NUM_STR_LEN]; + int nret; + nret = sprintf_s(numstr, sizeof(numstr), "%lld", (long long int)map->keys[i]); + if (nret < 0) { + if (!*err && asprintf(err, "Error to print string") < 0) { + *(err) = safe_strdup("error allocating memory"); + } + return yajl_gen_in_error_state; + } + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)numstr, strlen(numstr)); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)(map->values[i]), strlen(map->values[i])); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + } + + stat = yajl_gen_map_close((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 1); + } + return yajl_gen_status_ok; +} + +void free_json_map_int_string(json_map_int_string *map) { + if (map != NULL) { + size_t i; + for (i = 0; i < map->len; i++) { + // No need to free key for type int + free(map->values[i]); + map->values[i] = NULL; + } + free(map->keys); + map->keys = NULL; + free(map->values); + map->values = NULL; + free(map); + } +} +json_map_int_string *make_json_map_int_string(yajl_val src, const struct parser_context *ctx, parser_error *err) { + json_map_int_string *ret = NULL; + if (src != NULL && YAJL_GET_OBJECT(src) != NULL) { + size_t i; + size_t len = YAJL_GET_OBJECT(src)->len; + ret = safe_malloc(sizeof(*ret)); + ret->len = len; + ret->keys = safe_malloc((len + 1) * sizeof(int)); + ret->values = safe_malloc((len + 1) * sizeof(char *)); + for (i = 0; i < len; i++) { + const char *srckey = YAJL_GET_OBJECT(src)->keys[i]; + yajl_val srcval = YAJL_GET_OBJECT(src)->values[i]; + + if (srckey != NULL) { + int invalid; + invalid = common_safe_int(srckey, &(ret->keys[i])); + if (invalid) { + if (*err == NULL && asprintf(err, "Invalid key '%s' with type 'int': %s", srckey, strerror(-invalid)) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_int_string(ret); + return NULL; + } + } + + if (srcval != NULL) { + if (!YAJL_IS_STRING(srcval)) { + if (*err == NULL && asprintf(err, "Invalid value with type 'string' for key '%s'", srckey) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_int_string(ret); + return NULL; + } + char *str = YAJL_GET_STRING(srcval); + ret->values[i] = safe_strdup(str ? str : ""); + } + } + } + return ret; +} +int append_json_map_int_string(json_map_int_string *map, int key, const char *val) { + size_t len; + int *keys = NULL; + char **vals = NULL; + + if (map == NULL) { + return -1; + } + + if ((SIZE_MAX / sizeof(int) - 1) < map->len || (SIZE_MAX / sizeof(char *) - 1) < map->len) { + return -1; + } + + len = map->len + 1; + keys = safe_malloc(len * sizeof(int)); + vals = safe_malloc(len * sizeof(char *)); + + if (map->len) { + if (memcpy_s(keys, len * sizeof(int), map->keys, map->len * sizeof(int)) != EOK) { + free(keys); + free(vals); + return -1; + } + if (memcpy_s(vals, len * sizeof(char *), map->values, map->len * sizeof(char *)) != EOK) { + free(keys); + free(vals); + return -1; + } + } + free(map->keys); + map->keys = keys; + free(map->values); + map->values = vals; + map->keys[map->len] = key; + map->values[map->len] = safe_strdup(val ? val : ""); + + map->len++; + return 0; +} + +yajl_gen_status gen_json_map_string_int(void *ctx, const json_map_string_int *map, const struct parser_context *ptx, parser_error *err) { + yajl_gen_status stat = yajl_gen_status_ok; + yajl_gen g = (yajl_gen) ctx; + size_t len = 0, i = 0; + if (map != NULL) { + len = map->len; + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 0); + } + stat = yajl_gen_map_open((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + + } + for (i = 0; i < len; i++) { + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)(map->keys[i]), strlen(map->keys[i])); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + stat = map_int(g, map->values[i]); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + } + + stat = yajl_gen_map_close((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 1); + } + return yajl_gen_status_ok; +} + +void free_json_map_string_int(json_map_string_int *map) { + if (map != NULL) { + size_t i; + for (i = 0; i < map->len; i++) { + free(map->keys[i]); + map->keys[i] = NULL; + } + free(map->keys); + map->keys = NULL; + free(map->values); + map->values = NULL; + free(map); + } +} +json_map_string_int *make_json_map_string_int(yajl_val src, const struct parser_context *ctx, parser_error *err) { + json_map_string_int *ret = NULL; + if (src != NULL && YAJL_GET_OBJECT(src) != NULL) { + size_t i; + size_t len = YAJL_GET_OBJECT(src)->len; + ret = safe_malloc(sizeof(*ret)); + ret->len = len; + ret->keys = safe_malloc((len + 1) * sizeof(char *)); + ret->values = safe_malloc((len + 1) * sizeof(int)); + for (i = 0; i < len; i++) { + const char *srckey = YAJL_GET_OBJECT(src)->keys[i]; + yajl_val srcval = YAJL_GET_OBJECT(src)->values[i]; + ret->keys[i] = safe_strdup(srckey ? srckey : ""); + + if (srcval != NULL) { + int invalid; + if (!YAJL_IS_NUMBER(srcval)) { + if (*err == NULL && asprintf(err, "Invalid value with type 'int' for key '%s'", srckey) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_string_int(ret); + return NULL; + } + invalid = common_safe_int(YAJL_GET_NUMBER(srcval), &(ret->values[i])); + if (invalid) { + if (*err == NULL && asprintf(err, "Invalid value with type 'int' for key '%s': %s", srckey, strerror(-invalid)) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_string_int(ret); + return NULL; + } + } + } + } + return ret; +} +int append_json_map_string_int(json_map_string_int *map, const char *key, int val) { + size_t len; + char **keys = NULL; + int *vals = NULL; + + if (map == NULL) { + return -1; + } + + if ((SIZE_MAX / sizeof(char *) - 1) < map->len || (SIZE_MAX / sizeof(int) - 1) < map->len) { + return -1; + } + + len = map->len + 1; + keys = safe_malloc(len * sizeof(char *)); + vals = safe_malloc(len * sizeof(int)); + + if (map->len) { + if (memcpy_s(keys, len * sizeof(char *), map->keys, map->len * sizeof(char *)) != EOK) { + free(keys); + free(vals); + return -1; + } + if (memcpy_s(vals, len * sizeof(int), map->values, map->len * sizeof(int)) != EOK) { + free(keys); + free(vals); + return -1; + } + } + free(map->keys); + map->keys = keys; + free(map->values); + map->values = vals; + map->keys[map->len] = safe_strdup(key ? key : ""); + map->values[map->len] = val; + + map->len++; + return 0; +} + +yajl_gen_status gen_json_map_string_bool(void *ctx, const json_map_string_bool *map, const struct parser_context *ptx, parser_error *err) { + yajl_gen_status stat = yajl_gen_status_ok; + yajl_gen g = (yajl_gen) ctx; + size_t len = 0, i = 0; + if (map != NULL) { + len = map->len; + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 0); + } + stat = yajl_gen_map_open((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + + } + for (i = 0; i < len; i++) { + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)(map->keys[i]), strlen(map->keys[i])); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + stat = yajl_gen_bool((yajl_gen)g, (int)(map->values[i])); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + } + + stat = yajl_gen_map_close((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 1); + } + return yajl_gen_status_ok; +} + +void free_json_map_string_bool(json_map_string_bool *map) { + if (map != NULL) { + size_t i; + for (i = 0; i < map->len; i++) { + free(map->keys[i]); + map->keys[i] = NULL; + // No need to free value for type bool + } + free(map->keys); + map->keys = NULL; + free(map->values); + map->values = NULL; + free(map); + } +} +json_map_string_bool *make_json_map_string_bool(yajl_val src, const struct parser_context *ctx, parser_error *err) { + json_map_string_bool *ret = NULL; + if (src != NULL && YAJL_GET_OBJECT(src) != NULL) { + size_t i; + size_t len = YAJL_GET_OBJECT(src)->len; + ret = safe_malloc(sizeof(*ret)); + ret->len = len; + ret->keys = safe_malloc((len + 1) * sizeof(char *)); + ret->values = safe_malloc((len + 1) * sizeof(bool)); + for (i = 0; i < len; i++) { + const char *srckey = YAJL_GET_OBJECT(src)->keys[i]; + yajl_val srcval = YAJL_GET_OBJECT(src)->values[i]; + ret->keys[i] = safe_strdup(srckey ? srckey : ""); + + if (srcval != NULL) { + if (YAJL_IS_TRUE(srcval)) { + ret->values[i] = true; + } else if (YAJL_IS_FALSE(srcval)) { + ret->values[i] = false; + } else { + if (*err == NULL && asprintf(err, "Invalid value with type 'bool' for key '%s'", srckey) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_string_bool(ret); + return NULL; + } + } + } + } + return ret; +} +int append_json_map_string_bool(json_map_string_bool *map, const char *key, bool val) { + size_t len; + char **keys = NULL; + bool *vals = NULL; + + if (map == NULL) { + return -1; + } + + if ((SIZE_MAX / sizeof(char *) - 1) < map->len || (SIZE_MAX / sizeof(bool) - 1) < map->len) { + return -1; + } + + len = map->len + 1; + keys = safe_malloc(len * sizeof(char *)); + vals = safe_malloc(len * sizeof(bool)); + + if (map->len) { + if (memcpy_s(keys, len * sizeof(char *), map->keys, map->len * sizeof(char *)) != EOK) { + free(keys); + free(vals); + return -1; + } + if (memcpy_s(vals, len * sizeof(bool), map->values, map->len * sizeof(bool)) != EOK) { + free(keys); + free(vals); + return -1; + } + } + free(map->keys); + map->keys = keys; + free(map->values); + map->values = vals; + map->keys[map->len] = safe_strdup(key ? key : ""); + map->values[map->len] = val; + + map->len++; + return 0; +} + +yajl_gen_status gen_json_map_string_string(void *ctx, const json_map_string_string *map, const struct parser_context *ptx, parser_error *err) { + yajl_gen_status stat = yajl_gen_status_ok; + yajl_gen g = (yajl_gen) ctx; + size_t len = 0, i = 0; + if (map != NULL) { + len = map->len; + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 0); + } + stat = yajl_gen_map_open((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + + } + for (i = 0; i < len; i++) { + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)(map->keys[i]), strlen(map->keys[i])); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)(map->values[i]), strlen(map->values[i])); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + } + + stat = yajl_gen_map_close((yajl_gen)g); + if (yajl_gen_status_ok != stat) { + GEN_SET_ERROR_AND_RETURN(stat, err); + } + if (!len && !(ptx->options & OPT_GEN_SIMPLIFY)) { + yajl_gen_config(g, yajl_gen_beautify, 1); + } + return yajl_gen_status_ok; +} + +void free_json_map_string_string(json_map_string_string *map) { + if (map != NULL) { + size_t i; + for (i = 0; i < map->len; i++) { + free(map->keys[i]); + map->keys[i] = NULL; + free(map->values[i]); + map->values[i] = NULL; + } + free(map->keys); + map->keys = NULL; + free(map->values); + map->values = NULL; + free(map); + } +} +json_map_string_string *make_json_map_string_string(yajl_val src, const struct parser_context *ctx, parser_error *err) { + json_map_string_string *ret = NULL; + if (src != NULL && YAJL_GET_OBJECT(src) != NULL) { + size_t i; + size_t len = YAJL_GET_OBJECT(src)->len; + ret = safe_malloc(sizeof(*ret)); + ret->len = len; + ret->keys = safe_malloc((len + 1) * sizeof(char *)); + ret->values = safe_malloc((len + 1) * sizeof(char *)); + for (i = 0; i < len; i++) { + const char *srckey = YAJL_GET_OBJECT(src)->keys[i]; + yajl_val srcval = YAJL_GET_OBJECT(src)->values[i]; + ret->keys[i] = safe_strdup(srckey ? srckey : ""); + + if (srcval != NULL) { + if (!YAJL_IS_STRING(srcval)) { + if (*err == NULL && asprintf(err, "Invalid value with type 'string' for key '%s'", srckey) < 0) { + *(err) = safe_strdup("error allocating memory"); + } + free_json_map_string_string(ret); + return NULL; + } + char *str = YAJL_GET_STRING(srcval); + ret->values[i] = safe_strdup(str ? str : ""); + } + } + } + return ret; +} +int append_json_map_string_string(json_map_string_string *map, const char *key, const char *val) { + size_t len, i; + char **keys = NULL; + char **vals = NULL; + + if (map == NULL) { + return -1; + } + + for (i = 0; i < map->len; i++) { + if (strcmp(map->keys[i], key) == 0) { + free(map->values[i]); + map->values[i] = safe_strdup(val ? val : ""); + return 0; + } + } + + if ((SIZE_MAX / sizeof(char *) - 1) < map->len) { + return -1; + } + + len = map->len + 1; + keys = safe_malloc(len * sizeof(char *)); + vals = safe_malloc(len * sizeof(char *)); + + if (map->len) { + if (memcpy_s(keys, len * sizeof(char *), map->keys, map->len * sizeof(char *)) != EOK) { + free(keys); + free(vals); + return -1; + } + if (memcpy_s(vals, len * sizeof(char *), map->values, map->len * sizeof(char *)) != EOK) { + free(keys); + free(vals); + return -1; + } + } + free(map->keys); + map->keys = keys; + free(map->values); + map->values = vals; + map->keys[map->len] = safe_strdup(key ? key : ""); + map->values[map->len] = safe_strdup(val ? val : ""); + + map->len++; + return 0; +} + +char *json_marshal_string(const char *str, size_t strlen, const struct parser_context *ctx, parser_error *err) +{ + yajl_gen g = NULL; + struct parser_context tmp_ctx = { 0 }; + const unsigned char *gen_buf = NULL; + char *json_buf = NULL; + size_t gen_len = 0; + yajl_gen_status stat; + + if (str == NULL || err == NULL) + return NULL; + + *err = NULL; + if (ctx == NULL) { + ctx = (const struct parser_context *)(&tmp_ctx); + } + + if (!json_gen_init(&g, ctx)) { + *err = safe_strdup("Json_gen init failed"); + goto out; + } + stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)str, strlen); + if (yajl_gen_status_ok != stat) { + if (asprintf(err, "error generating json, errcode: %d", (int)stat) < 0) { + *err = safe_strdup("error allocating memory"); + } + goto free_out; + } + yajl_gen_get_buf(g, &gen_buf, &gen_len); + if (gen_buf == NULL) { + *err = safe_strdup("Error to get generated json"); + goto free_out; + } + + json_buf = safe_malloc(gen_len + 1); + if (memcpy_s(json_buf, gen_len + 1, gen_buf, gen_len) != EOK) { + *err = safe_strdup("Error to memcpy json"); + free(json_buf); + json_buf = NULL; + goto free_out; + } + json_buf[gen_len] = '\\0'; + +free_out: + yajl_gen_clear(g); + yajl_gen_free(g); +out: + return json_buf; +} + +''' diff --git a/src/json/schema/src/common_h.py b/src/json/schema/src/common_h.py new file mode 100644 index 0000000..5c45daf --- /dev/null +++ b/src/json/schema/src/common_h.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- +''' +Description: commom header file +Interface: None +History: 2019-06-17 +''' +# - Copyright (C) Huawei Technologies., Ltd. 2018-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +# - Description: generate json +# - Author: tanyifeng +# - Create: 2018-04-25 +#!/usr/bin/python -Es + +""" +Description: json common c code +Interface: None +History: 2019-06-18 +Purpose: defined the common tool function for parse json +Defined the CODE global variable to hold the c code +""" +# - Defined the CODE global variable to hold the c code +CODE = '''// Auto generated file. Do not edit! +# ifndef _JSON_COMMON_H +# define _JSON_COMMON_H + +# include +# include +# include +# include +# include +# include +# include +# include "securec.h" + +# ifdef __cplusplus +extern "C" { +# endif + +# undef linux + +//options to report error if there is unknown key found in json +# define OPT_PARSE_STRICT 0x01 +//options to generate all key and value +# define OPT_GEN_KAY_VALUE 0x02 +//options to generate simplify(no indent) json string +# define OPT_GEN_SIMPLIFY 0x04 +//options not to validate utf8 data +# define OPT_GEN_NO_VALIDATE_UTF8 0x08 + +# define GEN_SET_ERROR_AND_RETURN(stat, err) { \\ + if (*(err) == NULL) {\\ + if (asprintf(err, "%s: %s: %d: error generating json, errcode: %u", __FILE__, __func__, __LINE__, stat) < 0) { \\ + *(err) = safe_strdup("error allocating memory"); \\ + } \\ + }\\ + return stat; \\ +} + +typedef char *parser_error; + +struct parser_context { + unsigned int options; + FILE *stderr; +}; + +yajl_gen_status map_uint(void *ctx, long long unsigned int num); + +yajl_gen_status map_int(void *ctx, long long int num); + +bool json_gen_init(yajl_gen *g, const struct parser_context *ctx); + +yajl_val get_val(yajl_val tree, const char *name, yajl_type type); + +void *safe_malloc(size_t size); + +int common_safe_double(const char *numstr, double *converted); + +int common_safe_uint8(const char *numstr, uint8_t *converted); + +int common_safe_uint16(const char *numstr, uint16_t *converted); + +int common_safe_uint32(const char *numstr, uint32_t *converted); + +int common_safe_uint64(const char *numstr, uint64_t *converted); + +int common_safe_uint(const char *numstr, unsigned int *converted); + +int common_safe_int8(const char *numstr, int8_t *converted); + +int common_safe_int16(const char *numstr, int16_t *converted); + +int common_safe_int32(const char *numstr, int32_t *converted); + +int common_safe_int64(const char *numstr, int64_t *converted); + +int common_safe_int(const char *numstr, int *converted); + +char *safe_strdup(const char *src); + +typedef struct { + int *keys; + int *values; + size_t len; +} json_map_int_int; + +void free_json_map_int_int(json_map_int_int *map); + +json_map_int_int *make_json_map_int_int(yajl_val src, const struct parser_context *ctx, parser_error *err); + +yajl_gen_status gen_json_map_int_int(void *ctx, const json_map_int_int *map, const struct parser_context *ptx, parser_error *err); + +int append_json_map_int_int(json_map_int_int *map, int key, int val); + +typedef struct { + int *keys; + bool *values; + size_t len; +} json_map_int_bool; + +void free_json_map_int_bool(json_map_int_bool *map); + +json_map_int_bool *make_json_map_int_bool(yajl_val src, const struct parser_context *ctx, parser_error *err); + +yajl_gen_status gen_json_map_int_bool(void *ctx, const json_map_int_bool *map, const struct parser_context *ptx, parser_error *err); + +int append_json_map_int_bool(json_map_int_bool *map, int key, bool val); + +typedef struct { + int *keys; + char **values; + size_t len; +} json_map_int_string; + +void free_json_map_int_string(json_map_int_string *map); + +json_map_int_string *make_json_map_int_string(yajl_val src, const struct parser_context *ctx, parser_error *err); + +yajl_gen_status gen_json_map_int_string(void *ctx, const json_map_int_string *map, const struct parser_context *ptx, parser_error *err); + +int append_json_map_int_string(json_map_int_string *map, int key, const char *val); + +typedef struct { + char **keys; + int *values; + size_t len; +} json_map_string_int; + +void free_json_map_string_int(json_map_string_int *map); + +json_map_string_int *make_json_map_string_int(yajl_val src, const struct parser_context *ctx, parser_error *err); + +yajl_gen_status gen_json_map_string_int(void *ctx, const json_map_string_int *map, const struct parser_context *ptx, parser_error *err); + +int append_json_map_string_int(json_map_string_int *map, const char *key, int val); + +typedef struct { + char **keys; + bool *values; + size_t len; +} json_map_string_bool; + +void free_json_map_string_bool(json_map_string_bool *map); + +json_map_string_bool *make_json_map_string_bool(yajl_val src, const struct parser_context *ctx, parser_error *err); + +yajl_gen_status gen_json_map_string_bool(void *ctx, const json_map_string_bool *map, const struct parser_context *ptx, parser_error *err); + +int append_json_map_string_bool(json_map_string_bool *map, const char *key, bool val); + +typedef struct { + char **keys; + char **values; + size_t len; +} json_map_string_string; + +void free_json_map_string_string(json_map_string_string *map); + +json_map_string_string *make_json_map_string_string(yajl_val src, const struct parser_context *ctx, parser_error *err); + +yajl_gen_status gen_json_map_string_string(void *ctx, const json_map_string_string *map, const struct parser_context *ptx, parser_error *err); + +int append_json_map_string_string(json_map_string_string *map, const char *key, const char *val); + +char *json_marshal_string(const char *str, size_t strlen, const struct parser_context *ctx, parser_error *err); + +# ifdef __cplusplus +} +# endif + +# endif +''' diff --git a/src/json/schema/src/generate.py b/src/json/schema/src/generate.py new file mode 100644 index 0000000..72e6d62 --- /dev/null +++ b/src/json/schema/src/generate.py @@ -0,0 +1,802 @@ +# -*- coding: utf-8 -*- +''' +Description: header class and functions +Interface: None +History: 2019-06-17 +''' +# - Copyright (C) Huawei Technologies., Ltd. 2018-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +# - Description: generate json +# - Author: tanyifeng +# - Create: 2018-04-25 +#!/usr/bin/python -Es + +import traceback +import os +import sys +import json +import fcntl +import argparse + +from collections import OrderedDict +import helpers +import headers +import sources +import common_h +import common_c + +# - json suffix +JSON_SUFFIX = ".json" + +''' +Description: ref suffix +Interface: ref_suffix +History: 2019-06-17 +''' +# - Description: ref suffix +REF_SUFFIX = "_json" + +''' +Description: root paths +Interface: rootpaths +History: 2019-06-17 +''' +class MyRoot(object): + ''' + Description: Store schema information + Interface: None + History: 2019-06-17 + ''' + def __init__(self, root_path): + self.root_path = root_path + + def get_repr(self): + ''' + Description: Store schema information + Interface: None + History: 2019-06-17 + ''' + return "{root_path:(%s)}" % (self.root_path) + + def get_path(self): + ''' + Description: Store schema information + Interface: None + History: 2019-06-17 + ''' + return self.root_path + + +def trimJsonSuffix(name): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + if name.endswith(JSON_SUFFIX) or name.endswith(REF_SUFFIX): + name = name[:-len(JSON_SUFFIX)] + return helpers.convertToCStyle(name.replace('.', '_').replace('-', '_')) + + +def getPrefixPackage(filepath, rootpath): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + realpath = os.path.realpath(filepath) + + if realpath.startswith(rootpath) and len(realpath) > len(rootpath): + return helpers.convertToCStyle(os.path.dirname(realpath)[(len(rootpath) + 1):]) + else: + raise RuntimeError('schema path \"%s\" is not in scope of root path \"%s\"' \ + % (realpath, rootpath)) + + +def getPrefixFromFile(filepath): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + prefix_file = trimJsonSuffix(os.path.basename(filepath)) + root_path = MyRoot.root_path + prefix_package = getPrefixPackage(filepath, root_path) + prefix = prefix_file if prefix_package == "" else prefix_package + "_" + prefix_file + return prefix + +def schemaFromFile(filepath, srcpath): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + schemapath = helpers.FilePath(filepath) + prefix = getPrefixFromFile(schemapath.name) + header = helpers.FilePath(os.path.join(srcpath, prefix + ".h")) + source = helpers.FilePath(os.path.join(srcpath, prefix + ".c")) + schema_info = helpers.SchemaInfo(schemapath, header, source, prefix, srcpath) + return schema_info + +def makeRefName(refname, reffile): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + prefix = getPrefixFromFile(reffile) + if refname == "" or prefix.endswith(refname): + return prefix + return prefix + "_" + helpers.convertToCStyle(refname) + +def splitRefName(ref): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + tmp_f, tmp_r = ref.split("#/") if '#/' in ref else (ref, "") + return tmp_f, tmp_r + +def merge(children): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + subchildren = [] + for i in children: + for j in i.children: + subchildren.append(j) + + return subchildren + +# BASIC_TYPES include all basic types +BASIC_TYPES = ( + "byte", "int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "UID", "GID", + "bytePointer", "doublePointer", "int8Pointer", "int16Pointer", "int32Pointer", "int64Pointer", + "uint8Pointer", "uint16Pointer", "uint32Pointer", "uint64Pointer", "ArrayOfStrings", + "booleanPointer" +) + +def judgeSupportedType(typ): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + return typ in ("integer", "boolean", "string", "double") or typ in BASIC_TYPES + +def getRefSubref(src, subref): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + cur = src + subrefname = "" + for j in subref.split('/'): + subrefname = j + if j in BASIC_TYPES: + return src, {"type": j}, subrefname + cur = cur[j] + + return src, cur, subrefname + +def getRefRoot(schema_info, src, ref, curfile): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + refname = "" + tmp_f, tmp_r = splitRefName(ref) + + if tmp_f == "": + cur = src + else: + realpath = os.path.realpath(os.path.join(os.path.dirname(curfile), tmp_f)) + curfile = realpath + + subschema = schemaFromFile(realpath, schema_info.filesdir) + if schema_info.refs is None: + schema_info.refs = {} + schema_info.refs[subschema.header.basename] = subschema + with open(realpath) as i: + cur = src = json.loads(i.read()) + subcur = cur + if tmp_r != "": + src, subcur, refname = getRefSubref(src, tmp_r) + + if 'type' not in subcur and '$ref' in subcur: + subf, subr = splitRefName(subcur['$ref']) + if subf == "": + src, subcur, refname = getRefSubref(src, subr) + if 'type' not in subcur: + raise RuntimeError("Not support reference of nesting more than 2 level: ", ref) + else: + return getRefRoot(schema_info, src, subcur['$ref'], curfile) + return src, subcur, curfile, makeRefName(refname, curfile) + +def getTypePatternInCur(cur, schema_info, src, curfile): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + # pattern of key: + # '.{1,}' represents type 'string', + # '.{2,}' represents type 'integer' + if '.{2,}' in cur['patternProperties']: + map_key = 'Int' + else: + map_key = 'String' + for i, value in enumerate(cur['patternProperties'].values()): + # only use the first value + if i == 0: + if 'type' in value: + val = value["type"] + else: + dummy_subsrc, subcur, dummy_subcurfile, dummy_subrefname = getRefRoot( + schema_info, src, value['$ref'], curfile) + val = subcur['type'] + break + + mapKey = { + 'object': 'Object', + 'string': 'String', + 'integer': 'Int', + 'boolean': 'Bool' + }[val] + map_val = mapKey + + typ = 'map' + map_key + map_val + return typ + +class GenerateNodeInfo(object): + ''' + Description: Store schema information + Interface: None + History: 2019-06-17 + ''' + def __init__(self, schema_info, name, cur, curfile): + self.schema_info = schema_info + self.name = name + self.cur = cur + self.curfile = curfile + + def get_repr(self): + ''' + Description: Store schema information + Interface: None + History: 2019-06-17 + ''' + return "{schema_info:(%s) name:(%s) cur:(%s) curfile:(%s)}" \ + % (self.schema_info, self.name, self.cur, self.curfile) + + def get_name(self): + ''' + Description: Store schema information + Interface: None + History: 2019-06-17 + ''' + return self.name + +def generateAllofArrayTypNode(node_info, src, typ, refname): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + schema_info = node_info.schema_info + name = node_info.name + cur = node_info.cur + curfile = node_info.curfile + subtyp = None + subtypobj = None + required = None + children = merge(parseList(schema_info, name, src, cur["items"]['allOf'], curfile)) + subtyp = children[0].typ + subtypobj = children + return helpers.Unite(name, + typ, + children, + subtyp=subtyp, + subtypobj=subtypobj, + subtypname=refname, + required=required), src + +def generateAnyofArrayTypNode(node_info, src, typ, refname): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + schema_info = node_info.schema_info + name = node_info.name + cur = node_info.cur + curfile = node_info.curfile + subtyp = None + subtypobj = None + required = None + anychildren = parseList(schema_info, name, src, cur["items"]['anyOf'], curfile) + subtyp = anychildren[0].typ + children = anychildren[0].children + subtypobj = children + refname = anychildren[0].subtypname + return helpers.Unite(name, + typ, + children, + subtyp=subtyp, + subtypobj=subtypobj, + subtypname=refname, + required=required), src + +def generateRefArrayTypNode(node_info, src, typ, refname): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + schema_info = node_info.schema_info + name = node_info.name + cur = node_info.cur + curfile = node_info.curfile + + item_type, src = resolveType(schema_info, name, src, cur["items"], curfile) + ref_file, subref = splitRefName(cur['items']['$ref']) + if ref_file == "": + src, dummy_subcur, subrefname = getRefSubref(src, subref) + refname = makeRefName(subrefname, curfile) + else: + refname = item_type.subtypname + return helpers.Unite(name, + typ, + None, + subtyp=item_type.typ, + subtypobj=item_type.children, + subtypname=refname, + required=item_type.required), src + +def generateTypeArrayTypNode(node_info, src, typ, refname): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + schema_info = node_info.schema_info + name = node_info.name + cur = node_info.cur + curfile = node_info.curfile + + item_type, src = resolveType(schema_info, name, src, cur["items"], curfile) + return helpers.Unite(name, + typ, + None, + subtyp=item_type.typ, + subtypobj=item_type.children, + subtypname=refname, + required=item_type.required), src + + +def generateArrayTypNode(node_info, src, typ, refname): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + cur = node_info.cur + + if 'allOf' in cur["items"]: + return generateAllofArrayTypNode(node_info, src, typ, refname) + elif 'anyOf' in cur["items"]: + return generateAnyofArrayTypNode(node_info, src, typ, refname) + elif '$ref' in cur["items"]: + return generateRefArrayTypNode(node_info, src, typ, refname) + elif 'type' in cur["items"]: + return generateTypeArrayTypNode(node_info, src, typ, refname) + return None + +def generateObjTypNode(node_info, src, typ, refname): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + schema_info = node_info.schema_info + name = node_info.name + cur = node_info.cur + curfile = node_info.curfile + children = None + subtyp = None + subtypobj = None + required = None + + if 'allOf' in cur: + children = merge(parseList(schema_info, name, src, cur['allOf'], curfile)) + elif 'anyOf' in cur: + children = parseList(schema_info, name, src, cur['anyOf'], curfile) + elif 'patternProperties' in cur: + children = parseProperties(schema_info, name, src, cur, curfile) + children[0].name = children[0].name.replace('_{1,}', 'element').replace('_{2,}', \ + 'element') + children[0].fixname = "values" + if helpers.validBasicMapName(children[0].typ): + children[0].name = helpers.makeBasicMapName(children[0].typ) + else: + children = parseProperties(schema_info, name, src, cur, curfile) \ + if 'properties' in cur else None + if 'required' in cur: + required = cur['required'] + return helpers.Unite(name,\ + typ,\ + children,\ + subtyp=subtyp,\ + subtypobj=subtypobj,\ + subtypname=refname,\ + required=required), src + +def getTypNotOneof(schema_info, src, cur, curfile): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + if 'patternProperties' in cur: + typ = getTypePatternInCur(cur, schema_info, src, curfile) + elif "type" in cur: + typ = cur["type"] + else: + typ = "object" + + return typ + + +def resolveType(schema_info, name, src, cur, curfile): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + children = None + subtyp = None + subtypobj = None + required = None + refname = None + + if '$ref' in cur: + src, cur, curfile, refname = getRefRoot(schema_info, src, cur['$ref'], curfile) + + if "oneOf" in cur: + cur = cur['oneOf'][0] + if '$ref' in cur: + return resolveType(schema_info, name, src, cur, curfile) + else: + typ = cur['type'] + else: + typ = getTypNotOneof(schema_info, src, cur, curfile) + + node_info = GenerateNodeInfo(schema_info, name, cur, curfile) + + if helpers.validBasicMapName(typ): + pass + elif typ == 'array': + return generateArrayTypNode(node_info, src, typ, refname) + elif typ == 'object' or typ == 'mapStringObject': + return generateObjTypNode(node_info, src, typ, refname) + elif typ == 'ArrayOfStrings': + typ = 'array' + subtyp = 'string' + children = subtypobj = None + else: + if not judgeSupportedType(typ): + raise RuntimeError("Invalid schema type: %s" % typ) + children = None + + return helpers.Unite(name, + typ, + children, + subtyp=subtyp, + subtypobj=subtypobj, + subtypname=refname, + required=required), src + + +def parseList(schema_info, name, schema, objs, curfile): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + obj = [] + index = 0 + for i in objs: + generated_name = helpers.CombinationName( \ + i['$ref'].split("/")[-1]) if '$ref' in i else helpers.CombinationName(name.name + str(index)) + node, _ = resolveType(schema_info, generated_name, schema, i, curfile) + if node: + obj.append(node) + index += 1 + if not obj: + obj = None + return obj + + +def parseDictionary(schema_info, name, schema, objs, curfile): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + obj = [] + for i in objs: + node, _ = resolveType(schema_info, name.append(i), schema, objs[i], curfile) + if node: + obj.append(node) + if not obj: + obj = None + return obj + + +def parseProperties(schema_info, name, schema, props, curfile): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + if 'definitions' in props: + return parseDictionary(schema_info, name, schema, props['definitions'], curfile) + if 'patternProperties' in props: + return parseDictionary(schema_info, name, schema, props['patternProperties'], curfile) + return parseDictionary(schema_info, name, schema, props['properties'], curfile) + +def handleTypeNotInSchema(schema_info, schema, prefix): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + required = None + if 'definitions' in schema: + return helpers.Unite( \ + helpers.CombinationName("definitions"), 'definitions', \ + parseProperties(schema_info, helpers.CombinationName(""), schema, schema, \ + schema_info.name.name), None, None, None, None) + else: + if len(schema) > 1: + print('More than one element found in schema') + return None + value_nodes = [] + for value in schema: + if 'required' in schema[value]: + required = schema[value]['required'] + childrens = parseProperties(schema_info, helpers.CombinationName(""), schema[value], \ + schema[value], schema_info.name.name) + value_node = helpers.Unite(helpers.CombinationName(prefix), 'object', childrens, None, None, \ + None, required) + value_nodes.append(value_node) + return helpers.Unite(helpers.CombinationName("definitions"), 'definitions', value_nodes, None, None, \ + None, None) + +def parseSchema(schema_info, schema, prefix): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + required = None + if 'type' not in schema: + return handleTypeNotInSchema(schema_info, schema, prefix) + + if 'type' not in schema: + print("No 'type' defined in schema") + return prefix, None + + if 'object' in schema['type']: + if 'required' in schema: + required = schema['required'] + return helpers.Unite( + helpers.CombinationName(prefix), 'object', + parseProperties(schema_info, helpers.CombinationName(""), schema, schema, schema_info.name.name), \ + None, None, None, required) + elif 'array' in schema['type']: + item_type, _ = resolveType(schema_info, helpers.CombinationName(""), schema['items'], \ + schema['items'], schema_info.name.name) + return helpers.Unite(helpers.CombinationName(prefix), 'array', None, item_type.typ, \ + item_type.children, None, item_type.required) + else: + print("Not supported type '%s'") % schema['type'] + return prefix, None + + +def expand(tree, structs, visited): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + if tree.children is not None: + for i in tree.children: + if tree.subtypname: + i.subtypname = "from_ref" + expand(i, structs, visited=visited) + if tree.subtypobj is not None: + for i in tree.subtypobj: + expand(i, structs, visited=visited) + + if tree.typ == 'array' and helpers.validBasicMapName(tree.subtyp): + name = helpers.CombinationName(tree.name + "_element") + node = helpers.Unite(name, tree.subtyp, None) + expand(node, structs, visited) + + id_ = "%s:%s" % (tree.name, tree.typ) + if id_ not in visited.keys(): + structs.append(tree) + visited[id_] = tree + + return structs + + +def reflection(schema_info, gen_ref): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + with open(schema_info.header.name, "w") as \ + header_file, open(schema_info.source.name, "w") as source_file: + fcntl.flock(header_file, fcntl.LOCK_EX) + fcntl.flock(source_file, fcntl.LOCK_EX) + + with open(schema_info.name.name) as schema_file: + schema_json = json.loads(schema_file.read(), object_pairs_hook=OrderedDict) + try: + tree = parseSchema(schema_info, schema_json, schema_info.prefix) + if tree is None: + print("Failed parse schema") + sys.exit(1) + structs = expand(tree, [], {}) + headers.headerReflection(structs, schema_info, header_file) + sources.sourceReflection(structs, schema_info, source_file, tree.typ) + except RuntimeError: + traceback.print_exc() + print("Failed to parse schema file: %s") % schema_info.name.name + sys.exit(1) + finally: + pass + + fcntl.flock(source_file, fcntl.LOCK_UN) + fcntl.flock(header_file, fcntl.LOCK_UN) + + if gen_ref is True: + if schema_info.refs: + for reffile in schema_info.refs.values(): + reflection(reffile, True) + + +def generateCommonFiles(out): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + print(out, " gao\n") + with open(os.path.join(out, 'json_common.h'), "w") as \ + header_file, open(os.path.join(out, 'json_common.c'), "w") as source_file: + fcntl.flock(header_file, fcntl.LOCK_EX) + fcntl.flock(source_file, fcntl.LOCK_EX) + + header_file.write(common_h.CODE) + source_file.write(common_c.CODE) + + fcntl.flock(source_file, fcntl.LOCK_UN) + fcntl.flock(header_file, fcntl.LOCK_UN) + +def handlerSingleFile(args, srcpath, gen_ref, schemapath): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + if not os.path.exists(schemapath.name) or not os.path.exists(srcpath.name): + print('Path %s is not exist') % schemapath.name + sys.exit(1) + + if os.path.isdir(schemapath.name): + if args.recursive is True: + # recursively parse schema + for dirpath, dummy_dirnames, files in os.walk(schemapath.name): + for target_file in files: + if target_file.endswith(JSON_SUFFIX): + schema_info = schemaFromFile(os.path.join(dirpath, target_file), \ + srcpath.name) + reflection(schema_info, gen_ref) + else: + # only parse files in current direcotory + for target_file in os.listdir(schemapath.name): + fullpath = os.path.join(schemapath.name, target_file) + if fullpath.endswith(JSON_SUFFIX) and os.path.isfile(fullpath): + schema_info = schemaFromFile(fullpath, srcpath.name) + reflection(schema_info, gen_ref) + else: + if schemapath.name.endswith(JSON_SUFFIX): + schema_info = schemaFromFile(schemapath.name, srcpath.name) + reflection(schema_info, gen_ref) + else: + print('File %s is not ends with .json') % schemapath.name + + +def handlerFiles(args, srcpath): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + for path in args.path: + gen_ref = args.gen_ref + schemapath = helpers.FilePath(path) + handlerSingleFile(args, srcpath, gen_ref, schemapath) + +def main(): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + parser = argparse.ArgumentParser(prog='generate.py', + usage='%(prog)s [options] path [path ...]', + description='Generate C header and source from json-schema') + parser.add_argument('path', nargs='+', help='File or directory to parse') + parser.add_argument( + '--root', + required=True, + help= + 'All schema files must be placed in root directory or sub-directory of root," \ + " and naming of C variables is started from this path' + ) + parser.add_argument('--gen-common', + action='store_true', + help='Generate json_common.c and json_common.h') + parser.add_argument('--gen-ref', + action='store_true', + help='Generate reference file defined in schema with key \"$ref\"') + parser.add_argument('-r', + '--recursive', + action='store_true', + help='Recursively generate all schema files in directory') + parser.add_argument( + '--out', + help='Specify a directory to save C header and source(default is current directory)') + args = parser.parse_args() + + if not args.root: + print('Missing root path, see help') + sys.exit(1) + + root_path = os.path.realpath(args.root) + if not os.path.exists(root_path): + print('Root %s is not exist') % args.root + sys.exit(1) + + MyRoot.root_path = root_path + + if args.out: + srcpath = helpers.FilePath(args.out) + else: + srcpath = helpers.FilePath(os.getcwd()) + if not os.path.exists(srcpath.name): + os.makedirs(srcpath.name) + + if args.gen_common: + generateCommonFiles(srcpath.name) + handlerFiles(args, srcpath) + +if __name__ == "__main__": + main() diff --git a/src/json/schema/src/headers.py b/src/json/schema/src/headers.py new file mode 100644 index 0000000..972e36c --- /dev/null +++ b/src/json/schema/src/headers.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +''' +Description: header class and functions +Interface: None +History: 2019-06-17 +''' +# - Copyright (C) Huawei Technologies., Ltd. 2018-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +# - Description: generate json +# - Author: tanyifeng +# - Create: 2018-04-25 +#!/usr/bin/python -Es +import helpers + +def appendHeaderArray(obj, header, prefix): + ''' + Description: Write c header file of array + Interface: None + History: 2019-06-17 + ''' + if not obj.subtypobj or obj.subtypname: + return + header.write("typedef struct {\n") + for i in obj.subtypobj: + if i.typ == 'array': + c_typ = helpers.getPrefixPointer(i.name, i.subtyp, prefix) or \ + helpers.getMapCTypes(i.subtyp) + if i.subtypobj is not None: + c_typ = helpers.getNameSubstr(i.name, prefix) + + if not helpers.judgeComplex(i.subtyp): + header.write(" %s%s*%s;\n" % (c_typ, " " if '*' not in c_typ else "", \ + i.fixname)) + else: + header.write(" %s **%s;\n" % (c_typ, i.fixname)) + header.write(" size_t %s;\n\n" % (i.fixname + "_len")) + else: + c_typ = helpers.getPrefixPointer(i.name, i.typ, prefix) or \ + helpers.getMapCTypes(i.typ) + header.write(" %s%s%s;\n" % (c_typ, " " if '*' not in c_typ else "", i.fixname)) + typename = helpers.getNameSubstr(obj.name, prefix) + header.write("}\n%s;\n\n" % typename) + header.write("void free_%s(%s *ptr);\n\n" % (typename, typename)) + header.write("%s *make_%s(yajl_val tree, const struct parser_context *ctx, parser_error *err);"\ + "\n\n" % (typename, typename)) + +def appendHeaderMapStrObj(obj, header, prefix): + ''' + Description: Write c header file of mapStringObject + Interface: None + History: 2019-06-17 + ''' + child = obj.children[0] + header.write("typedef struct {\n") + header.write(" char **keys;\n") + if helpers.validBasicMapName(child.typ): + c_typ = helpers.getPrefixPointer("", child.typ, "") + elif child.subtypname: + c_typ = child.subtypname + else: + c_typ = helpers.getPrefixPointer(child.name, child.typ, prefix) + header.write(" %s%s*%s;\n" % (c_typ, " " if '*' not in c_typ else "", child.fixname)) + header.write(" size_t len;\n") + +def appendHeaderChildArray(child, header, prefix): + ''' + Description: Write c header file of array of child + Interface: None + History: 2019-06-17 + ''' + if helpers.getMapCTypes(child.subtyp) != "": + c_typ = helpers.getMapCTypes(child.subtyp) + elif helpers.validBasicMapName(child.subtyp): + c_typ = '%s *' % helpers.makeBasicMapName(child.subtyp) + elif child.subtypname is not None: + c_typ = child.subtypname + elif child.subtypobj is not None: + c_typ = helpers.getNameSubstr(child.name, prefix) + else: + c_typ = helpers.getPrefixPointer(child.name, child.subtyp, prefix) + + if helpers.validBasicMapName(child.subtyp): + header.write(" %s **%s;\n" % (helpers.makeBasicMapName(child.subtyp), child.fixname)) + elif not helpers.judgeComplex(child.subtyp): + header.write(" %s%s*%s;\n" % (c_typ, " " if '*' not in c_typ else "", child.fixname)) + else: + header.write(" %s%s**%s;\n" % (c_typ, " " if '*' not in c_typ else "", child.fixname)) + header.write(" size_t %s;\n\n" % (child.fixname + "_len")) + +def appendHeaderChildOthers(child, header, prefix): + ''' + Description: Write c header file of others of child + Interface: None + History: 2019-06-17 + ''' + if helpers.getMapCTypes(child.typ) != "": + c_typ = helpers.getMapCTypes(child.typ) + elif helpers.validBasicMapName(child.typ): + c_typ = '%s *' % helpers.makeBasicMapName(child.typ) + elif child.subtypname: + c_typ = helpers.getPrefixPointer(child.subtypname, child.typ, "") + else: + c_typ = helpers.getPrefixPointer(child.name, child.typ, prefix) + header.write(" %s%s%s;\n\n" % (c_typ, " " if '*' not in c_typ else "", child.fixname)) + +def appendTypeCHeader(obj, header, prefix): + ''' + Description: Write c header file + Interface: None + History: 2019-06-17 + ''' + if not helpers.judgeComplex(obj.typ): + return + + if obj.typ == 'array': + appendHeaderArray(obj, header, prefix) + return + + if obj.typ == 'mapStringObject': + if obj.subtypname is not None: + return + appendHeaderMapStrObj(obj, header, prefix) + elif obj.typ == 'object': + if obj.subtypname is not None: + return + header.write("typedef struct {\n") + if obj.children is None: + header.write(" char unuseful;//unuseful definition to avoid empty struct\n") + for i in obj.children or [ ]: + if i.typ == 'array': + appendHeaderChildArray(i, header, prefix) + else: + appendHeaderChildOthers(i, header, prefix) + + typename = helpers.getPrefixName(obj.name, prefix) + header.write("}\n%s;\n\n" % typename) + header.write("void free_%s(%s *ptr);\n\n" % (typename, typename)) + header.write("%s *make_%s(yajl_val tree, const struct parser_context *ctx, parser_error *err)"\ + ";\n\n" % (typename, typename)) + header.write("yajl_gen_status gen_%s(yajl_gen g, const %s *ptr, const struct parser_context "\ + "*ctx, parser_error *err);\n\n" % (typename, typename)) + +def headerReflection(structs, schema_info, header): + ''' + Description: Reflection header files + Interface: None + History: 2019-06-17 + ''' + prefix = schema_info.prefix + header.write("// Generated from %s. Do not edit!\n" % (schema_info.name.basename)) + header.write("#ifndef %s_SCHEMA_H\n" % prefix.upper()) + header.write("#define %s_SCHEMA_H\n\n" % prefix.upper()) + header.write("#include \n") + header.write("#include \n") + header.write("#include \"json_common.h\"\n") + if schema_info.refs: + for ref in schema_info.refs.keys(): + header.write("#include \"%s\"\n" % (ref)) + header.write("\n#ifdef __cplusplus\n") + header.write("extern \"C\" {\n") + header.write("#endif\n\n") + + for i in structs: + appendTypeCHeader(i, header, prefix) + length = len(structs) + toptype = structs[length - 1].typ if length != 0 else "" + if toptype == 'object': + header.write("%s *%s_parse_file(const char *filename, const struct parser_context *ctx, "\ + "parser_error *err);\n\n" % (prefix, prefix)) + header.write("%s *%s_parse_file_stream(FILE *stream, const struct parser_context *ctx, "\ + "parser_error *err);\n\n" % (prefix, prefix)) + header.write("%s *%s_parse_data(const char *jsondata, const struct parser_context *ctx, "\ + "parser_error *err);\n\n" % (prefix, prefix)) + header.write("char *%s_generate_json(const %s *ptr, const struct parser_context *ctx, "\ + "parser_error *err);\n\n" % (prefix, prefix)) + elif toptype == 'array': + header.write("void free_%s(%s_element **ptr, size_t len);\n\n" % (prefix, prefix)) + header.write("%s_element **%s_parse_file(const char *filename, const struct "\ + "parser_context *ctx, parser_error *err, size_t *len);\n\n" % (prefix, prefix)) + header.write("%s_element **%s_parse_file_stream(FILE *stream, const struct "\ + "parser_context *ctx, parser_error *err, size_t *len);\n\n" % (prefix, prefix)) + header.write("%s_element **%s_parse_data(const char *jsondata, const struct "\ + "parser_context *ctx, parser_error *err, size_t *len);\n\n" % (prefix, prefix)) + header.write("char *%s_generate_json(const %s_element **ptr, size_t len, "\ + "const struct parser_context *ctx, parser_error *err);\n\n" % (prefix, prefix)) + + header.write("#ifdef __cplusplus\n") + header.write("}\n") + header.write("#endif\n\n") + header.write("#endif\n\n") diff --git a/src/json/schema/src/helpers.py b/src/json/schema/src/helpers.py new file mode 100644 index 0000000..b88ed69 --- /dev/null +++ b/src/json/schema/src/helpers.py @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- +''' +Description: helper class and functions +Interface: None +History: 2019-06-17 +''' +# - Copyright (C) Huawei Technologies., Ltd. 2018-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +# - Description: generate json +# - Author: tanyifeng +# - Create: 2018-04-25 +#!/usr/bin/python -Es +import os +import sys + +def appendSeparator(subStr): + ''' + Description: append only '_' at last position of subStr + Interface: None + History: 2019-09-20 + ''' + if subStr and subStr[-1] != '_': + subStr.append('_') + +def convertToCStyle(name): + ''' + Description: convert name to linux c format + Interface: None + History: 2019-06-17 + ''' + if name is None or name == "": + return "" + name = name.replace('.', '_').replace('-', '_').replace('/', '_') + subStr = [] + preindex = 0 + index = 0 + for index, char in enumerate(name): + if char == '_': + appendSeparator(subStr) + subStr.append(name[preindex:index].lower()) + preindex = index + 1 + if not char.isupper() and name[preindex].isupper() and \ + name[preindex + 1].isupper(): + appendSeparator(subStr) + subStr.append(name[preindex:index - 1].lower()) + preindex = index - 1 + continue + if char.isupper() and index > 0 and name[index - 1].islower(): + appendSeparator(subStr) + subStr.append(name[preindex:index].lower()) + preindex = index + + if preindex <= index and index >= 0 and name[index] != '_' and \ + preindex != 0: + appendSeparator(subStr) + subStr.append(name[preindex:index + 1].lower()) + result = ''.join(subStr) + return result + +def getMapCTypes(typ): + ''' + Description: Get map c types + Interface: None + History: 2019-06-17 + ''' + map_c_types = { + 'byte': 'uint8_t', + 'string': 'char *', + 'integer': 'int', + 'boolean': 'bool', + 'double': 'double', + 'int8': 'int8_t', + "int16": 'int16_t', + "int32": "int32_t", + "int64": "int64_t", + 'uint8': 'uint8_t', + "uint16": 'uint16_t', + "uint32": "uint32_t", + "uint64": "uint64_t", + "UID": "uid_t", + "GID": "gid_t", + "booleanPointer": "bool *", + 'bytePointer': 'uint8_t *', + 'integerPointer': 'int *', + 'doublePointer': 'double *', + 'int8Pointer': 'int8_t *', + "int16Pointer": 'int16_t *', + "int32Pointer": "int32_t *", + "int64Pointer": "int64_t *", + 'uint8Pointer': 'uint8_t *', + "uint16Pointer": 'uint16_t *', + "uint32Pointer": "uint32_t *", + "uint64Pointer": "uint64_t *", + } + if typ in map_c_types: + return map_c_types[typ] + return "" + +def validBasicMapName(typ): + ''' + Description: Valid basic map name + Interface: None + History: 2019-06-17 + ''' + return typ != 'mapStringObject' and hasattr(typ, 'startswith') and \ + typ.startswith('map') + +def makeBasicMapName(mapname): + ''' + Description: Make basic map name + Interface: None + History: 2019-06-17 + ''' + basic_map_types = ('string', 'int', 'bool') + parts = convertToCStyle(mapname).split('_') + if len(parts) != 3 or parts[0] != 'map' or \ + (parts[1] not in basic_map_types) or \ + (parts[2] not in basic_map_types): + print('Invalid map name: %s') % mapname + sys.exit(1) + return "json_map_%s_%s" % (parts[1], parts[2]) + + +def getNameSubstr(name, prefix): + ''' + Description: Make array name + Interface: None + History: 2019-06-17 + ''' + return "%s_element" % prefix if name is None or name == "" or prefix == name \ + else "%s_%s_element" % (prefix, name) + +def getPrefixName(name, prefix): + ''' + Description: Make name + Interface: None + History: 2019-06-17 + ''' + if name is None or name == "" or prefix.endswith(name): + return "%s" % prefix + if prefix is None or prefix == "" or prefix == name or name.endswith(prefix): + return "%s" % name + return "%s_%s" % (prefix, name) + +def getPrefixPointer(name, typ, prefix): + ''' + Description: Make pointer name + Interface: None + History: 2019-06-17 + ''' + if typ != 'object' and typ != 'mapStringObject' and \ + not validBasicMapName(typ): + return "" + return '%s *' % makeBasicMapName(typ) if validBasicMapName(typ) \ + else "%s *" % getPrefixName(name, prefix) + +def judgeComplex(typ): + ''' + Description: Check compound object + Interface: None + History: 2019-06-17 + ''' + return typ in ('object', 'array', 'mapStringObject') + +def judgeDataType(typ): + ''' + Description: Check numeric type + Interface: None + History: 2019-06-17 + ''' + if (typ.startswith("int") or typ.startswith("uint")) and \ + "Pointer" not in typ: + return True + return typ in ("integer", "UID", "GID", "double") + +def judgeDataPointerType(typ): + ''' + Description: Check numeric pointer type + Interface: None + History: 2019-06-17 + ''' + if (typ.startswith("int") or typ.startswith("uint")) and "Pointer" in typ: + return True + return False + +def obtainDataPointerType(typ): + ''' + Description: Get numeric pointer type + Interface: None + History: 2019-06-17 + ''' + index = typ.find("Pointer") + return typ[0:index] if index != -1 else "" + +def obtainPointer(name, typ, prefix): + ''' + Description: Obtain pointer string + Interface: None + History: 2019-06-17 + ''' + ptr = getPrefixPointer(name, typ, prefix) + if ptr != "": + return ptr + + return "char *" if typ == "string" else \ + ("%s *" % typ if typ == "ArrayOfStrings" else "") + +class CombinationName(object): + ''' + Description: Store CombinationName information + Interface: None + History: 2019-06-17 + ''' + + def __init__(self, name, leaf=None): + self.name = name + self.leaf = leaf + + def __repr__(self): + return self.name + + def __str__(self): + return self.name + + def append(self, leaf): + ''' + Description: append name + Interface: None + History: 2019-06-17 + ''' + prefix_name = self.name + '_' if self.name != "" else "" + return CombinationName(prefix_name + leaf, leaf) + + +class Unite(object): + ''' + Description: Store Unite information + Interface: None + History: 2019-06-17 + ''' + def __init__(self, name, typ, children, subtyp=None, subtypobj=None, subtypname=None, \ + required=None): + self.typ = typ + self.children = children + self.subtyp = subtyp + self.subtypobj = subtypobj + self.subtypname = subtypname + self.required = required + self.name = convertToCStyle(name.name.replace('.', '_')) + self.origname = name.leaf or name.name + self.fixname = convertToCStyle(self.origname.replace('.', '_')) + + + + def __repr__(self): + if self.subtyp is not None: + return "name:(%s) type:(%s -> %s)" \ + % (self.name, self.typ, self.subtyp) + return "name:(%s) type:(%s)" % (self.name, self.typ) + + def __str__(self): + return self.__repr__(self) + + +class FilePath(object): + ''' + Description: Store filepath information + Interface: None + History: 2019-06-17 + ''' + def __init__(self, name): + self.name = os.path.realpath(name) + self.dirname = os.path.dirname(self.name) + self.basename = os.path.basename(self.name) + + def __repr__(self): + return "{name:(%s) dirname:(%s) basename:(%s)}" \ + % (self.name, self.dirname, self.basename) + + def __str__(self): + return self.__repr__(self) + + +class SchemaInfo(object): + ''' + Description: Store schema information + Interface: None + History: 2019-06-17 + ''' + + def __init__(self, name, header, source, prefix, filesdir, refs=None): + self.name = name + self.fileprefix = convertToCStyle( \ + name.basename.replace('.', '_').replace('-', '_')) + self.header = header + self.source = source + self.prefix = prefix + self.refs = refs + self.filesdir = os.path.realpath(filesdir) + + def __repr__(self): + return "{name:(%s) header:(%s) source:(%s) prefix:(%s)}" \ + % (self.name, self.header, self.source, self.prefix) + + def __str__(self): + return self.__repr__(self) diff --git a/src/json/schema/src/read_file.c b/src/json/schema/src/read_file.c new file mode 100644 index 0000000..530e52e --- /dev/null +++ b/src/json/schema/src/read_file.c @@ -0,0 +1,151 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide file read functions + ********************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "securec.h" +#include "read_file.h" + +#ifndef O_CLOEXEC +#define O_CLOEXEC 02000000 +#endif + +#define JSON_MAX_SIZE (10LL * 1024LL * 1024LL) +#define FILE_MODE 0640 + +static int do_check_fread_args(const FILE *stream, const size_t *length) +{ + if (stream == NULL) { + return -1; + } + if (length == NULL) { + return -1; + } + + return 0; +} + +char *fread_file(FILE *stream, size_t *length) +{ + char *buf = NULL; + char *tmpbuf = NULL; + size_t off = 0; + + if (do_check_fread_args(stream, length) != 0) { + return NULL; + } + + while (1) { + size_t ret, newsize, sizejudge; + int pret; + errno_t rc = EOK; + sizejudge = (JSON_MAX_SIZE - BUFSIZ) - 1; + if (sizejudge < off) { + goto out; + } + newsize = off + BUFSIZ + 1; + + tmpbuf = (char *)calloc(1, newsize); + if (tmpbuf == NULL) { + goto out; + } + + if (buf != NULL) { + pret = memcpy_s(tmpbuf, newsize, buf, off); + if (pret) { + goto out; + } + + rc = memset_s(buf, off, 0, off); + if (rc != EOK) { + goto out; + } + + free(buf); + } + + buf = tmpbuf; + tmpbuf = NULL; + + ret = fread(buf + off, 1, BUFSIZ, stream); + if (ret == 0 && ferror(stream)) { + goto out; + } + if (ret < BUFSIZ || feof(stream)) { + *length = off + ret + 1; + buf[*length - 1] = '\0'; + return buf; + } + + off += BUFSIZ; + } +out: + free(buf); + free(tmpbuf); + return NULL; +} + +static int do_check_args(const char *path, const size_t *length) +{ + if (path == NULL || length == NULL) { + return -1; + } + if (strlen(path) > PATH_MAX) { + return -1; + } + return 0; +} + +char *read_file(const char *path, size_t *length) +{ + char *buf = NULL; + char rpath[PATH_MAX + 1] = { 0 }; + int fd = -1; + int tmperrno = -1; + FILE *fp = NULL; + + if (do_check_args(path, length) != 0) { + return NULL; + } + + if (realpath(path, rpath) == NULL) { + return NULL; + } + + fd = open(rpath, O_RDONLY | O_CLOEXEC, FILE_MODE); + if (fd < 0) { + return NULL; + } + + fp = fdopen(fd, "r"); + tmperrno = errno; + if (fp == NULL) { + (void)close(fd); + errno = tmperrno; + return NULL; + } + + buf = fread_file(fp, length); + (void)fclose(fp); + return buf; +} diff --git a/src/json/schema/src/read_file.h b/src/json/schema/src/read_file.h new file mode 100644 index 0000000..cc14940 --- /dev/null +++ b/src/json/schema/src/read_file.h @@ -0,0 +1,25 @@ +/***************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container read file definition + ****************************************************************************/ +#ifndef __JSON_READ_FILE_H_ +#define __JSON_READ_FILE_H_ + +#include +#include + +char *fread_file(FILE *stream, size_t *length); + +char *read_file(const char *path, size_t *length); + +#endif diff --git a/src/json/schema/src/sources.py b/src/json/schema/src/sources.py new file mode 100644 index 0000000..6835a9e --- /dev/null +++ b/src/json/schema/src/sources.py @@ -0,0 +1,1013 @@ +# -*- coding: utf-8 -*- +""" +Copyright (C) Huawei Technologies., Ltd. 2017-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +Description: generate json +Author: tanyifeng +Interface: None +History: 2018-04-25 created +2019-06-17 Code specification +""" +#!/usr/bin/python -Es +import helpers + + +def appendCCode(obj, c_file, prefix): + """ + Description: append c language code to file + Interface: None + History: 2019-06-17 + """ + parseJsonToC(obj, c_file, prefix) + makeCFree(obj, c_file, prefix) + obtainCJson(obj, c_file, prefix) + +def parseMapStringObject(obj, c_file, prefix, obj_typename): + """ + Description: generate c language for parse json map string object + Interface: None + History: 2019-06-17 + """ + child = obj.children[0] + if helpers.validBasicMapName(child.typ): + childname = helpers.makeBasicMapName(child.typ) + else: + if child.subtypname: + childname = child.subtypname + else: + childname = helpers.getPrefixName(child.name, prefix) + c_file.write(' if (YAJL_GET_OBJECT(tree) != NULL && YAJL_GET_OBJECT(tree)->len > 0) {\n') + c_file.write(' size_t i;\n') + c_file.write(' ret->len = YAJL_GET_OBJECT(tree)->len;\n') + c_file.write(' ret->keys = safe_malloc((YAJL_GET_OBJECT(tree)->len + 1) ' \ + '* sizeof(*ret->keys));\n') + c_file.write(' ret->%s = safe_malloc((YAJL_GET_OBJECT(tree)->len + 1) ' \ + '* sizeof(*ret->%s));\n' % (child.fixname, child.fixname)) + c_file.write(' for (i = 0; i < YAJL_GET_OBJECT(tree)->len; i++) {\n') + c_file.write(' const char *tmpkey = YAJL_GET_OBJECT(tree)->keys[i];\n') + c_file.write(' ret->keys[i] = safe_strdup(tmpkey ? tmpkey : "");\n') + c_file.write(' yajl_val val = YAJL_GET_OBJECT(tree)->values[i];\n') + c_file.write(' ret->%s[i] = make_%s(val, ctx, err);\n' \ + % (child.fixname, childname)) + c_file.write(' if (ret->%s[i] == NULL) {\n' % (child.fixname)) + c_file.write(" free_%s(ret);\n" % obj_typename) + c_file.write(" return NULL;\n") + c_file.write(' }\n') + c_file.write(' }\n') + c_file.write(' }\n') + +def parseObjectType(obj, c_file, prefix, obj_typename): + """ + Description: generate c language for parse object type + Interface: None + History: 2019-06-17 + """ + if obj.typ == 'string': + c_file.write(' {\n') + readValueGenerator(c_file, 2, 'get_val(tree, "%s", yajl_t_string)' % obj.origname, \ + "ret->%s" % obj.fixname, obj.typ, obj.origname, obj_typename) + c_file.write(' }\n') + elif helpers.judgeDataType(obj.typ): + c_file.write(' {\n') + readValueGenerator(c_file, 2, 'get_val(tree, "%s", yajl_t_number)' % obj.origname, \ + "ret->%s" % obj.fixname, obj.typ, obj.origname, obj_typename) + c_file.write(' }\n') + elif helpers.judgeDataPointerType(obj.typ): + c_file.write(' {\n') + readValueGenerator(c_file, 2, 'get_val(tree, "%s", yajl_t_number)' % obj.origname, \ + "ret->%s" % obj.fixname, obj.typ, obj.origname, obj_typename) + c_file.write(' }\n') + if obj.typ == 'boolean': + c_file.write(' {\n') + readValueGenerator(c_file, 2, 'get_val(tree, "%s", yajl_t_true)' % obj.origname, \ + "ret->%s" % obj.fixname, obj.typ, obj.origname, obj_typename) + c_file.write(' }\n') + if obj.typ == 'booleanPointer': + c_file.write(' {\n') + readValueGenerator(c_file, 2, 'get_val(tree, "%s", yajl_t_true)' % obj.origname, \ + "ret->%s" % obj.fixname, obj.typ, obj.origname, obj_typename) + c_file.write(' }\n') + elif obj.typ == 'object' or obj.typ == 'mapStringObject': + if obj.subtypname is not None: + typename = obj.subtypname + else: + typename = helpers.getPrefixName(obj.name, prefix) + c_file.write( + ' ret->%s = make_%s(get_val(tree, "%s", yajl_t_object), ctx, err);\n' \ + % (obj.fixname, typename, obj.origname)) + c_file.write(" if (ret->%s == NULL && *err != 0) {\n" % obj.fixname) + c_file.write(" free_%s(ret);\n" % obj_typename) + c_file.write(" return NULL;\n") + c_file.write(" }\n") + elif obj.typ == 'array' and (obj.subtypobj or obj.subtyp == 'object'): + if obj.subtypname: + typename = obj.subtypname + else: + typename = helpers.getNameSubstr(obj.name, prefix) + c_file.write(' {\n') + c_file.write(' yajl_val tmp = get_val(tree, "%s", yajl_t_array);\n' \ + % (obj.origname)) + c_file.write(' if (tmp != NULL && YAJL_GET_ARRAY(tmp) != NULL &&' \ + ' YAJL_GET_ARRAY(tmp)->len > 0) {\n') + c_file.write(' size_t i;\n') + c_file.write(' ret->%s_len = YAJL_GET_ARRAY(tmp)->len;\n' % (obj.fixname)) + c_file.write(' ret->%s = safe_malloc((YAJL_GET_ARRAY(tmp)->len + 1) ' \ + '* sizeof(*ret->%s));\n' % (obj.fixname, obj.fixname)) + c_file.write(' for (i = 0; i < YAJL_GET_ARRAY(tmp)->len; i++) {\n') + c_file.write(' yajl_val val = YAJL_GET_ARRAY(tmp)->values[i];\n') + c_file.write(' ret->%s[i] = make_%s(val, ctx, err);\n' \ + % (obj.fixname, typename)) + c_file.write(' if (ret->%s[i] == NULL) {\n' % (obj.fixname)) + c_file.write(" free_%s(ret);\n" % obj_typename) + c_file.write(" return NULL;\n") + c_file.write(' }\n') + c_file.write(' }\n') + c_file.write(' }\n') + c_file.write(' }\n') + elif obj.typ == 'array' and obj.subtyp == 'byte': + c_file.write(' {\n') + c_file.write(' yajl_val tmp = get_val(tree, "%s", yajl_t_string);\n' \ + % (obj.origname)) + c_file.write(' if (tmp != NULL) {\n') + c_file.write(' char *str = YAJL_GET_STRING(tmp);\n') + c_file.write(' ret->%s = (uint8_t *)safe_strdup(str ? str : "");\n' \ + % obj.fixname) + c_file.write(' ret->%s_len = str != NULL ? strlen(str) : 0;\n' \ + % obj.fixname) + c_file.write(' }\n') + c_file.write(' }\n') + elif obj.typ == 'array': + c_file.write(' {\n') + c_file.write(' yajl_val tmp = get_val(tree, "%s", yajl_t_array);\n' \ + % (obj.origname)) + c_file.write(' if (tmp != NULL && YAJL_GET_ARRAY(tmp) != NULL &&' \ + ' YAJL_GET_ARRAY(tmp)->len > 0) {\n') + c_file.write(' size_t i;\n') + c_file.write(' ret->%s_len = YAJL_GET_ARRAY(tmp)->len;\n' % (obj.fixname)) + c_file.write( + ' ret->%s = safe_malloc((YAJL_GET_ARRAY(tmp)->len + 1) *' \ + ' sizeof(*ret->%s));\n' % (obj.fixname, obj.fixname)) + c_file.write(' for (i = 0; i < YAJL_GET_ARRAY(tmp)->len; i++) {\n') + readValueGenerator(c_file, 4, 'YAJL_GET_ARRAY(tmp)->values[i]', \ + "ret->%s[i]" % obj.fixname, obj.subtyp, obj.origname, obj_typename) + c_file.write(' }\n') + c_file.write(' }\n') + c_file.write(' }\n') + elif helpers.validBasicMapName(obj.typ): + c_file.write(' {\n') + c_file.write(' yajl_val tmp = get_val(tree, "%s", yajl_t_object);\n' \ + % (obj.origname)) + c_file.write(' if (tmp != NULL) {\n') + c_file.write(' ret->%s = make_%s(tmp, ctx, err);\n' \ + % (obj.fixname, helpers.makeBasicMapName(obj.typ))) + c_file.write(' if (ret->%s == NULL) {\n' % (obj.fixname)) + c_file.write(' char *new_error = NULL;\n') + c_file.write(" if (asprintf(&new_error, \"Value error for key" \ + " '%s': %%s\", *err ? *err : \"null\") < 0) {\n" % (obj.origname)) + c_file.write(' new_error = safe_strdup(' \ + '"error allocating memory");\n') + c_file.write(' }\n') + c_file.write(' free(*err);\n') + c_file.write(' *err = new_error;\n') + c_file.write(' free_%s(ret);\n' % obj_typename) + c_file.write(' return NULL;\n') + c_file.write(' }\n') + c_file.write(' }\n') + c_file.write(' }\n') + +def parseObjectOrArrayObject(obj, c_file, prefix, obj_typename): + """ + Description: generate c language for parse object or array object + Interface: None + History: 2019-06-17 + """ + nodes = obj.children if obj.typ == 'object' else obj.subtypobj + + required_to_check = [] + for i in nodes or []: + if obj.required and i.origname in obj.required and \ + not helpers.judgeDataType(i.typ) and i.typ != 'boolean': + required_to_check.append(i) + parseObjectType(i, c_file, prefix, obj_typename) + + for i in required_to_check: + c_file.write(' if (ret->%s == NULL) {\n' % i.fixname) + c_file.write(' if (asprintf(err, "Required field \'%%s\' not present", ' \ + ' "%s") < 0)\n' % i.origname) + c_file.write(' *err = safe_strdup("error allocating memory");\n') + c_file.write(" free_%s(ret);\n" % obj_typename) + c_file.write(" return NULL;\n") + c_file.write(' }\n') + + if obj.typ == 'object' and obj.children is not None: + # O(n^2) complexity, but the objects should not really be big... + condition = " &&\n ".join( \ + ['strcmp(tree->u.object.keys[i], "%s")' % i.origname for i in obj.children]) + c_file.write(""" + if (tree->type == yajl_t_object && (ctx->options & OPT_PARSE_STRICT)) { + size_t i; + for (i = 0; i < tree->u.object.len; i++) + if (%s) { + if (ctx->stderr > 0) + (void)fprintf(ctx->stderr, "WARNING: unknown key found: %%s\\n", + tree->u.object.keys[i]); + } + } +""" % condition) + +def parseJsonToC(obj, c_file, prefix): + """ + Description: generate c language for parse json file + Interface: None + History: 2019-06-17 + """ + if not helpers.judgeComplex(obj.typ): + return + + if obj.typ == 'object' or obj.typ == 'mapStringObject': + if obj.subtypname: + return + obj_typename = typename = helpers.getPrefixName(obj.name, prefix) + + if obj.typ == 'array': + obj_typename = typename = helpers.getNameSubstr(obj.name, prefix) + objs = obj.subtypobj + if objs is None or obj.subtypname: + return + + c_file.write("%s *make_%s(yajl_val tree, const struct parser_context *ctx, "\ + "parser_error *err) {\n" % (typename, typename)) + c_file.write(" %s *ret = NULL;\n" % (typename)) + c_file.write(" *err = 0;\n") + c_file.write(" if (tree == NULL)\n") + c_file.write(" return ret;\n") + c_file.write(" ret = safe_malloc(sizeof(*ret));\n") + + if obj.typ == 'mapStringObject': + parseMapStringObject(obj, c_file, prefix, obj_typename) + + if obj.typ == 'object' or (obj.typ == 'array' and obj.subtypobj): + parseObjectOrArrayObject(obj, c_file, prefix, obj_typename) + + c_file.write(' return ret;\n') + c_file.write("}\n\n") + + +def obtainMapStringObject(obj, c_file, prefix): + """ + Description: c language generate map string object + Interface: None + History: 2019-06-17 + """ + child = obj.children[0] + if helpers.validBasicMapName(child.typ): + childname = helpers.makeBasicMapName(child.typ) + else: + if child.subtypname: + childname = child.subtypname + else: + childname = helpers.getPrefixName(child.name, prefix) + c_file.write(' size_t len = 0, i;\n') + c_file.write(" if (ptr != NULL)\n") + c_file.write(" len = ptr->len;\n") + c_file.write(" if (!len && !(ctx->options & OPT_GEN_SIMPLIFY))\n") + c_file.write(' yajl_gen_config(g, yajl_gen_beautify, 0);\n') + c_file.write(" stat = yajl_gen_map_open((yajl_gen)g);\n") + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' if (len ||(ptr != NULL && ptr->keys != NULL && ptr->%s != NULL)) {\n' \ + % child.fixname) + c_file.write(' for (i = 0; i < len; i++) {\n') + c_file.write(' char *str = ptr->keys[i] ? ptr->keys[i] : "";\n') + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)str, strlen(str));\n') + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' stat = gen_%s(g, ptr->%s[i], ctx, err);\n' \ + % (childname, child.fixname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' }\n') + c_file.write(' }\n') + c_file.write(" stat = yajl_gen_map_close((yajl_gen)g);\n") + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (!len && !(ctx->options & OPT_GEN_SIMPLIFY))\n") + c_file.write(' yajl_gen_config(g, yajl_gen_beautify, 1);\n') + +def obtainObjectOrArrayObject(obj, c_file, prefix): + """ + Description: c language generate object or array object + Interface: None + History: 2019-06-17 + """ + if obj.typ == 'string': + c_file.write(' if ((ctx->options & OPT_GEN_KAY_VALUE) ||' \ + ' (ptr != NULL && ptr->%s != NULL)) {\n' % obj.fixname) + c_file.write(' char *str = "";\n') + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (ptr != NULL && ptr->%s != NULL) {\n" % obj.fixname) + c_file.write(" str = ptr->%s;\n" % obj.fixname) + c_file.write(" }\n") + jsonValueGenerator(c_file, 2, "str", 'g', 'ctx', obj.typ) + c_file.write(" }\n") + + elif helpers.judgeDataType(obj.typ): + c_file.write(' if ((ctx->options & OPT_GEN_KAY_VALUE) ||' \ + ' (ptr != NULL && ptr->%s)) {\n' % obj.fixname) + if obj.typ == 'double': + numtyp = 'double' + elif obj.typ.startswith("uint") or obj.typ == 'GID' or obj.typ == 'UID': + numtyp = 'long long unsigned int' + else: + numtyp = 'long long int' + c_file.write(' %s num = 0;\n' % numtyp) + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (ptr != NULL && ptr->%s) {\n" % obj.fixname) + c_file.write(" num = (%s)ptr->%s;\n" % (numtyp, obj.fixname)) + c_file.write(" }\n") + jsonValueGenerator(c_file, 2, "num", 'g', 'ctx', obj.typ) + c_file.write(" }\n") + + elif helpers.judgeDataPointerType(obj.typ): + c_file.write(' if ((ptr != NULL && ptr->%s != NULL)) {\n' % obj.fixname) + numtyp = helpers.obtainDataPointerType(obj.typ) + if numtyp == "": + return + c_file.write(' %s num = 0;\n' % helpers.getMapCTypes(numtyp)) + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (ptr != NULL && ptr->%s != NULL) {\n" % obj.fixname) + c_file.write(" num = (%s)*(ptr->%s);\n" \ + % (helpers.getMapCTypes(numtyp), obj.fixname)) + c_file.write(" }\n") + jsonValueGenerator(c_file, 2, "num", 'g', 'ctx', numtyp) + c_file.write(" }\n") + + elif obj.typ == 'boolean': + c_file.write(' if ((ctx->options & OPT_GEN_KAY_VALUE) ||' \ + ' (ptr != NULL && ptr->%s)) {\n' % obj.fixname) + c_file.write(' bool b = false;\n') + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (ptr != NULL && ptr->%s) {\n" % obj.fixname) + c_file.write(" b = ptr->%s;\n" % obj.fixname) + c_file.write(" }\n") + jsonValueGenerator(c_file, 2, "b", 'g', 'ctx', obj.typ) + c_file.write(" }\n") + elif obj.typ == 'object' or obj.typ == 'mapStringObject': + if obj.subtypname: + typename = obj.subtypname + else: + typename = helpers.getPrefixName(obj.name, prefix) + c_file.write(' if ((ctx->options & OPT_GEN_KAY_VALUE) ||' \ + ' (ptr != NULL && ptr->%s != NULL)) {\n' % obj.fixname) + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' stat = gen_%s(g, ptr != NULL ? ptr->%s : NULL, ctx, err);\n' \ + % (typename, obj.fixname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" }\n") + + elif obj.typ == 'array' and (obj.subtypobj or obj.subtyp == 'object'): + if obj.subtypname: + typename = obj.subtypname + else: + typename = helpers.getNameSubstr(obj.name, prefix) + c_file.write(' if ((ctx->options & OPT_GEN_KAY_VALUE) || ' \ + '(ptr != NULL && ptr->%s != NULL)) {\n' % obj.fixname) + c_file.write(' size_t len = 0, i;\n') + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (ptr != NULL && ptr->%s != NULL) {\n" % obj.fixname) + c_file.write(" len = ptr->%s_len;\n" % obj.fixname) + c_file.write(" }\n") + c_file.write(" if (!len && !(ctx->options & OPT_GEN_SIMPLIFY))\n") + c_file.write(' yajl_gen_config(g, yajl_gen_beautify, 0);\n') + c_file.write(' stat = yajl_gen_array_open((yajl_gen)g);\n') + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' for (i = 0; i < len; i++) {\n') + c_file.write(' stat = gen_%s(g, ptr->%s[i], ctx, err);\n' \ + % (typename, obj.fixname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' }\n') + c_file.write(' stat = yajl_gen_array_close((yajl_gen)g);\n') + c_file.write(" if (!len && !(ctx->options & OPT_GEN_SIMPLIFY))\n") + c_file.write(' yajl_gen_config(g, yajl_gen_beautify, 1);\n') + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' }\n') + elif obj.typ == 'array' and obj.subtyp == 'byte': + c_file.write(' if ((ctx->options & OPT_GEN_KAY_VALUE) ||' \ + ' (ptr != NULL && ptr->%s != NULL && ptr->%s_len)) {\n' \ + % (obj.fixname, obj.fixname)) + c_file.write(' const char *str = "";\n') + c_file.write(' size_t len = 0;\n') + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (ptr != NULL && ptr->%s != NULL) {\n" % obj.fixname) + c_file.write(" str = (const char *)ptr->%s;\n" % obj.fixname) + c_file.write(" len = ptr->%s_len;\n" % obj.fixname) + c_file.write(" }\n") + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)str, len);\n') + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" }\n") + elif obj.typ == 'array': + c_file.write(' if ((ctx->options & OPT_GEN_KAY_VALUE) || ' \ + '(ptr != NULL && ptr->%s != NULL)) {\n' % obj.fixname) + c_file.write(' size_t len = 0, i;\n') + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (ptr != NULL && ptr->%s != NULL) {\n" % obj.fixname) + c_file.write(" len = ptr->%s_len;\n" % obj.fixname) + c_file.write(" }\n") + c_file.write(" if (!len && !(ctx->options & OPT_GEN_SIMPLIFY))\n") + c_file.write(' yajl_gen_config(g, yajl_gen_beautify, 0);\n') + c_file.write(' stat = yajl_gen_array_open((yajl_gen)g);\n') + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' for (i = 0; i < len; i++) {\n') + jsonValueGenerator(c_file, 3, "ptr->%s[i]" % obj.fixname, 'g', 'ctx', obj.subtyp) + c_file.write(' }\n') + c_file.write(' stat = yajl_gen_array_close((yajl_gen)g);\n') + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" if (!len && !(ctx->options & OPT_GEN_SIMPLIFY))\n") + c_file.write(' yajl_gen_config(g, yajl_gen_beautify, 1);\n') + c_file.write(' }\n') + + elif helpers.validBasicMapName(obj.typ): + c_file.write(' if ((ctx->options & OPT_GEN_KAY_VALUE) || ' \ + '(ptr != NULL && ptr->%s != NULL)) {\n' % obj.fixname) + c_file.write(' stat = yajl_gen_string((yajl_gen)g, (const unsigned char *)("%s"), strlen("%s"));\n' \ + % (obj.origname, obj.origname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(' stat = gen_%s(g, ptr ? ptr->%s : NULL, ctx, err);\n' \ + % (helpers.makeBasicMapName(obj.typ), obj.fixname)) + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + c_file.write(" }\n") + +def obtainCJson(obj, c_file, prefix): + """ + Description: c language generate json file + Interface: None + History: 2019-06-17 + """ + if not helpers.judgeComplex(obj.typ) or obj.subtypname: + return + if obj.typ == 'object' or obj.typ == 'mapStringObject': + obj_typename = typename = helpers.getPrefixName(obj.name, prefix) + elif obj.typ == 'array': + obj_typename = typename = helpers.getNameSubstr(obj.name, prefix) + objs = obj.subtypobj + if objs is None: + return + + c_file.write( + "yajl_gen_status gen_%s(yajl_gen g, const %s *ptr, const struct parser_context " \ + "*ctx, parser_error *err) {\n" % (typename, typename)) + c_file.write(" yajl_gen_status stat = yajl_gen_status_ok;\n") + c_file.write(" *err = 0;\n") + + if obj.typ == 'mapStringObject': + obtainMapStringObject(obj, c_file, prefix) + elif obj.typ == 'object' or (obj.typ == 'array' and obj.subtypobj): + nodes = obj.children if obj.typ == 'object' else obj.subtypobj + if nodes is None: + c_file.write(' if (!(ctx->options & OPT_GEN_SIMPLIFY))\n') + c_file.write(' yajl_gen_config(g, yajl_gen_beautify, 0);\n') + + c_file.write(" stat = yajl_gen_map_open((yajl_gen)g);\n") + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + + for i in nodes or []: + obtainObjectOrArrayObject(i, c_file, prefix) + + c_file.write(" stat = yajl_gen_map_close((yajl_gen)g);\n") + c_file.write(" if (yajl_gen_status_ok != stat)\n") + c_file.write(" GEN_SET_ERROR_AND_RETURN(stat, err);\n") + if nodes is None: + c_file.write(' if (!(ctx->options & OPT_GEN_SIMPLIFY))\n') + c_file.write(' yajl_gen_config(g, yajl_gen_beautify, 1);\n') + c_file.write(' return yajl_gen_status_ok;\n') + c_file.write("}\n\n") + + +def readValueGenerator(c_file, level, src, dest, typ, keyname, obj_typename): + """ + Description: read value generateor + Interface: None + History: 2019-06-17 + """ + if helpers.validBasicMapName(typ): + c_file.write('%syajl_val val = %s;\n' % (' ' * level, src)) + c_file.write('%sif (val != NULL) {\n' % (' ' * level)) + c_file.write('%s%s = make_%s(val, ctx, err);\n' \ + % (' ' * (level + 1), dest, helpers.makeBasicMapName(typ))) + c_file.write('%sif (%s == NULL) {\n' % (' ' * (level + 1), dest)) + c_file.write('%s char *new_error = NULL;\n' % (' ' * (level + 1))) + c_file.write("%s if (asprintf(&new_error, \"Value error for key" \ + " '%s': %%s\", *err ? *err : \"null\") < 0) {\n" \ + % (' ' * (level + 1), keyname)) + c_file.write('%s new_error = safe_strdup("error allocating memory");\n' \ + % (' ' * (level + 1))) + c_file.write('%s }\n' % (' ' * (level + 1))) + c_file.write('%s free(*err);\n' % (' ' * (level + 1))) + c_file.write('%s *err = new_error;\n' % (' ' * (level + 1))) + c_file.write('%s free_%s(ret);\n' % (' ' * (level + 1), obj_typename)) + c_file.write('%s return NULL;\n' % (' ' * (level + 1))) + c_file.write('%s}\n' % (' ' * (level + 1))) + c_file.write('%s}\n' % (' ' * (level))) + elif typ == 'string': + c_file.write('%syajl_val val = %s;\n' % (' ' * (level), src)) + c_file.write('%sif (val != NULL) {\n' % (' ' * (level))) + c_file.write('%schar *str = YAJL_GET_STRING(val);\n' % (' ' * (level + 1))) + c_file.write('%s%s = safe_strdup(str ? str : "");\n' % (' ' * (level + 1), dest)) + c_file.write('%s}\n' % (' ' * level)) + elif helpers.judgeDataType(typ): + c_file.write('%syajl_val val = %s;\n' % (' ' * (level), src)) + c_file.write('%sif (val != NULL) {\n' % (' ' * (level))) + if typ.startswith("uint") or \ + (typ.startswith("int") and typ != "integer") or typ == "double": + c_file.write('%sint invalid = common_safe_%s(YAJL_GET_NUMBER(val), &%s);\n' \ + % (' ' * (level + 1), typ, dest)) + elif typ == "integer": + c_file.write('%sint invalid = common_safe_int(YAJL_GET_NUMBER(val), (int *)&%s);\n' \ + % (' ' * (level + 1), dest)) + elif typ == "UID" or typ == "GID": + c_file.write('%sint invalid = common_safe_uint(YAJL_GET_NUMBER(val),' \ + ' (unsigned int *)&%s);\n' % (' ' * (level + 1), dest)) + c_file.write('%sif (invalid) {\n' % (' ' * (level + 1))) + c_file.write('%s if (asprintf(err, "Invalid value \'%%s\' with type \'%s\' ' + 'for key \'%s\': %%s", YAJL_GET_NUMBER(val), strerror(-invalid)) < 0)\n' \ + % (' ' * (level + 1), typ, keyname)) + c_file.write('%s *err = safe_strdup("error allocating memory");\n' \ + % (' ' * (level + 1))) + c_file.write('%s free_%s(ret);\n' % (' ' * (level + 1), obj_typename)) + c_file.write('%s return NULL;\n' % (' ' * (level + 1))) + c_file.write('%s}\n' % (' ' * (level + 1))) + c_file.write('%s}\n' % (' ' * (level))) + elif helpers.judgeDataPointerType(typ): + num_type = helpers.obtainDataPointerType(typ) + if num_type == "": + return + c_file.write('%syajl_val val = %s;\n' % (' ' * (level), src)) + c_file.write('%sif (val != NULL) {\n' % (' ' * (level))) + c_file.write('%s%s = safe_malloc(sizeof(%s));\n' % + (' ' * (level + 1), dest, helpers.getMapCTypes(num_type))) + c_file.write('%sint invalid = common_safe_%s(YAJL_GET_NUMBER(val), %s);\n' \ + % (' ' * (level + 1), num_type, dest)) + c_file.write('%sif (invalid) {\n' % (' ' * (level + 1))) + c_file.write('%s if (asprintf(err, "Invalid value \'%%s\' with type \'%s\' ' \ + 'for key \'%s\': %%s", YAJL_GET_NUMBER(val), strerror(-invalid)) < 0)\n' \ + % (' ' * (level + 1), typ, keyname)) + c_file.write('%s *err = safe_strdup("error allocating memory");\n' \ + % (' ' * (level + 1))) + c_file.write('%s free_%s(ret);\n' % (' ' * (level + 1), obj_typename)) + c_file.write('%s return NULL;\n' % (' ' * (level + 1))) + c_file.write('%s}\n' % (' ' * (level + 1))) + c_file.write('%s}\n' % (' ' * (level))) + elif typ == 'boolean': + c_file.write('%syajl_val val = %s;\n' % (' ' * (level), src)) + c_file.write('%sif (val != NULL)\n' % (' ' * (level))) + c_file.write('%s%s = YAJL_IS_TRUE(val);\n' % (' ' * (level + 1), dest)) + elif typ == 'booleanPointer': + c_file.write('%syajl_val val = %s;\n' % (' ' * (level), src)) + c_file.write('%sif (val != NULL) {\n' % (' ' * (level))) + c_file.write('%s%s = safe_malloc(sizeof(bool));\n' % (' ' * (level + 1), dest)) + c_file.write('%s*(%s) = YAJL_IS_TRUE(val);\n' % (' ' * (level + 1), dest)) + c_file.write('%s} else {\n' % (' ' * (level))) + c_file.write('%sval = get_val(tree, "%s", yajl_t_false);\n' \ + % (' ' * (level + 1), keyname)) + c_file.write('%sif (val != NULL) {\n' % (' ' * (level + 1))) + c_file.write('%s%s = safe_malloc(sizeof(bool));\n' % (' ' * (level + 2), dest)) + c_file.write('%s*(%s) = YAJL_IS_TRUE(val);\n' % (' ' * (level + 2), dest)) + c_file.write('%s}\n' % (' ' * (level + 1))) + c_file.write('%s}\n' % (' ' * (level))) + + +def jsonValueGenerator(c_file, level, src, dst, ptx, typ): + """ + Description: json value generateor + Interface: None + History: 2019-06-17 + """ + if helpers.validBasicMapName(typ): + c_file.write('%sstat = gen_%s(%s, %s, %s, err);\n' \ + % (' ' * (level), helpers.makeBasicMapName(typ), dst, src, ptx)) + c_file.write("%sif (yajl_gen_status_ok != stat)\n" % (' ' * (level))) + c_file.write("%sGEN_SET_ERROR_AND_RETURN(stat, err);\n" % (' ' * (level + 1))) + elif typ == 'string': + c_file.write('%sstat = yajl_gen_string((yajl_gen)%s, (const unsigned char *)(%s), strlen(%s));\n' \ + % (' ' * (level), dst, src, src)) + c_file.write("%sif (yajl_gen_status_ok != stat)\n" % (' ' * (level))) + c_file.write("%sGEN_SET_ERROR_AND_RETURN(stat, err);\n" % (' ' * (level + 1))) + + elif helpers.judgeDataType(typ): + if typ == 'double': + c_file.write('%sstat = yajl_gen_double((yajl_gen)%s, %s);\n' % (' ' * (level), dst, src)) + elif typ.startswith("uint") or typ == 'GID' or typ == 'UID': + c_file.write('%sstat = map_uint(%s, %s);\n' % (' ' * (level), dst, src)) + else: + c_file.write('%sstat = map_int(%s, %s);\n' % (' ' * (level), dst, src)) + c_file.write("%sif (yajl_gen_status_ok != stat)\n" % (' ' * (level))) + c_file.write("%sGEN_SET_ERROR_AND_RETURN(stat, err);\n" % (' ' * (level + 1))) + + elif typ == 'boolean': + c_file.write('%sstat = yajl_gen_bool((yajl_gen)%s, (int)(%s));\n' % (' ' * (level), dst, src)) + c_file.write("%sif (yajl_gen_status_ok != stat)\n" % (' ' * (level))) + c_file.write("%sGEN_SET_ERROR_AND_RETURN(stat, err);\n" % (' ' * (level + 1))) + + +def makeCFree(obj, c_file, prefix): + """ + Description: generate c free function + Interface: None + History: 2019-06-17 + """ + if not helpers.judgeComplex(obj.typ) or obj.subtypname: + return + + typename = helpers.getPrefixName(obj.name, prefix) + + case = obj.typ + result = { + 'mapStringObject': lambda x: [], + 'object': lambda x: x.children, + 'array': lambda x: x.subtypobj + }[case](obj) + + objs = result + if obj.typ == 'array': + if objs is None: + return + else: + typename = helpers.getNameSubstr(obj.name, prefix) + + c_file.write("void free_%s(%s *ptr) {\n" % (typename, typename)) + c_file.write(" if (ptr == NULL)\n") + c_file.write(" return;\n") + if obj.typ == 'mapStringObject': + child = obj.children[0] + if helpers.validBasicMapName(child.typ): + childname = helpers.makeBasicMapName(child.typ) + else: + if child.subtypname: + childname = child.subtypname + else: + childname = helpers.getPrefixName(child.name, prefix) + c_file.write(" if (ptr->keys != NULL && ptr->%s != NULL) {\n" % child.fixname) + c_file.write(" size_t i;\n") + c_file.write(" for (i = 0; i < ptr->len; i++) {\n") + c_file.write(" free(ptr->keys[i]);\n") + c_file.write(" ptr->keys[i] = NULL;\n") + c_file.write(" free_%s(ptr->%s[i]);\n" % (childname, child.fixname)) + c_file.write(" ptr->%s[i] = NULL;\n" % (child.fixname)) + c_file.write(" }\n") + c_file.write(" free(ptr->keys);\n") + c_file.write(" ptr->keys = NULL;\n") + c_file.write(" free(ptr->%s);\n" % (child.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (child.fixname)) + c_file.write(" }\n") + + for i in objs or []: + if helpers.validBasicMapName(i.typ): + free_func = helpers.makeBasicMapName(i.typ) + c_file.write(" free_%s(ptr->%s);\n" % (free_func, i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + if i.typ == 'mapStringObject': + if i.subtypname: + free_func = i.subtypname + else: + free_func = helpers.getPrefixName(i.name, prefix) + c_file.write(" free_%s(ptr->%s);\n" % (free_func, i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + elif i.typ == 'array': + if helpers.validBasicMapName(i.subtyp): + free_func = helpers.makeBasicMapName(i.subtyp) + c_file.write(" if (ptr->%s != NULL) {\n" % i.fixname) + c_file.write(" size_t i;\n") + c_file.write(" for (i = 0; i < ptr->%s_len; i++) {\n" % i.fixname) + c_file.write(" if (ptr->%s[i] != NULL) {\n" % (i.fixname)) + c_file.write(" free_%s(ptr->%s[i]);\n" % (free_func, i.fixname)) + c_file.write(" ptr->%s[i] = NULL;\n" % (i.fixname)) + c_file.write(" }\n") + c_file.write(" }\n") + c_file.write(" free(ptr->%s);\n" % (i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + c_file.write(" }\n") + elif i.subtyp == 'string': + c_file.write(" if (ptr->%s != NULL) {\n" % i.fixname) + c_file.write(" size_t i;\n") + c_file.write(" for (i = 0; i < ptr->%s_len; i++) {\n" % i.fixname) + c_file.write(" if (ptr->%s[i] != NULL) {\n" % (i.fixname)) + c_file.write(" free(ptr->%s[i]);\n" % (i.fixname)) + c_file.write(" ptr->%s[i] = NULL;\n" % (i.fixname)) + c_file.write(" }\n") + c_file.write(" }\n") + c_file.write(" free(ptr->%s);\n" % (i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + c_file.write(" }\n") + elif not helpers.judgeComplex(i.subtyp): + c_file.write(" free(ptr->%s);\n" % (i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + elif i.subtyp == 'object' or i.subtypobj is not None: + if i.subtypname is not None: + free_func = i.subtypname + else: + free_func = helpers.getNameSubstr(i.name, prefix) + c_file.write(" if (ptr->%s != NULL) {\n" % i.fixname) + c_file.write(" size_t i;\n") + c_file.write(" for (i = 0; i < ptr->%s_len; i++)\n" % i.fixname) + c_file.write(" if (ptr->%s[i] != NULL) {\n" % (i.fixname)) + c_file.write(" free_%s(ptr->%s[i]);\n" % (free_func, i.fixname)) + c_file.write(" ptr->%s[i] = NULL;\n" % (i.fixname)) + c_file.write(" }\n") + c_file.write(" free(ptr->%s);\n" % i.fixname) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + c_file.write(" }\n") + + c_typ = helpers.obtainPointer(i.name, i.subtypobj, prefix) + if c_typ == "": + continue + if i.subobj is not None: + c_typ = c_typ + "_element" + c_file.write(" free_%s(ptr->%s);\n" % (c_typ, i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + else: # not array + typename = helpers.getPrefixName(i.name, prefix) + if i.typ == 'string': + c_file.write(" free(ptr->%s);\n" % (i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + elif i.typ == 'booleanPointer': + c_file.write(" free(ptr->%s);\n" % (i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + elif helpers.judgeDataPointerType(i.typ): + c_file.write(" free(ptr->%s);\n" % (i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + elif i.typ == 'object': + if i.subtypname is not None: + typename = i.subtypname + c_file.write(" if (ptr->%s != NULL) {\n" % (i.fixname)) + c_file.write(" free_%s(ptr->%s);\n" % (typename, i.fixname)) + c_file.write(" ptr->%s = NULL;\n" % (i.fixname)) + c_file.write(" }\n") + c_file.write(" free(ptr);\n") + c_file.write("}\n\n") + + +def sourceReflection(structs, schema_info, c_file, root_typ): + """ + Description: reflect code + Interface: None + History: 2019-06-17 + """ + c_file.write("// Generated from %s. Do not edit!\n" % (schema_info.name.basename)) + c_file.write("#ifndef _GNU_SOURCE\n") + c_file.write("#define _GNU_SOURCE\n") + c_file.write("#endif\n") + c_file.write('#include \n') + c_file.write('#include \n') + c_file.write('#include "securec.h"\n') + c_file.write('#include "%s"\n\n' % schema_info.header.basename) + + for i in structs: + appendCCode(i, c_file, schema_info.prefix) + + obtainCEpilogue(c_file, schema_info.prefix, root_typ) + + +def obtainCEpilogue(c_file, prefix, typ): + """ + Description: generate c language epilogue + Interface: None + History: 2019-06-17 + """ + if typ != 'array' and typ != 'object': + return + + if typ == 'array': + c_file.write("""\n +%s_element **make_%s(yajl_val tree, const struct parser_context *ctx, parser_error *err, size_t *len) { + %s_element **ptr = NULL; + size_t i, alen; + if (tree == NULL || err == NULL || !len || YAJL_GET_ARRAY(tree) == NULL) + return NULL; + *err = 0; + alen = YAJL_GET_ARRAY(tree)->len; + if (alen == 0) + return NULL; + ptr = safe_malloc((alen + 1) * sizeof(%s_element *)); + for (i = 0; i < alen; i++) { + yajl_val val = YAJL_GET_ARRAY(tree)->values[i]; + ptr[i] = make_%s_element(val, ctx, err); + if (ptr[i] == NULL) { + free_%s(ptr, alen); + return NULL; + } + } + *len = alen; + return ptr; +} +""" % (prefix, prefix, prefix, prefix, prefix, prefix)) + c_file.write("""\n +void free_%s(%s_element **ptr, size_t len) { + size_t i; + + if (ptr == NULL || len == 0) + return; + + for (i = 0; i < len; i++) { + if (ptr[i] != NULL) { + free_%s_element(ptr[i]); + ptr[i] = NULL; + } + } + free(ptr); +} +""" % (prefix, prefix, prefix)) + c_file.write("""\n +yajl_gen_status gen_%s(yajl_gen g, const %s_element **ptr, size_t len, const struct parser_context *ctx, + parser_error *err) { + yajl_gen_status stat; + size_t i; + *err = 0; + stat = yajl_gen_array_open((yajl_gen)g); + if (yajl_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN(stat, err); + for (i = 0; i < len; i++) { + stat = gen_%s_element(g, ptr[i], ctx, err); + if (yajl_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN(stat, err); + } + stat = yajl_gen_array_close((yajl_gen)g); + if (yajl_gen_status_ok != stat) + GEN_SET_ERROR_AND_RETURN(stat, err); + return yajl_gen_status_ok; +} +""" % (prefix, prefix, prefix)) + + c_file.write(""" +%s%s*%s_parse_file(const char *filename, const struct parser_context *ctx, parser_error *err%s) { + %s%s*ptr = NULL;""" % (prefix, ' ' if typ == 'object' else '_element *', \ + prefix, '' if typ == 'object' else ', size_t *len', \ + prefix, ' ' if typ == 'object' else '_element *')) + + c_file.write(""" + size_t filesize; + char *content = NULL; + + if (filename == NULL || err == NULL) + return NULL; + + *err = NULL; + content = read_file(filename, &filesize); + if (content == NULL) { + if (asprintf(err, "cannot read the file: %%s", filename) < 0) + *err = safe_strdup("error allocating memory"); + return NULL; + } + ptr = %s_parse_data(content, ctx, err%s); + free(content); + return ptr; +} +""" % (prefix, '' if typ == 'object' else ', len')) + + c_file.write(""" +%s%s*%s_parse_file_stream(FILE *stream, const struct parser_context *ctx, parser_error *err%s) { + %s%s*ptr = NULL;""" % (prefix, ' ' if typ == 'object' else '_element *', \ + prefix, '' if typ == 'object' else ', size_t *len', \ + prefix, ' ' if typ == 'object' else '_element *')) + + c_file.write(""" + size_t filesize; + char *content = NULL ; + + if (stream == NULL || err == NULL) + return NULL; + + *err = NULL; + content = fread_file(stream, &filesize); + if (content == NULL) { + *err = safe_strdup("cannot read the file"); + return NULL; + } + ptr = %s_parse_data(content, ctx, err%s); + free(content); + return ptr; +} +""" % (prefix, '' if typ == 'object' else ', len')) + + c_file.write(""" +%s%s*%s_parse_data(const char *jsondata, const struct parser_context *ctx, parser_error *err%s) { + %s%s*ptr = NULL;""" % (prefix, ' ' if typ == 'object' else '_element *', \ + prefix, '' if typ == 'object' else ', size_t *len', \ + prefix, ' ' if typ == 'object' else '_element *')) + + c_file.write(""" + yajl_val tree; + char errbuf[1024]; + struct parser_context tmp_ctx = { 0 }; + + if (jsondata == NULL || err == NULL) + return NULL; + + *err = NULL; + if (ctx == NULL) { + ctx = (const struct parser_context *)(&tmp_ctx); + } + tree = yajl_tree_parse(jsondata, errbuf, sizeof(errbuf)); + if (tree == NULL) { + if (asprintf(err, "cannot parse the data: %%s", errbuf) < 0) + *err = safe_strdup("error allocating memory"); + return NULL; + } + ptr = make_%s(tree, ctx, err%s); + yajl_tree_free(tree); + return ptr; +} +""" % (prefix, '' if typ == 'object' else ', len')) + + c_file.write("char *%s_generate_json(const %s%s*ptr%s, const struct parser_context *ctx," \ + " parser_error *err) {" % (prefix, prefix, \ + ' ' if typ == 'object' else '_element *', \ + '' if typ == 'object' else ', size_t len')) + + c_file.write(""" + yajl_gen g = NULL; + struct parser_context tmp_ctx = { 0 }; + const unsigned char *gen_buf = NULL; + char *json_buf = NULL; + size_t gen_len = 0; + + if (ptr == NULL || err == NULL) + return NULL; + + *err = NULL; + if (ctx == NULL) { + ctx = (const struct parser_context *)(&tmp_ctx); + } + + if (!json_gen_init(&g, ctx)) { + *err = safe_strdup("Json_gen init failed"); + goto out; + } + if (yajl_gen_status_ok != gen_%s(g, ptr%s, ctx, err)) { + if (*err == NULL) + *err = safe_strdup("Failed to generate json"); + goto free_out; + } + yajl_gen_get_buf(g, &gen_buf, &gen_len); + if (gen_buf == NULL) { + *err = safe_strdup("Error to get generated json"); + goto free_out; + } + + json_buf = safe_malloc(gen_len + 1); + if (memcpy_s(json_buf, gen_len + 1, gen_buf, gen_len) != EOK) { + *err = safe_strdup("Error to memcpy json"); + free(json_buf); + json_buf = NULL; + goto free_out; + } + json_buf[gen_len] = '\\0'; + +free_out: + yajl_gen_clear(g); + yajl_gen_free(g); +out: + return json_buf; +} + +""" % (prefix, '' if typ == 'object' else ', len')) diff --git a/src/liblcrc.c b/src/liblcrc.c new file mode 100644 index 0000000..9987058 --- /dev/null +++ b/src/liblcrc.c @@ -0,0 +1,1383 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container lcrc library functions + ******************************************************************************/ +#include +#include +#include +#include + +#include "liblcrc.h" +#include "log.h" +#include "pack_config.h" +#include "utils.h" +#include "securec.h" + +/* lcrc filters free */ +void lcrc_filters_free(struct lcrc_filters *filters) +{ + size_t i; + if (filters == NULL) { + return; + } + for (i = 0; i < filters->len; i++) { + free(filters->keys[i]); + filters->keys[i] = NULL; + free(filters->values[i]); + filters->values[i] = NULL; + } + free(filters->keys); + filters->keys = NULL; + free(filters->values); + filters->values = NULL; + free(filters); +} + +struct lcrc_filters *lcrc_filters_parse_args(const char **array, size_t len) +{ + struct lcrc_filters *filters = NULL; + size_t i; + + if (len == 0 || array == NULL) { + return NULL; + } + + if (len > (SIZE_MAX / sizeof(char *))) { + ERROR("Too many filters"); + return NULL; + } + + filters = util_common_calloc_s(sizeof(*filters)); + if (filters == NULL) { + ERROR("Out of memory"); + return NULL; + } + + filters->keys = util_common_calloc_s(sizeof(char *) * len); + if (filters->keys == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + filters->values = util_common_calloc_s(sizeof(char *) * len); + if (filters->values == NULL) { + free(filters->keys); + filters->keys = NULL; + ERROR("Out of memory"); + goto cleanup; + } + + for (i = 0; i < len; i++) { + char *valuepos = NULL; + char *copy = NULL; + char *lowerkey = NULL; + if (strlen(array[i]) == 0) { + continue; + } + copy = util_strdup_s(array[i]); + valuepos = strchr(copy, '='); + if (valuepos == NULL) { + COMMAND_ERROR("Bad format of filter '%s', (expected name=value)", copy); + free(copy); + goto cleanup; + } + *valuepos++ = '\0'; + filters->values[filters->len] = util_strdup_s(util_trim_space(valuepos)); + lowerkey = strings_to_lower(util_trim_space(copy)); + free(copy); + if (lowerkey == NULL) { + free(filters->values[filters->len]); + filters->values[filters->len] = NULL; + ERROR("Out of memory"); + goto cleanup; + } + filters->keys[filters->len] = lowerkey; + filters->len++; + } + return filters; +cleanup: + lcrc_filters_free(filters); + return NULL; +} + +/* lcrc container info free */ +void lcrc_container_info_free(struct lcrc_container_info *info) +{ + if (info == NULL) { + return; + } + + free(info->id); + info->id = NULL; + free(info); +} + +/* lcrc version request free */ +void lcrc_version_request_free(struct lcrc_version_request *request) +{ + if (request == NULL) { + return; + } + free(request); +} + +/* lcrc version response free */ +void lcrc_version_response_free(struct lcrc_version_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response->version); + response->version = NULL; + + free(response->git_commit); + response->git_commit = NULL; + + free(response->build_time); + response->build_time = NULL; + + free(response->root_path); + response->root_path = NULL; + + free(response); +} + +/* lcrc info request free */ +void lcrc_info_request_free(struct lcrc_info_request *request) +{ + if (request == NULL) { + return; + } + free(request); +} + +/* lcrc info response free */ +void lcrc_info_response_free(struct lcrc_info_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response->version); + response->version = NULL; + + free(response->kversion); + response->kversion = NULL; + + free(response->os_type); + response->os_type = NULL; + + free(response->architecture); + response->architecture = NULL; + + free(response->nodename); + response->nodename = NULL; + + free(response->operating_system); + response->operating_system = NULL; + + free(response->cgroup_driver); + response->cgroup_driver = NULL; + + free(response->logging_driver); + response->logging_driver = NULL; + + free(response->huge_page_size); + response->huge_page_size = NULL; + + free(response->isulad_root_dir); + response->isulad_root_dir = NULL; + + free(response->http_proxy); + response->http_proxy = NULL; + + free(response->https_proxy); + response->https_proxy = NULL; + + free(response->no_proxy); + response->no_proxy = NULL; + free(response); +} + +void lcrc_ns_change_files_free(lcrc_host_config_t *hostconfig) +{ + if (hostconfig == NULL) { + return; + } + + util_free_array(hostconfig->ns_change_files); + hostconfig->ns_change_files = NULL; + hostconfig->ns_change_files_len = 0; +} + +void lcrc_host_config_storage_opts_free(lcrc_host_config_t *hostconfig) +{ + if (hostconfig == NULL) { + return; + } + + free_json_map_string_string(hostconfig->storage_opts); + hostconfig->storage_opts = NULL; +} + +void lcrc_host_config_sysctl_free(lcrc_host_config_t *hostconfig) +{ + if (hostconfig == NULL) { + return; + } + + free_json_map_string_string(hostconfig->sysctls); + hostconfig->sysctls = NULL; +} + +/* lcrc host config free */ +void lcrc_host_config_free(lcrc_host_config_t *hostconfig) +{ + if (hostconfig == NULL) { + return; + } + + util_free_array(hostconfig->cap_add); + hostconfig->cap_add = NULL; + hostconfig->cap_add_len = 0; + + util_free_array(hostconfig->cap_drop); + hostconfig->cap_drop = NULL; + hostconfig->cap_drop_len = 0; + + free_json_map_string_string(hostconfig->storage_opts); + hostconfig->storage_opts = NULL; + + free_json_map_string_string(hostconfig->sysctls); + hostconfig->sysctls = NULL; + + util_free_array(hostconfig->devices); + hostconfig->devices = NULL; + hostconfig->devices_len = 0; + + util_free_array(hostconfig->hugetlbs); + hostconfig->hugetlbs = NULL; + hostconfig->hugetlbs_len = 0; + + free(hostconfig->network_mode); + hostconfig->network_mode = NULL; + + free(hostconfig->ipc_mode); + hostconfig->ipc_mode = NULL; + + free(hostconfig->pid_mode); + hostconfig->pid_mode = NULL; + + free(hostconfig->uts_mode); + hostconfig->uts_mode = NULL; + + free(hostconfig->userns_mode); + hostconfig->userns_mode = NULL; + + free(hostconfig->user_remap); + hostconfig->user_remap = NULL; + + util_free_array(hostconfig->ulimits); + hostconfig->ulimits = NULL; + hostconfig->ulimits_len = 0; + + free(hostconfig->restart_policy); + hostconfig->restart_policy = NULL; + + free(hostconfig->host_channel); + hostconfig->host_channel = NULL; + + free(hostconfig->hook_spec); + hostconfig->hook_spec = NULL; + + free(hostconfig->env_target_file); + hostconfig->env_target_file = NULL; + + free(hostconfig->cgroup_parent); + hostconfig->cgroup_parent = NULL; + + util_free_array(hostconfig->binds); + hostconfig->binds = NULL; + hostconfig->binds_len = 0; + + util_free_array(hostconfig->blkio_weight_device); + hostconfig->blkio_weight_device = NULL; + hostconfig->blkio_weight_device_len = 0; + + container_cgroup_resources_free(hostconfig->cr); + hostconfig->cr = NULL; + + free(hostconfig); +} + +/* lcrc container config free */ +void lcrc_container_config_free(lcrc_container_config_t *config) +{ + if (config == NULL) { + return; + } + + util_free_array(config->env); + config->env = NULL; + config->env_len = 0; + + free(config->hostname); + config->hostname = NULL; + + free(config->user); + config->user = NULL; + + util_free_array(config->mounts); + config->mounts = NULL; + config->mounts_len = 0; + + util_free_array(config->cmd); + config->cmd = NULL; + config->cmd_len = 0; + + free(config->entrypoint); + config->entrypoint = NULL; + + free(config->log_file); + config->log_file = NULL; + + free(config->log_file_size); + config->log_file_size = NULL; + + free_json_map_string_string(config->annotations); + config->annotations = NULL; + + free(config->workdir); + config->workdir = NULL; + + free(config); +} + +/* lcrc create request free */ +void lcrc_create_request_free(struct lcrc_create_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request->rootfs); + request->rootfs = NULL; + + free(request->image); + request->image = NULL; + + free(request->runtime); + request->runtime = NULL; + + lcrc_host_config_free(request->hostconfig); + request->hostconfig = NULL; + + lcrc_container_config_free(request->config); + request->config = NULL; + free(request); +} + +/* lcrc create response free */ +void lcrc_create_response_free(struct lcrc_create_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response->id); + response->id = NULL; + + free(response); +} + +/* lcrc start request free */ +void lcrc_start_request_free(struct lcrc_start_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request->stdin); + request->stdin = NULL; + + free(request->stdout); + request->stdout = NULL; + + free(request->stderr); + request->stderr = NULL; + + free(request); +} + +/* lcrc start response free */ +void lcrc_start_response_free(struct lcrc_start_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc_top_request_free */ +void lcrc_top_request_free(struct lcrc_top_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + if (request->ps_argc && request->ps_args != NULL) { + int i; + for (i = 0; i < request->ps_argc; i++) { + free(request->ps_args[i]); + request->ps_args[i] = NULL; + } + free(request->ps_args); + request->ps_args = NULL; + request->ps_argc = 0; + } + + free(request); +} +/* lcrc_top_response_free */ +void lcrc_top_response_free(struct lcrc_top_response *response) +{ + if (response == NULL) { + return; + } + + free(response->titles); + response->titles = NULL; + + if (response->processes_len && response->processes != NULL) { + size_t i; + for (i = 0; i < response->processes_len; i++) { + free(response->processes[i]); + response->processes[i] = NULL; + } + free(response->processes); + response->processes = NULL; + response->processes_len = 0; + } + + free(response); +} + +/* lcrc stop request free */ +void lcrc_stop_request_free(struct lcrc_stop_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request); +} + +/* lcrc stop response free */ +void lcrc_stop_response_free(struct lcrc_stop_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc restart request free */ +void lcrc_restart_request_free(struct lcrc_restart_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request); +} + +/* lcrc restart response free */ +void lcrc_restart_response_free(struct lcrc_restart_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc delete request free */ +void lcrc_delete_request_free(struct lcrc_delete_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request); +} + +/* lcrc delete response free */ +void lcrc_delete_response_free(struct lcrc_delete_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response->name); + response->name = NULL; + + free(response); +} + +/* lcrc list request free */ +void lcrc_list_request_free(struct lcrc_list_request *request) +{ + if (request == NULL) { + return; + } + + free(request); +} + +/* lcrc list response free */ +void lcrc_list_response_free(struct lcrc_list_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + if (response->container_num > 0 && response->container_summary != NULL) { + int i; + for (i = 0; i < (int)response->container_num; i++) { + if (response->container_summary[i]->id != NULL) { + free(response->container_summary[i]->id); + response->container_summary[i]->id = NULL; + } + if (response->container_summary[i]->name != NULL) { + free(response->container_summary[i]->name); + response->container_summary[i]->name = NULL; + } + if (response->container_summary[i]->runtime != NULL) { + free(response->container_summary[i]->runtime); + response->container_summary[i]->runtime = NULL; + } + if (response->container_summary[i]->image != NULL) { + free(response->container_summary[i]->image); + response->container_summary[i]->image = NULL; + } + if (response->container_summary[i]->command != NULL) { + free(response->container_summary[i]->command); + response->container_summary[i]->command = NULL; + } + if (response->container_summary[i]->startat != NULL) { + free(response->container_summary[i]->startat); + response->container_summary[i]->startat = NULL; + } + if (response->container_summary[i]->finishat != NULL) { + free(response->container_summary[i]->finishat); + response->container_summary[i]->finishat = NULL; + } + if (response->container_summary[i]->health_state != NULL) { + free(response->container_summary[i]->health_state); + response->container_summary[i]->health_state = NULL; + } + free(response->container_summary[i]); + response->container_summary[i] = NULL; + } + free(response->container_summary); + response->container_summary = NULL; + } + free(response); +} + +/* lcrc exec request free */ +void lcrc_exec_request_free(struct lcrc_exec_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request->stdout); + request->stdout = NULL; + + free(request->stdin); + request->stdin = NULL; + + free(request->stderr); + request->stderr = NULL; + + if (request->argc && request->argv != NULL) { + int i; + for (i = 0; i < request->argc; i++) { + free(request->argv[i]); + request->argv[i] = NULL; + } + free(request->argv); + request->argv = NULL; + request->argc = 0; + } + if (request->env_len && request->env != NULL) { + size_t j; + for (j = 0; j < request->env_len; j++) { + free(request->env[j]); + request->env[j] = NULL; + } + free(request->env); + request->env = NULL; + request->env_len = 0; + } + free(request); +} + +/* lcrc exec response free */ +void lcrc_exec_response_free(struct lcrc_exec_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc attach request free */ +void lcrc_attach_request_free(struct lcrc_attach_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request->stderr); + request->stderr = NULL; + + free(request->stdout); + request->stdout = NULL; + + free(request->stdin); + request->stdin = NULL; + + free(request); +} + +/* lcrc attach response free */ +void lcrc_attach_response_free(struct lcrc_attach_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc pause request free */ +void lcrc_pause_request_free(struct lcrc_pause_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + free(request); +} + +/* lcrc pause response free */ +void lcrc_pause_response_free(struct lcrc_pause_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc resume request free */ +void lcrc_resume_request_free(struct lcrc_resume_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + free(request); +} + +/* lcrc resume response free */ +void lcrc_resume_response_free(struct lcrc_resume_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc container conf request free */ +void lcrc_container_conf_request_free(struct lcrc_container_conf_request *req) +{ + if (req == NULL) { + return; + } + + free(req->name); + req->name = NULL; + + free(req); +} + +/* lcrc container conf response free */ +void lcrc_container_conf_response_free(struct lcrc_container_conf_response *resp) +{ + if (resp == NULL) { + return; + } + + free(resp->errmsg); + resp->errmsg = NULL; + + free(resp->container_logpath); + resp->container_logpath = NULL; + + free(resp->container_logsize); + resp->container_logsize = NULL; + + free(resp); +} + +/* lcrc kill request free */ +void lcrc_kill_request_free(struct lcrc_kill_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + free(request); +} + +/* lcrc kill response free */ +void lcrc_kill_response_free(struct lcrc_kill_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + free(response); +} + +/* lcrc update config free */ +void lcrc_update_config_free(lcrc_update_config_t *config) +{ + if (config == NULL) { + return; + } + + free(config->restart_policy); + config->restart_policy = NULL; + + container_cgroup_resources_free(config->cr); + config->cr = NULL; + + free(config); +} + +/* lcrc update request free */ +void lcrc_update_request_free(struct lcrc_update_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + lcrc_update_config_free(request->updateconfig); + request->updateconfig = NULL; + + free(request); +} + +/* lcrc update response free */ +void lcrc_update_response_free(struct lcrc_update_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc stats request free */ +void lcrc_stats_request_free(struct lcrc_stats_request *request) +{ + if (request == NULL) { + return; + } + + free(request->runtime); + request->runtime = NULL; + free(request); +} + +/* lcrc stats response free */ +void lcrc_stats_response_free(struct lcrc_stats_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + if (response->container_stats != NULL && response->container_num) { + int i; + for (i = 0; i < (int)response->container_num; i++) { + free(response->container_stats[i].id); + response->container_stats[i].id = NULL; + } + free(response->container_stats); + response->container_stats = NULL; + } + free(response); +} + +/* lcrc events request free */ +void lcrc_events_request_free(struct lcrc_events_request *request) +{ + if (request == NULL) { + return; + } + + free(request->id); + request->id = NULL; + + free(request); +} + +/* lcrc events response free */ +void lcrc_events_response_free(struct lcrc_events_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +void lcrc_copy_from_container_request_free(struct lcrc_copy_from_container_request *request) +{ + if (request == NULL) { + return; + } + + free(request->id); + request->id = NULL; + free(request->runtime); + request->runtime = NULL; + free(request->srcpath); + request->srcpath = NULL; + + free(request); +} + +void lcrc_copy_from_container_response_free(struct lcrc_copy_from_container_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + free_container_path_stat(response->stat); + response->stat = NULL; + free(response); +} + +void lcrc_copy_to_container_request_free(struct lcrc_copy_to_container_request *request) +{ + if (request == NULL) { + return; + } + + free(request->id); + request->id = NULL; + free(request->runtime); + request->runtime = NULL; + free(request->srcpath); + request->srcpath = NULL; + free(request->srcrebase); + request->srcrebase = NULL; + free(request->dstpath); + request->dstpath = NULL; + + free(request); +} + +void lcrc_copy_to_container_response_free(struct lcrc_copy_to_container_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc inspect request free */ +void lcrc_inspect_request_free(struct lcrc_inspect_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request); +} + +/* lcrc inspect response free */ +void lcrc_inspect_response_free(struct lcrc_inspect_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response->json); + response->json = NULL; + + free(response); +} + +/* lcrc wait request free */ +void lcrc_wait_request_free(struct lcrc_wait_request *request) +{ + if (request == NULL) { + return; + } + + free(request->id); + request->id = NULL; + free(request); +} + +/* lcrc wait response free */ +void lcrc_wait_response_free(struct lcrc_wait_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc health check request free */ +void lcrc_health_check_request_free(struct lcrc_health_check_request *request) +{ + if (request == NULL) { + return; + } + + free(request->service); + request->service = NULL; + + free(request); +} + +/* lcrc health check response free */ +void lcrc_health_check_response_free(struct lcrc_health_check_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc create image request free */ +void lcrc_create_image_request_free(struct lcrc_create_image_request *request) +{ + if (request == NULL) { + return; + } + + free(request->image_info.imageref); + request->image_info.imageref = NULL; + + free(request->image_info.type); + request->image_info.type = NULL; + + free(request->image_info.digest); + request->image_info.digest = NULL; + + free(request); + return; +} + +/* lcrc create image response free */ +void lcrc_create_image_response_free(struct lcrc_create_image_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response->image_info.imageref); + response->image_info.imageref = NULL; + + free(response->image_info.type); + response->image_info.type = NULL; + + free(response->image_info.digest); + response->image_info.digest = NULL; + + free(response); + return; +} + +/* lcrc images list free */ +void lcrc_images_list_free(size_t images_num, struct lcrc_image_info *images_list) +{ + int i = 0; + struct lcrc_image_info *in = NULL; + + if (images_num == 0 || images_list == NULL) { + return; + } + + for (i = 0, in = images_list; i < (int)images_num; i++, in++) { + free(in->imageref); + free(in->type); + free(in->digest); + } + + free(images_list); + return; +} + +/* lcrc list images request free */ +void lcrc_list_images_request_free(struct lcrc_list_images_request *request) +{ + if (request == NULL) { + return; + } + + free(request); + return; +} + +/* lcrc list images response free */ +void lcrc_list_images_response_free(struct lcrc_list_images_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + lcrc_images_list_free(response->images_num, response->images_list); + response->images_num = 0; + response->images_list = NULL; + free(response); +} + +/* lcrc rmi request free */ +void lcrc_rmi_request_free(struct lcrc_rmi_request *request) +{ + if (request == NULL) { + return; + } + + free(request->image_name); + request->image_name = NULL; + + free(request); + return; +} + +/* lcrc rmi response free */ +void lcrc_rmi_response_free(struct lcrc_rmi_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); + return; +} + +/* lcrc pull response free */ +void lcrc_pull_request_free(struct lcrc_pull_request *request) +{ + if (request == NULL) { + return; + } + + free(request->image_name); + request->image_name = NULL; + + free(request); + return; +} + +/* lcrc pull response free */ +void lcrc_pull_response_free(struct lcrc_pull_response *response) +{ + if (response == NULL) { + return; + } + + free(response->image_ref); + response->image_ref = NULL; + + free(response->errmsg); + response->errmsg = NULL; + free(response); + return; +} + +/* lcrc load request free */ +void lcrc_load_request_free(struct lcrc_load_request *request) +{ + if (request == NULL) { + return; + } + + free(request->file); + request->file = NULL; + + free(request->type); + request->type = NULL; + + free(request->tag); + request->tag = NULL; + + free(request); + return; +} + +/* lcrc load response free */ +void lcrc_load_response_free(struct lcrc_load_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); + return; +} + +/* lcrc login response free */ +void lcrc_login_response_free(struct lcrc_login_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); + return; +} + +/* lcrc logout response free */ +void lcrc_logout_response_free(struct lcrc_logout_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); + return; +} + +/* lcrc export request free */ +void lcrc_export_request_free(struct lcrc_export_request *request) +{ + if (request == NULL) { + return; + } + + free(request->name); + request->name = NULL; + + free(request->file); + request->file = NULL; + + free(request); +} + +/* lcrc export response free */ +void lcrc_export_response_free(struct lcrc_export_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc rename request free */ +void lcrc_rename_request_free(struct lcrc_rename_request *request) +{ + if (request == NULL) { + return; + } + + free(request->old_name); + request->old_name = NULL; + + free(request->new_name); + request->new_name = NULL; + + free(request); +} + +/* lcrc rename response free */ +void lcrc_rename_response_free(struct lcrc_rename_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +/* lcrc logs request free */ +void lcrc_logs_request_free(struct lcrc_logs_request *request) +{ + if (request == NULL) { + return; + } + + free(request->id); + request->id = NULL; + free(request->runtime); + request->runtime = NULL; + free(request->since); + request->since = NULL; + free(request->until); + request->until = NULL; + + free(request); +} + +/* lcrc logs response free */ +void lcrc_logs_response_free(struct lcrc_logs_response *response) +{ + if (response == NULL) { + return; + } + + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + diff --git a/src/liblcrc.h b/src/liblcrc.h new file mode 100644 index 0000000..7b0decb --- /dev/null +++ b/src/liblcrc.h @@ -0,0 +1,842 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container lcrc library definition + ******************************************************************************/ +#ifndef __LIB_LCRC_H +#define __LIB_LCRC_H + +#include +#include +#include + +#include "container_def.h" +#include "container_path_stat.h" +#include "json_common.h" +#include "console.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct lcrc_filters { + char **keys; + char **values; + size_t len; +}; + +typedef struct lcrc_container_config { + char **env; + size_t env_len; + + char *hostname; + + char *user; + + bool attach_stdin; + + bool attach_stdout; + + bool attach_stderr; + + bool open_stdin; + + bool tty; + + bool readonly; + + bool all_devices; + + bool system_container; + char *ns_change_opt; + + char **mounts; + size_t mounts_len; + + char *entrypoint; + + char **cmd; + size_t cmd_len; + + char *log_file; + + char *log_file_size; + + unsigned int log_file_rotate; + + json_map_string_string *annotations; + + char *workdir; + + char *health_cmd; + + int64_t health_interval; + + int health_retries; + + int64_t health_timeout; + + int64_t health_start_period; + + bool no_healthcheck; + + bool exit_on_unhealthy; + + char **accel; + size_t accel_len; +} lcrc_container_config_t; + +typedef struct lcrc_host_config { + char **devices; + size_t devices_len; + + char **hugetlbs; + size_t hugetlbs_len; + + char **group_add; + size_t group_add_len; + + char *network_mode; + + char *ipc_mode; + + char *pid_mode; + + char *uts_mode; + + char *userns_mode; + + char *user_remap; + + char **ulimits; + size_t ulimits_len; + + char *restart_policy; + + char *host_channel; + + char **cap_add; + size_t cap_add_len; + + char **cap_drop; + size_t cap_drop_len; + + json_map_string_string *storage_opts; + + json_map_string_string *sysctls; + + char **dns; + size_t dns_len; + + char **dns_options; + size_t dns_options_len; + + char **dns_search; + size_t dns_search_len; + + char **extra_hosts; + size_t extra_hosts_len; + + char *hook_spec; + + char **binds; + size_t binds_len; + + char **blkio_weight_device; + size_t blkio_weight_device_len; + + char **blkio_throttle_read_bps_device; + size_t blkio_throttle_read_bps_device_len; + + char **blkio_throttle_write_bps_device; + size_t blkio_throttle_write_bps_device_len; + + bool privileged; + bool system_container; + char **ns_change_files; + size_t ns_change_files_len; + bool auto_remove; + + bool oom_kill_disable; + + int64_t shm_size; + + bool readonly_rootfs; + + char *env_target_file; + + char *cgroup_parent; + + container_cgroup_resources_t *cr; + + char **security; + size_t security_len; +} lcrc_host_config_t; + +struct lcrc_create_request { + char *name; + char *rootfs; + char *image; + char *runtime; + lcrc_host_config_t *hostconfig; + lcrc_container_config_t *config; +}; + +struct lcrc_create_response { + char *id; + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_start_request { + char *name; + char *stdin; + bool attach_stdin; + char *stdout; + bool attach_stdout; + char *stderr; + bool attach_stderr; +}; + +struct lcrc_start_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_top_request { + char *name; + int ps_argc; + char **ps_args; +}; + +struct lcrc_top_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; + char *titles; + char **processes; + size_t processes_len; +}; + +struct lcrc_stop_request { + char *name; + bool force; + int timeout; +}; + +struct lcrc_stop_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_restart_request { + char *name; + unsigned int timeout; +}; + +struct lcrc_restart_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_kill_request { + char *name; + uint32_t signal; +}; + +struct lcrc_kill_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_delete_request { + char *name; + bool force; +}; + +struct lcrc_delete_response { + char *name; + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_pause_request { + char *name; +}; + +struct lcrc_pause_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_resume_request { + char *name; +}; + +struct lcrc_resume_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_container_info { + char *id; + bool has_pid; + uint32_t pid; + Container_Status status; + uint64_t pids_current; + // CPU usage + uint64_t cpu_use_nanos; + uint64_t cpu_system_use; + uint32_t online_cpus; + // BlkIO usage + uint64_t blkio_read; + uint64_t blkio_write; + // Memory usage + uint64_t mem_used; + uint64_t mem_limit; + // Kernel Memory usage + uint64_t kmem_used; + uint64_t kmem_limit; +}; + +struct lcrc_inspect_request { + char *name; + bool bformat; + int timeout; +}; + +struct lcrc_inspect_response { + uint32_t cc; + uint32_t server_errono; + char *json; + char *errmsg; +}; + +struct lcrc_list_request { + struct lcrc_filters *filters; + bool all; +}; + +struct lcrc_container_summary_info { + char *id; + char *name; + uint32_t has_pid; + uint32_t pid; + Container_Status status; + char *image; + char *command; + uint32_t exit_code; + uint32_t restart_count; + char *startat; + char *finishat; + char *runtime; + char *health_state; +}; + +struct lcrc_list_response { + uint32_t cc; + uint32_t server_errono; + size_t container_num; + struct lcrc_container_summary_info **container_summary; + char *errmsg; +}; + +struct lcrc_stats_request { + char *runtime; + char **containers; + size_t containers_len; + bool all; +}; + +struct lcrc_stats_response { + uint32_t cc; + uint32_t server_errono; + size_t container_num; + struct lcrc_container_info *container_stats; + char *errmsg; +}; + +struct lcrc_events_request { + container_events_callback_t cb; + bool storeonly; + char *id; + types_timestamp_t since; + types_timestamp_t until; +}; + +struct lcrc_events_response { + uint32_t server_errono; + uint32_t cc; + char *errmsg; +}; + +struct lcrc_copy_from_container_request { + char *id; + char *runtime; + char *srcpath; +}; + +struct lcrc_copy_from_container_response { + uint32_t server_errono; + uint32_t cc; + char *errmsg; + container_path_stat *stat; + struct io_read_wrapper reader; +}; + +struct lcrc_copy_to_container_request { + char *id; + char *runtime; + char *srcpath; + char *srcrebase; + bool srcisdir; + char *dstpath; + struct io_read_wrapper reader; +}; + +struct lcrc_copy_to_container_response { + uint32_t server_errono; + uint32_t cc; + char *errmsg; +}; + +struct lcrc_logs_request { + char *id; + char *runtime; + + char *since; + char *until; + bool timestamps; + bool follow; + int64_t tail; + bool details; +}; + +struct lcrc_logs_response { + uint32_t server_errono; + uint32_t cc; + char *errmsg; +}; + +struct lcrc_wait_request { + char *id; + uint32_t condition; +}; + +struct lcrc_wait_response { + int exit_code; + uint32_t server_errono; + uint32_t cc; + char *errmsg; +}; + +struct lcrc_exec_request { + char *name; + bool tty; + bool open_stdin; + bool attach_stdin; + bool attach_stdout; + bool attach_stderr; + char *stdin; + char *stdout; + char *stderr; + int argc; + char **argv; + size_t env_len; + char **env; + int64_t timeout; +}; + +struct lcrc_exec_response { + uint32_t cc; + uint32_t server_errono; + uint32_t pid; + uint32_t exit_code; + char *errmsg; +}; + +struct lcrc_attach_request { + char *name; + char *stdin; + char *stdout; + char *stderr; + bool attach_stdin; + bool attach_stdout; + bool attach_stderr; +}; + +struct lcrc_attach_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_health_check_request { + char *service; +}; + +struct lcrc_health_check_response { + Health_Serving_Status health_status; + uint32_t cc; + char *errmsg; +}; + +struct lcrc_version_request { + char unuseful; +}; + +struct lcrc_version_response { + uint32_t cc; + uint32_t server_errono; + char *version; + char *git_commit; + char *build_time; + char *root_path; + char *errmsg; +}; + +struct lcrc_info_request { + char unuseful; +}; + +struct lcrc_info_response { + uint32_t cc; + uint32_t server_errono; + char *version; + char *kversion; + char *os_type; + char *architecture; + char *nodename; + char *operating_system; + char *cgroup_driver; + char *logging_driver; + char *huge_page_size; + char *isulad_root_dir; + char *http_proxy; + char *https_proxy; + char *no_proxy; + uint32_t total_mem; + uint32_t containers_num; + uint32_t c_running; + uint32_t c_paused; + uint32_t c_stopped; + uint32_t images_num; + uint32_t cpus; + char *errmsg; +}; + +struct lcrc_container_conf_request { + char *name; +}; + +struct lcrc_container_conf_response { + uint32_t cc; + uint32_t server_errono; + char *container_logpath; + uint32_t container_logrotate; + char *container_logsize; + char *errmsg; +}; + +typedef struct lcrc_update_config { + char *restart_policy; + container_cgroup_resources_t *cr; +} lcrc_update_config_t; + +struct lcrc_update_request { + char *name; + lcrc_update_config_t *updateconfig; +}; + +struct lcrc_update_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_image_info { + char *imageref; + char *type; + char *digest; + int64_t created; /* seconds */ + int32_t created_nanos; + int64_t size; /*Bytes*/ +}; + +struct lcrc_create_image_request { + struct lcrc_image_info image_info; +}; + +struct lcrc_create_image_response { + uint32_t cc; + uint32_t server_errono; + struct lcrc_image_info image_info; + char *errmsg; +}; + +struct lcrc_list_images_request { + // unuseful definition to avoid generate empty struct which will get 0 if we call sizeof + char unuseful; +}; + +struct lcrc_list_images_response { + uint32_t cc; + uint32_t server_errono; + size_t images_num; + struct lcrc_image_info *images_list; + char *errmsg; +}; + +struct lcrc_rmi_request { + char *image_name; + bool force; +}; + +struct lcrc_rmi_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_pull_request { + char *image_name; +}; + +struct lcrc_pull_response { + char *image_ref; + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_load_request { + char *socketname; + char *file; + char *type; + char *tag; +}; + +struct lcrc_load_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_login_request { + char *socketname; + char *username; + char *password; + char *server; + char *type; +}; + +struct lcrc_login_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_logout_request { + char *socketname; + char *server; + char *type; +}; + +struct lcrc_logout_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_export_request { + char *name; + char *file; +}; + +struct lcrc_export_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +struct lcrc_rename_request { + char *old_name; + char *new_name; +}; + +struct lcrc_rename_response { + uint32_t cc; + uint32_t server_errono; + char *errmsg; +}; + +Container_Status lcrcstastr2sta(const char *state); + +struct lcrc_filters *lcrc_filters_parse_args(const char **array, size_t len); + +void lcrc_filters_free(struct lcrc_filters *filters); + +void lcrc_container_info_free(struct lcrc_container_info *info); + +void lcrc_version_request_free(struct lcrc_version_request *request); + +void lcrc_version_response_free(struct lcrc_version_response *response); + +void lcrc_info_request_free(struct lcrc_info_request *request); + +void lcrc_info_response_free(struct lcrc_info_response *response); + +void lcrc_ns_change_files_free(lcrc_host_config_t *hostconfig); + +void lcrc_host_config_storage_opts_free(lcrc_host_config_t *hostconfig); + +void lcrc_host_config_sysctl_free(lcrc_host_config_t *hostconfig); + +void lcrc_host_config_free(lcrc_host_config_t *hostconfig); + +void lcrc_container_config_free(lcrc_container_config_t *config); + +void lcrc_create_request_free(struct lcrc_create_request *request); + +void lcrc_create_response_free(struct lcrc_create_response *response); + +void lcrc_start_request_free(struct lcrc_start_request *request); + +void lcrc_start_response_free(struct lcrc_start_response *response); + +void lcrc_top_request_free(struct lcrc_top_request *request); + +void lcrc_top_response_free(struct lcrc_top_response *response); + +void lcrc_stop_request_free(struct lcrc_stop_request *request); + +void lcrc_stop_response_free(struct lcrc_stop_response *response); + +void lcrc_restart_request_free(struct lcrc_restart_request *request); + +void lcrc_restart_response_free(struct lcrc_restart_response *response); + +void lcrc_delete_request_free(struct lcrc_delete_request *request); + +void lcrc_delete_response_free(struct lcrc_delete_response *response); + +void lcrc_list_request_free(struct lcrc_list_request *request); + +void lcrc_list_response_free(struct lcrc_list_response *response); + +void lcrc_exec_request_free(struct lcrc_exec_request *request); + +void lcrc_exec_response_free(struct lcrc_exec_response *response); + +void lcrc_attach_request_free(struct lcrc_attach_request *request); + +void lcrc_attach_response_free(struct lcrc_attach_response *response); + +void lcrc_pause_request_free(struct lcrc_pause_request *request); + +void lcrc_pause_response_free(struct lcrc_pause_response *response); + +void lcrc_resume_request_free(struct lcrc_resume_request *request); + +void lcrc_resume_response_free(struct lcrc_resume_response *response); + +void lcrc_container_conf_request_free(struct lcrc_container_conf_request *req); + +void lcrc_container_conf_response_free(struct lcrc_container_conf_response *resp); + +void lcrc_kill_request_free(struct lcrc_kill_request *request); + +void lcrc_kill_response_free(struct lcrc_kill_response *response); + +void lcrc_update_config_free(lcrc_update_config_t *config); + +void lcrc_update_request_free(struct lcrc_update_request *request); + +void lcrc_update_response_free(struct lcrc_update_response *response); + +void lcrc_stats_request_free(struct lcrc_stats_request *request); + +void lcrc_stats_response_free(struct lcrc_stats_response *response); + +void lcrc_events_request_free(struct lcrc_events_request *request); + +void lcrc_events_response_free(struct lcrc_events_response *response); + +void lcrc_copy_from_container_request_free(struct lcrc_copy_from_container_request *request); + +void lcrc_copy_from_container_response_free(struct lcrc_copy_from_container_response *response); + +void lcrc_copy_to_container_request_free(struct lcrc_copy_to_container_request *request); + +void lcrc_copy_to_container_response_free(struct lcrc_copy_to_container_response *response); + +void lcrc_inspect_request_free(struct lcrc_inspect_request *request); + +void lcrc_inspect_response_free(struct lcrc_inspect_response *response); + +void lcrc_wait_request_free(struct lcrc_wait_request *request); + +void lcrc_wait_response_free(struct lcrc_wait_response *response); + +void lcrc_health_check_request_free(struct lcrc_health_check_request *request); + +void lcrc_health_check_response_free(struct lcrc_health_check_response *response); + +void lcrc_create_image_request_free(struct lcrc_create_image_request *request); + +void lcrc_create_image_response_free(struct lcrc_create_image_response *response); + +void lcrc_images_list_free(size_t images_num, struct lcrc_image_info *images_list); + +void lcrc_list_images_request_free(struct lcrc_list_images_request *request); + +void lcrc_list_images_response_free(struct lcrc_list_images_response *response); + +void lcrc_rmi_request_free(struct lcrc_rmi_request *request); + +void lcrc_rmi_response_free(struct lcrc_rmi_response *response); + +void lcrc_load_request_free(struct lcrc_load_request *request); + +void lcrc_load_response_free(struct lcrc_load_response *response); + +void lcrc_login_response_free(struct lcrc_login_response *response); + +void lcrc_logout_response_free(struct lcrc_logout_response *response); + +void lcrc_pull_request_free(struct lcrc_pull_request *request); +void lcrc_pull_response_free(struct lcrc_pull_response *response); + +void lcrc_export_request_free(struct lcrc_export_request *request); + +void lcrc_export_response_free(struct lcrc_export_response *response); + +void lcrc_rename_request_free(struct lcrc_rename_request *request); + +void lcrc_rename_response_free(struct lcrc_rename_response *response); + +void lcrc_logs_request_free(struct lcrc_logs_request *request); +void lcrc_logs_response_free(struct lcrc_logs_response *response); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/liblcrd.c b/src/liblcrd.c new file mode 100644 index 0000000..0cb38a6 --- /dev/null +++ b/src/liblcrd.c @@ -0,0 +1,233 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container lcrd functions + ******************************************************************************/ +#include +#include +#include +#include + +#include "liblcrd.h" +#include "log.h" +#include "pack_config.h" +#include "utils.h" +#include "securec.h" + +// record the errno +__thread char *g_lcrd_errmsg = NULL; + +/* lcrd container conf request free */ +void lcrd_container_conf_request_free(struct lcrd_container_conf_request *request) +{ + if (request == NULL) { + return; + } + free(request->name); + request->name = NULL; + + free(request); +} + +/* lcrd container conf response free */ +void lcrd_container_conf_response_free(struct lcrd_container_conf_response *response) +{ + if (response == NULL) { + return; + } + free(response->errmsg); + response->errmsg = NULL; + + free(response->container_logpath); + response->container_logpath = NULL; + + free(response->container_logsize); + response->container_logsize = NULL; + + free(response); +} + +/* lcrd events request free */ +void lcrd_events_request_free(struct lcrd_events_request *request) +{ + if (request == NULL) { + return; + } + if (request->id != NULL) { + free(request->id); + request->id = NULL; + } + free(request); +} + +void lcrd_copy_from_container_request_free(struct lcrd_copy_from_container_request *request) +{ + if (request == NULL) { + return; + } + free(request->id); + request->id = NULL; + free(request->runtime); + request->runtime = NULL; + free(request->srcpath); + request->srcpath = NULL; + + free(request); +} + +void lcrd_copy_from_container_response_free(struct lcrd_copy_from_container_response *response) +{ + if (response == NULL) { + return; + } + + free(response->data); + response->data = NULL; + response->data_len = 0; + + free(response); +} + +/* lcrd set error message */ +void lcrd_set_error_message(const char *format, ...) +{ + int ret = 0; + char errbuf[BUFSIZ + 1] = { 0 }; + + DAEMON_CLEAR_ERRMSG(); + va_list argp; + va_start(argp, format); + + ret = vsprintf_s(errbuf, BUFSIZ, format, argp); + va_end(argp); + if (ret < 0) { + return; + } + + g_lcrd_errmsg = util_strdup_s(errbuf); +} + +/* lcrd try set error message */ +void lcrd_try_set_error_message(const char *format, ...) +{ + int ret = 0; + + if (g_lcrd_errmsg != NULL) { + return; + } + char errbuf[BUFSIZ + 1] = { 0 }; + + va_list argp; + va_start(argp, format); + + ret = vsprintf_s(errbuf, BUFSIZ, format, argp); + va_end(argp); + if (ret < 0) { + return; + } + + g_lcrd_errmsg = util_strdup_s(errbuf); +} + +/* lcrd append error message */ +void lcrd_append_error_message(const char *format, ...) +{ + int ret = 0; + char errbuf[BUFSIZ + 1] = { 0 }; + char *result = NULL; + + va_list argp; + va_start(argp, format); + + ret = vsprintf_s(errbuf, BUFSIZ, format, argp); + va_end(argp); + if (ret < 0) { + return; + } + result = util_string_append(g_lcrd_errmsg, errbuf); + if (result == NULL) { + return; + } + if (g_lcrd_errmsg != NULL) { + free(g_lcrd_errmsg); + } + g_lcrd_errmsg = result; +} + +/* lcrd container rename request free */ +void lcrd_container_rename_request_free(struct lcrd_container_rename_request *request) +{ + if (request == NULL) { + return; + } + + free(request->old_name); + request->old_name = NULL; + free(request->new_name); + request->new_name = NULL; + + free(request); +} + +/* lcrd container rename response free */ +void lcrd_container_rename_response_free(struct lcrd_container_rename_response *response) +{ + if (response == NULL) { + return; + } + + free(response->id); + response->id = NULL; + free(response->errmsg); + response->errmsg = NULL; + + free(response); +} + +void lcrd_logs_request_free(struct lcrd_logs_request *request) +{ + if (request == NULL) { + return; + } + + free(request->id); + request->id = NULL; + free(request->runtime); + request->runtime = NULL; + free(request->since); + request->since = NULL; + free(request->until); + request->until = NULL; + free(request); +} + +void lcrd_logs_response_free(struct lcrd_logs_response *response) +{ + if (response == NULL) { + return; + } + free(response->errmsg); + response->errmsg = NULL; + free(response); +} + +void container_log_config_free(struct container_log_config *conf) +{ + if (conf == NULL) { + return; + } + free(conf->path); + conf->path = NULL; + conf->rotate = 0; + conf->size = 0; + free(conf); +} diff --git a/src/liblcrd.h b/src/liblcrd.h new file mode 100644 index 0000000..685a862 --- /dev/null +++ b/src/liblcrd.h @@ -0,0 +1,326 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container lcrd definition + ******************************************************************************/ +#ifndef __LIB_LCRD_H +#define __LIB_LCRD_H + +#include +#include +#include + +#include "container_def.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*record the lcrd errmsg*/ +extern __thread char *g_lcrd_errmsg; + +#define CONTAINER_LOG_CONFIG_KEY_FILE "log.console.file" +#define CONTAINER_LOG_CONFIG_KEY_ROTATE "log.console.filerotate" +#define CONTAINER_LOG_CONFIG_KEY_SIZE "log.console.filesize" + +#define BLOBS_PATH "blobs/sha256" +#define DIFF_LAYERS_PATH "snapshots/diff" +#define DEFAULT_TCP_HOST "tcp://localhost:2375" +#define DEFAULT_TLS_HOST "tcp://localhost:2376" + +#define AUTH_PLUGIN "authz-broker" + +#define LCRD_ISULA_ADAPTER "isula-adapter" +#define LCRD_ISULA_ACCEL_ARGS "isulad.accel.args" +#define LCRD_ISULA_ACCEL_ARGS_SEPERATOR ";" +#define LCRD_ENABLE_PLUGINS "LCRD_ENABLE_PLUGINS" +#define LCRD_ENABLE_PLUGINS_SEPERATOR "," +#define LCRD_ENABLE_PLUGINS_SEPERATOR_CHAR ',' + +#define MAX_HOSTS 10 + +/*clear the g_lcrd_errmsg*/ +#define DAEMON_CLEAR_ERRMSG() do { \ + if (g_lcrd_errmsg != NULL) { \ + free(g_lcrd_errmsg); \ + g_lcrd_errmsg = NULL; \ + } \ + } while (0) + +typedef enum { + NO_CLIENT_CERT = 0, + REQUEST_CLIENT_CERT, + REQUIRE_ANY_CLIENT_CERT, + VERIFY_CLIENT_CERT_IF_GIVEN, + REQUIRE_AND_VERIFY_CLIENT_CERT +} client_auth_type_t; + +struct lcrd_client_cgroup_resources { + uint16_t blkio_weight; + int64_t cpu_shares; + int64_t cpu_period; + int64_t cpu_quota; + int64_t cpu_rt_period; + int64_t cpu_rt_runtime; + char *cpuset_cpus; + char *cpuset_mems; + int64_t memory_limit; + int64_t memory_swap; + int64_t memory_reservation; + int64_t kernel_memory_limit; + char **ulimits; + size_t ulimits_len; +}; + +struct create_custom_config { + /*environment variables*/ + int env_len; + char **env; + + /* cgroup resources */ + struct lcrd_client_cgroup_resources cr; + + /* hugepage limits*/ + int hugepage_limits_len; + char **hugepage_limits; + + /* hook-spec file */ + char *hook_spec; + + /* pids limit */ + char *pids_limit; + + /* files limit */ + char *files_limit; + + /* user and group */ + char *user; + + /*hostname*/ + char *hostname; + + /* privileged */ + bool privileged; + + /* readonly rootfs */ + bool readonly; + + /* alldevices */ + bool all_devices; + + /* system container*/ + bool system_container; + + /* cap add */ + int cap_adds_len; + char **cap_adds; + + /* cap drop */ + int cap_drops_len; + char **cap_drops; + + /* volumes to mount */ + int volumes_len; + char **volumes; + + /* mounts to mount filesystem */ + int mounts_len; + char **mounts; + + /* devices to populate in container */ + int devices_len; + char **devices; + + /* blkio weight devices */ + int weight_dev_len; + char **weight_devices; + + /* entrypoint */ + char *entrypoint; + + /* init command args */ + int command_len; + char * const *commands; + + /* console log options */ + char *log_file; + char *log_file_size; + unsigned int log_file_rotate; + + char *share_ns[NAMESPACE_MAX]; +}; + +struct lcrd_events_format { + char *id; + uint32_t has_type; + container_events_type_t type; + uint32_t has_pid; + uint32_t pid; + uint32_t has_exit_status; + uint32_t exit_status; + types_timestamp_t timestamp; +}; + +typedef void (handle_events_callback_t)(struct lcrd_events_format *event); + +typedef bool (*stream_check_call_cancelled)(void *context); +typedef bool (*stream_write_fun_t)(void *writer, void *data); +typedef bool (*stream_read_fun_t)(void *reader, void *data); +typedef bool (*stream_add_initial_metadata_fun_t)(void *context, const char *header, const char *val); + +typedef struct { + void *context; + stream_check_call_cancelled is_cancelled; + stream_add_initial_metadata_fun_t add_initial_metadata; + void *writer; + stream_write_fun_t write_func; + void *reader; + stream_read_fun_t read_func; +} stream_func_wrapper; + +struct lcrd_events_request { + handle_events_callback_t *cb; + bool storeonly; + char *id; + types_timestamp_t since; + types_timestamp_t until; +}; + +struct lcrd_events_response { + uint32_t server_errono; + uint32_t cc; + char *errmsg; +}; + +struct lcrd_copy_from_container_request { + char *id; + char *runtime; + char *srcpath; +}; + +struct lcrd_copy_from_container_response { + char *data; + size_t data_len; +}; + +struct lcrd_copy_to_container_data { + char *data; + size_t data_len; +}; + +struct lcrd_logs_request { + char *id; + char *runtime; + + char *since; + char *until; + bool timestamps; + bool follow; + int64_t tail; + bool details; +}; + +struct lcrd_logs_response { + uint32_t cc; + char *errmsg; +}; + +struct lcrd_health_check_request { + char *service; +}; + +struct lcrd_health_check_response { + Health_Serving_Status health_status; + uint32_t cc; + char *errmsg; +}; + +struct lcrd_container_conf_request { + char *name; +}; + +struct lcrd_container_conf_response { + uint32_t cc; + uint32_t server_errono; + char *container_logpath; + uint32_t container_logrotate; + char *container_logsize; + char *errmsg; +}; + +struct lcrd_container_rename_request { + char *old_name; + char *new_name; +}; + +struct lcrd_container_rename_response { + char *id; + uint32_t cc; + char *errmsg; +}; + + +struct lcrd_image_info { + char *imageref; + char *type; + char *digest; + int64_t created; /* seconds */ + int32_t created_nanos; + int64_t size; /*Bytes*/ +}; + +struct lcrd_create_image_request { + struct lcrd_image_info image_info; +}; + +struct lcrd_create_image_response { + uint32_t cc; + uint32_t server_errono; + struct lcrd_image_info image_info; + char *errmsg; +}; + +struct container_log_config { + char *path; + int rotate; + int64_t size; +}; +void container_log_config_free(struct container_log_config *conf); + +void lcrd_container_conf_request_free(struct lcrd_container_conf_request *request); + +void lcrd_container_conf_response_free(struct lcrd_container_conf_response *response); + +void lcrd_events_request_free(struct lcrd_events_request *request); + +void lcrd_copy_from_container_request_free(struct lcrd_copy_from_container_request *request); + +void lcrd_copy_from_container_response_free(struct lcrd_copy_from_container_response *response); + +void lcrd_set_error_message(const char *format, ...); + +void lcrd_try_set_error_message(const char *format, ...); + +void lcrd_append_error_message(const char *format, ...); + +void lcrd_container_rename_request_free(struct lcrd_container_rename_request *request); + +void lcrd_container_rename_response_free(struct lcrd_container_rename_response *response); + +void lcrd_logs_request_free(struct lcrd_logs_request *request); +void lcrd_logs_response_free(struct lcrd_logs_response *response); +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/linked_list.h b/src/linked_list.h new file mode 100644 index 0000000..4f96026 --- /dev/null +++ b/src/linked_list.h @@ -0,0 +1,122 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container linked list function definition + ******************************************************************************/ +#ifndef __LINKED_LIST_H +#define __LINKED_LIST_H + +struct linked_list { + void *elem; + struct linked_list *next; + struct linked_list *prev; +}; + +/* Iterate through an linked list. */ +#define linked_list_for_each(__iterator, __list) \ + for ((__iterator) = (__list)->next; \ + (__iterator) != (__list); \ + (__iterator) = (__iterator)->next) + +/* Iterate safely through an linked list. */ +#define linked_list_for_each_safe(__iterator, __list, __next) \ + for ((__iterator) = (__list)->next, (__next) = (__iterator)->next; \ + (__iterator) != (__list); \ + (__iterator) = (__next), (__next) = (__next)->next) + +/* Initialize list. */ +static inline void linked_list_init(struct linked_list *list) +{ + list->elem = NULL; + list->next = list->prev = list; +} + +/* Add an element to a list. See linked_list_add() and linked_list_add_tail() for an + * idiom. */ +static inline void linked_list_add_elem(struct linked_list *list, void *elem) +{ + list->elem = elem; +} + +/* Retrieve first element of list. */ +static inline void *linked_list_first_elem(struct linked_list *list) +{ + return list->next->elem; +} + +/* Retrieve last element of list. */ +static inline void *linked_list_last_elem(struct linked_list *list) +{ + return list->prev->elem; +} + +/* Retrieve first node of list. */ +static inline void *linked_list_first_node(struct linked_list *list) +{ + return list->next; +} + +/* Determine if list is empty. */ +static inline int linked_list_empty(struct linked_list *list) +{ + return list == list->next; +} + +/* Workhorse to be called from linked_list_add() and linked_list_add_tail(). */ +static inline void __linked_list_add(struct linked_list *newlist, + struct linked_list *prev, + struct linked_list *next) +{ + next->prev = newlist; + newlist->next = next; + newlist->prev = prev; + prev->next = newlist; +} + +/* Idiom to add an element to the beginning of an linked list */ +static inline void linked_list_add(struct linked_list *head, + struct linked_list *list) +{ + __linked_list_add(list, head, head->next); +} + +/* Idiom to add an element to the end of an linked list */ +static inline void linked_list_add_tail(struct linked_list *head, + struct linked_list *list) +{ + __linked_list_add(list, head->prev, head); +} + +/* Idiom to free an linked list */ +static inline void linked_list_del(const struct linked_list *list) +{ + struct linked_list *next, *prev; + + next = list->next; + prev = list->prev; + next->prev = prev; + prev->next = next; +} + +/* Return length of the list. */ +static inline size_t linked_list_len(struct linked_list *list) +{ + size_t i = 0; + struct linked_list *iter; + linked_list_for_each(iter, list) { + i++; + } + + return i; +} + +#endif diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..f3e5a95 --- /dev/null +++ b/src/log.c @@ -0,0 +1,459 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container log function + ******************************************************************************/ +#define _GNU_SOURCE +#define __STDC_FORMAT_MACROS /* Required for PRIu64 to work. */ +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "securec.h" + +#include "utils.h" + +const char * const g_log_prio_name[] = { + "FATAL", "ALERT", "CRIT", "ERROR", "WARN", "NOTICE", "INFO", "DEBUG", "TRACE" +}; + +#define MAX_MSG_LENGTH 4096 + +static __thread char *g_log_prefix = NULL; + +static char *g_log_vmname = NULL; +static bool g_log_quiet = false; +static int g_log_level = ISULA_LOG_DEBUG; +static int g_log_driver = LOG_DRIVER_STDOUT; +int g_lcrd_log_fd = -1; + +/* set log prefix */ +void set_log_prefix(const char *prefix) +{ + if (prefix == NULL) { + return; + } + if (g_log_prefix != NULL) { + free(g_log_prefix); + } + g_log_prefix = util_strdup_s(prefix); +} + +void set_default_command_log_config(const char *name, struct log_config *log) +{ + log->name = name; + log->quiet = true; + log->file = NULL; + log->priority = "DEBUG"; + log->driver = "stdout"; +} + +/* free log prefix */ +void free_log_prefix() +{ + if (g_log_prefix != NULL) { + free(g_log_prefix); + } + g_log_prefix = NULL; +} + +/* write nointr */ +static ssize_t write_nointr(int fd, const void *buf, size_t count) +{ + ssize_t nret = 0; + + for (;;) { + nret = write(fd, buf, count); + if (nret < 0 && errno == EINTR) { + continue; + } else { + break; + } + } + return nret; +} + +void log_append_logfile(const struct log_object_metadata *metadata, const char *timestamp, const char *msg); +void log_append_stderr(const struct log_object_metadata *metadata, const char *timestamp, const char *msg); + +/* change str logdriver to enum */ +int change_str_logdriver_to_enum(const char *driver) +{ + if (driver == NULL) { + return LOG_DRIVER_NOSET; + } + if (strcasecmp(driver, "stdout") == 0) { + return LOG_DRIVER_STDOUT; + } + if (strcasecmp(driver, "fifo") == 0) { + return LOG_DRIVER_FIFO; + } + + return -1; +} + +#define LOG_FIFO_SIZE (1024 * 1024) +/* open fifo */ +static int open_fifo(const char *fifo_path) +{ + int nret = 0; + int fifo_fd = -1; + + if (fifo_path == NULL) { + COMMAND_ERROR("Empty fifo path"); + return -1; + } + nret = mknod(fifo_path, S_IFIFO | S_IRUSR | S_IWUSR, (dev_t)0); + if (nret && errno != EEXIST) { + COMMAND_ERROR("Mknod failed: %s\n", strerror(errno)); + return nret; + } + + fifo_fd = util_open(fifo_path, O_RDWR | O_NONBLOCK, 0); + if (fifo_fd == -1) { + COMMAND_ERROR("Open fifo %s failed: %s\n", fifo_path, strerror(errno)); + return -1; + } + + if (fcntl(fifo_fd, F_SETPIPE_SZ, LOG_FIFO_SIZE) == -1) { + COMMAND_ERROR("Set fifo buffer size failed: %s", strerror(errno)); + close(fifo_fd); + return -1; + } + + return fifo_fd; +} + +static int log_init_checker(const struct log_config *log) +{ + int i = 0; + int driver = 0; + + if (log == NULL || log->name == NULL || log->priority == NULL) { + return -1; + } + + for (i = ISULA_LOG_FATAL; i < ISULA_LOG_MAX; i++) { + if (strcasecmp(g_log_prio_name[i], log->priority) == 0) { + g_log_level = i; + break; + } + } + + if (i == ISULA_LOG_MAX) { + fprintf(stderr, "Unable to parse logging level:%s\n", log->priority); + return -1; + } + + driver = change_str_logdriver_to_enum(log->driver); + if (driver < 0) { + fprintf(stderr, "Invalid log driver: %s\n", log->driver); + return -1; + } + g_log_driver = driver; + return 0; +} + +/* log init */ +int log_init(struct log_config *log) +{ + int nret = 0; + char *full_path = NULL; + + if (g_lcrd_log_fd != -1) { + fprintf(stderr, "lcrd log_init called with log already initialized\n"); + return 0; + } + + if (log_init_checker(log) != 0) { + return -1; + } + + free(g_log_vmname); + g_log_vmname = util_strdup_s(log->name); + + g_log_quiet = log->quiet; + + if (log->file == NULL || strcmp(log->file, "none") == 0) { + if (g_log_driver == LOG_DRIVER_FIFO) { + fprintf(stderr, "Must set log file if driver is %s\n", log->driver); + nret = -1; + } + goto out; + } + full_path = util_strdup_s(log->file); + + if (util_build_dir(full_path)) { + fprintf(stderr, "failed to create dir for log file\n"); + nret = -1; + goto out; + } + g_lcrd_log_fd = open_fifo(full_path); + + if (g_lcrd_log_fd == -1) { + nret = -1; + } +out: + if (nret != 0 && g_log_driver == LOG_DRIVER_FIFO) { + g_log_driver = LOG_DRIVER_NOSET; + } + free(full_path); + + return nret; +} + +static char *parse_timespec_to_human() +{ + struct timespec timestamp; + struct tm ptm = { 0 }; + char date_time[ISULAD_LOG_BUFFER_SIZE] = { 0 }; + int nret; + + if (clock_gettime(CLOCK_REALTIME, ×tamp) == -1) { + fprintf(stderr, "Failed to get real time\n"); + return 0; + } + + if (localtime_r(&(timestamp.tv_sec), &ptm) == NULL) { + SYSERROR("Transfer timespec failed"); + return NULL; + } + + nret = sprintf_s(date_time, ISULAD_LOG_BUFFER_SIZE, "%04d%02d%02d%02d%02d%02d.%03ld", + ptm.tm_year + 1900, ptm.tm_mon + 1, ptm.tm_mday, ptm.tm_hour, ptm.tm_min, ptm.tm_sec, + timestamp.tv_nsec / 1000000); + + if (nret < 0) { + COMMAND_ERROR("Sprintf failed"); + return NULL; + } + + return util_strdup_s(date_time); +} + +/* log append */ +int log_append(const struct log_object_metadata *metadata, const char *format, ...) +{ + int rc = 0; + va_list args; + char msg[MAX_MSG_LENGTH] = { 0 }; + char *date_time = NULL; + int ret = 0; + + va_start(args, format); + rc = vsprintf_s(msg, MAX_MSG_LENGTH, format, args); + va_end(args); + if (rc < 0 || rc >= MAX_MSG_LENGTH) { + rc = sprintf_s(msg, MAX_MSG_LENGTH, "%s", "!!LONG LONG A LOG!!"); + if (rc < 0) { + return 0; + } + } + + date_time = parse_timespec_to_human(); + if (date_time == NULL) { + goto out; + } + + switch (g_log_driver) { + case LOG_DRIVER_STDOUT: + if (g_log_quiet) { + break; + } + log_append_stderr(metadata, date_time, msg); + break; + case LOG_DRIVER_FIFO: + if (g_lcrd_log_fd == -1) { + COMMAND_ERROR("Do not set log file"); + ret = -1; + goto out; + } + log_append_logfile(metadata, date_time, msg); + break; + case LOG_DRIVER_NOSET: + break; + default: + COMMAND_ERROR("Invalid log driver"); + ret = -1; + goto out; + } + +out: + free(date_time); + return ret; +} + +/* log append logfile */ +void log_append_logfile(const struct log_object_metadata *metadata, const char *timestamp, const char *msg) +{ + int log_fd = -1; + int nret = 0; + size_t size = 0; + char *tmp_prefix = NULL; + char log_buffer[ISULAD_LOG_BUFFER_SIZE] = { 0 }; + + if (metadata == NULL || metadata->level > g_log_level) { + return; + } + log_fd = g_lcrd_log_fd; + if (log_fd == -1) { + return; + } + + tmp_prefix = g_log_prefix != NULL ? g_log_prefix : g_log_vmname; + if (tmp_prefix != NULL && strlen(tmp_prefix) > 15) { + tmp_prefix = tmp_prefix + (strlen(tmp_prefix) - 15); + } + if (metadata->func != NULL && metadata->file != NULL) { + nret = sprintf_s(log_buffer, sizeof(log_buffer), "%15s %s %-8s %s - %s:%s:%d - %s", + tmp_prefix ? tmp_prefix : "", timestamp, g_log_prio_name[metadata->level], + g_log_vmname ? g_log_vmname : "lcrd", metadata->file, metadata->func, + metadata->line, msg); + } else { + nret = sprintf_s(log_buffer, sizeof(log_buffer), "%s %s", timestamp, msg); + } + + if (nret < 0) { + return; + } + + size = (size_t)nret; + if (size > (sizeof(log_buffer) - 1)) { + size = sizeof(log_buffer) - 1; + } + + log_buffer[size] = '\n'; + + if (write_nointr(log_fd, log_buffer, (size + 1)) == -1) { + fprintf(stderr, "write log into logfile failed"); + } +} + +/* log append stderr */ +void log_append_stderr(const struct log_object_metadata *metadata, const char *timestamp, const char *msg) +{ + char *tmp_prefix = NULL; + + if (metadata == NULL || metadata->level > g_log_level) { + return; + } + + tmp_prefix = g_log_prefix ? g_log_prefix : g_log_vmname; + if (tmp_prefix != NULL && strlen(tmp_prefix) > 15) { + tmp_prefix = tmp_prefix + (strlen(tmp_prefix) - 15); + } + + if (metadata->func != NULL && metadata->file != NULL) { + fprintf(stderr, "%15s ", tmp_prefix ? tmp_prefix : ""); + } + fprintf(stderr, "%s ", timestamp); + if (metadata->func != NULL && metadata->file != NULL) { + fprintf(stderr, "%-8s ", g_log_prio_name[metadata->level]); + fprintf(stderr, "%s - ", g_log_vmname ? g_log_vmname : "lcrd"); + fprintf(stderr, "%s:%s:%d - ", metadata->file, metadata->func, metadata->line); + } + fprintf(stderr, "%s", msg); + fprintf(stderr, "\n"); +} + +/* lcrd unix trans to utc */ +int lcrd_unix_trans_to_utc(char *buf, size_t bufsize, const struct timespec *time) +{ + int ret = 0; + int64_t trans_to_days = 0; + int64_t all_days = 0; + int64_t age = 0; + int64_t doa = 0; + int64_t yoa = 0; + int64_t real_year = 0; + int64_t doy = 0; + int64_t nom = 0; + int64_t real_day = 0; + int64_t real_month = 0; + int64_t trans_to_sec = 0; + int64_t real_hours = 0; + int64_t hours_to_sec = 0; + int64_t real_minutes = 0; + int64_t real_seconds = 0; + char ns[LCRD_NUMSTRLEN64] = { 0 }; + + /* Transtate seconds to number of days. */ + trans_to_days = time->tv_sec / 86400; + + /* Calculate days from 0000-03-01 to 1970-01-01.Days base it*/ + all_days = trans_to_days + 719468; + + /* compute the age.One age means 400 years(146097 days) */ + age = (all_days >= 0 ? all_days : all_days - 146096) / 146097; + + /* The day-of-age (doa) can then be found by subtracting the genumber */ + doa = (all_days - age * 146097); + + /* Calculate year-of-age (yoa, range [0, 399]) */ + yoa = ((doa - (doa / 1460)) + (doa / 36524) - (doa / 146096)) / 365; + + /* Compute the year this moment */ + real_year = yoa + age * 400; + + /* Calculate the day-of-year */ + doy = doa - (365 * yoa + yoa / 4 - yoa / 100); + + /* Compute the month number. */ + nom = (5 * doy + 2) / 153; + + /* Compute the real_day. */ + real_day = (doy - ((153 * nom + 2) / 5)) + 1; + + /* Compute the correct month. */ + real_month = nom + (nom < 10 ? 3 : -9); + + /* Add one year before March */ + if (real_month < 3) { + real_year++; + } + + /* Translate days in the age to seconds. */ + trans_to_sec = trans_to_days * 86400; + + /* Compute the real_hours */ + real_hours = (time->tv_sec - trans_to_sec) / 3600; + + /* Translate the real hours to seconds. */ + hours_to_sec = real_hours * 3600; + + /* Calculate the real minutes */ + real_minutes = ((time->tv_sec - trans_to_sec) - hours_to_sec) / 60; + + /* Calculate the real seconds */ + real_seconds = (((time->tv_sec - trans_to_sec) - hours_to_sec) - (real_minutes * 60)); + + ret = sprintf_s(ns, LCRD_NUMSTRLEN64, "%ld", time->tv_nsec); + if (ret < 0 || ret >= LCRD_NUMSTRLEN64) { + return -1; + } + + /* Create the final timestamp */ + ret = sprintf_s(buf, bufsize, "%" PRId64 "%02" PRId64 "%02" PRId64 "%02" PRId64 "%02" PRId64 "%02" PRId64 ".%.3s", + real_year, real_month, real_day, real_hours, real_minutes, real_seconds, ns); + if (ret < 0 || (size_t)ret >= bufsize) { + return -1; + } + + return 0; +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..7173c51 --- /dev/null +++ b/src/log.h @@ -0,0 +1,147 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container log function definition + ******************************************************************************/ +#ifndef __LCRD_LOG_H +#define __LCRD_LOG_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 02000000 +#endif + +#ifndef F_DUPFD_CLOEXEC +#define F_DUPFD_CLOEXEC 1030 +#endif + +#define ISULAD_LOG_BUFFER_SIZE 4096 + +/* We're logging in seconds and nanoseconds. Assuming that the underlying + * datatype is currently at maximum a 64bit integer, we have a date string that + * is of maximum length (2^64 - 1) * 2 = (21 + 21) = 42. + * */ +#define LCRD_LOG_TIME_SIZE 42 + +enum g_log_driver { LOG_DRIVER_STDOUT, LOG_DRIVER_FIFO, LOG_DRIVER_NOSET }; + +enum isula_log_level { + ISULA_LOG_FATAL = 0, + ISULA_LOG_ALERT, + ISULA_LOG_CRIT, + ISULA_LOG_ERROR, + ISULA_LOG_WARN, + ISULA_LOG_NOTICE, + ISULA_LOG_INFO, + ISULA_LOG_DEBUG, + ISULA_LOG_TRACE, + ISULA_LOG_MAX +}; + +struct log_config { + const char *name; + const char *file; + const char *priority; + const char *prefix; + const char *driver; + bool quiet; +}; + +/* brief logging event object */ +struct log_object_metadata { + /* location information of the logging item */ + const char *file; + const char *func; + int line; + + int level; +}; + +#define LOG_METADATA_INIT \ + { \ + .file = __FILE__, .func = __func__, .line = __LINE__, \ + } + + +int log_init(struct log_config *log); + +void set_default_command_log_config(const char *name, struct log_config *log); + +void set_log_prefix(const char *prefix); + +void free_log_prefix(); + +int change_str_logdriver_to_enum(const char *driver); + +int log_append(const struct log_object_metadata *metadata, const char *format, ...); + +#define ISULA_COMMON_LOG(loglevel, format, ...) \ + do { \ + struct log_object_metadata meta = LOG_METADATA_INIT; \ + meta.level = loglevel; \ + (void)log_append(&meta, format, ##__VA_ARGS__); \ + } while (0) + +#define DEBUG(format, ...) \ + ISULA_COMMON_LOG(ISULA_LOG_DEBUG, format, ##__VA_ARGS__) + +#define INFO(format, ...) \ + ISULA_COMMON_LOG(ISULA_LOG_INFO, format, ##__VA_ARGS__) + +#define NOTICE(format, ...) \ + ISULA_COMMON_LOG(ISULA_LOG_NOTICE, format, ##__VA_ARGS__) + +#define WARN(format, ...) \ + ISULA_COMMON_LOG(ISULA_LOG_WARN, format, ##__VA_ARGS__) + +#define ERROR(format, ...) \ + ISULA_COMMON_LOG(ISULA_LOG_ERROR, format, ##__VA_ARGS__) + +#define EVENT(format, ...) \ + do { \ + struct log_object_metadata metadata; \ + metadata.level = ISULA_LOG_ERROR; \ + metadata.func = NULL; \ + metadata.file = NULL; \ + log_append(&metadata, format, ##__VA_ARGS__); \ + } while (0) + +#define CRIT(format, ...) \ + ISULA_COMMON_LOG(ISULA_LOG_CRIT, format, ##__VA_ARGS__) + +#define ALERT(format, ...) \ + ISULA_COMMON_LOG(ISULA_LOG_ALERT, format, ##__VA_ARGS__) + +#define FATAL(format, ...) \ + ISULA_COMMON_LOG(ISULA_LOG_FATAL, format, ##__VA_ARGS__) + +#define SYSERROR(format, ...) \ + do { \ + ERROR("%s - " format, strerror(errno), ##__VA_ARGS__); \ + } while (0) + +#define COMMAND_ERROR(fmt, args...) \ + do { \ + fprintf(stderr, fmt "\n", ##args); \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif /* __LCRD_LOG_H */ diff --git a/src/mainloop.c b/src/mainloop.c new file mode 100644 index 0000000..e4e14c7 --- /dev/null +++ b/src/mainloop.c @@ -0,0 +1,158 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container mainloop functions + ******************************************************************************/ +#include +#include +#include +#include +#include + +#include "mainloop.h" +#include "utils.h" + +struct epoll_loop_handler { + epoll_loop_callback_t cb; + int cbfd; + void *cbdata; +}; + +#define MAX_EVENTS 100 + +/* epoll loop */ +int epoll_loop(struct epoll_descr *descr, int t) +{ + int i; + int ret = 0; + struct epoll_loop_handler *epoll_handler = NULL; + struct epoll_event evs[MAX_EVENTS]; + + while (1) { + int ep_fds = epoll_wait(descr->fd, evs, MAX_EVENTS, t); + if (ep_fds < 0) { + if (errno == EINTR) { + continue; + } + ret = -1; + goto out; + } + + for (i = 0; i < ep_fds; i++) { + epoll_handler = (struct epoll_loop_handler *)(evs[i].data.ptr); + if (epoll_handler->cb(epoll_handler->cbfd, evs[i].events, epoll_handler->cbdata, descr) > 0) { + goto out; + } + } + + if (ep_fds == 0 && t != 0) { + goto out; + } + + if (linked_list_empty(&descr->handler_list)) { + goto out; + } + } +out: + return ret; +} + +/* epoll loop add handler */ +int epoll_loop_add_handler(struct epoll_descr *descr, int fd, + epoll_loop_callback_t callback, void *data) +{ + struct epoll_event ev; + struct epoll_loop_handler *epoll_handler = NULL; + struct linked_list *node = NULL; + + epoll_handler = util_common_calloc_s(sizeof(*epoll_handler)); + if (epoll_handler == NULL) { + goto fail_out; + } + + epoll_handler->cbfd = fd; + epoll_handler->cb = callback; + epoll_handler->cbdata = data; + + ev.events = EPOLLIN; + ev.data.ptr = epoll_handler; + + if (epoll_ctl(descr->fd, EPOLL_CTL_ADD, fd, &ev) < 0) { + goto fail_out; + } + + node = util_common_calloc_s(sizeof(struct linked_list)); + if (node == NULL) { + goto fail_out; + } + + node->elem = epoll_handler; + linked_list_add(&descr->handler_list, node); + return 0; + +fail_out: + free(epoll_handler); + + return -1; +} + +/* epoll loop del handler */ +int epoll_loop_del_handler(struct epoll_descr *descr, int fd) +{ + struct epoll_loop_handler *epoll_handler = NULL; + struct linked_list *index = NULL; + + linked_list_for_each(index, &descr->handler_list) { + epoll_handler = index->elem; + + if (fd == epoll_handler->cbfd) { + if (epoll_ctl(descr->fd, EPOLL_CTL_DEL, fd, NULL)) { + goto fail_out; + } + + linked_list_del(index); + free(index->elem); + free(index); + return 0; + } + } + +fail_out: + return -1; +} + +/* epoll loop open */ +int epoll_loop_open(struct epoll_descr *descr) +{ + descr->fd = epoll_create1(EPOLL_CLOEXEC); + if (descr->fd < 0) { + return -1; + } + + linked_list_init(&(descr->handler_list)); + return 0; +} + +/* epoll loop close */ +int epoll_loop_close(struct epoll_descr *descr) +{ + struct linked_list *index = NULL; + struct linked_list *next = NULL; + + linked_list_for_each_safe(index, &(descr->handler_list), next) { + linked_list_del(index); + free(index->elem); + free(index); + } + + return close(descr->fd); +} diff --git a/src/mainloop.h b/src/mainloop.h new file mode 100644 index 0000000..2fe4886 --- /dev/null +++ b/src/mainloop.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container mainloop definition + ******************************************************************************/ +#ifndef __EPOLLLOOP_H +#define __EPOLLLOOP_H + +#include +#include "linked_list.h" + +struct epoll_descr { + int fd; + struct linked_list handler_list; +}; + +typedef int (*epoll_loop_callback_t)(int fd, uint32_t event, + void *data, + struct epoll_descr *descr); + +extern int epoll_loop(struct epoll_descr *descr, int t); + +extern int epoll_loop_add_handler(struct epoll_descr *descr, int fd, + epoll_loop_callback_t callback, + void *data); + +extern int epoll_loop_del_handler(struct epoll_descr *descr, int fd); + +extern int epoll_loop_open(struct epoll_descr *descr); + +extern int epoll_loop_close(struct epoll_descr *descr); + +#endif diff --git a/src/map/CMakeLists.txt b/src/map/CMakeLists.txt new file mode 100644 index 0000000..c04e59c --- /dev/null +++ b/src/map/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_map_srcs) + +set(MAP_SRCS + ${local_map_srcs} + PARENT_SCOPE + ) diff --git a/src/map/map.c b/src/map/map.c new file mode 100644 index 0000000..e87d4cd --- /dev/null +++ b/src/map/map.c @@ -0,0 +1,386 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container map functions + ******************************************************************************/ +#include +#include + +#include "map.h" +#include "log.h" +#include "utils.h" + +static void map_free_key_value(void *key, void *val) +{ + free(key); + free(val); +} + +/* function to remove element by key */ +bool map_remove(map_t *map, void *key) +{ + if (map == NULL || key == NULL) { + return false; + } + + return rbtree_remove(map->store, key); +} + +/* function to search key */ +void *map_search(const map_t *map, void *key) +{ + if (map == NULL || key == NULL) { + return NULL; + } + + return rbtree_search(map->store, key); +} + +/* function to return map itor */ +map_itor *map_itor_new(const map_t *map) +{ + if (map == NULL) { + return NULL; + } + + return rbtree_iterator_new(map->store); +} + +/* function to free map itor */ +void map_itor_free(map_itor *itor) +{ + if (itor == NULL) { + return; + } + + rbtree_iterator_free(itor); +} + +/* function to locate first map itor */ +bool map_itor_first(map_itor *itor) +{ + if (itor == NULL) { + return false; + } + + return rbtree_iterator_first(itor); +} + +/* function to locate last map itor */ +bool map_itor_last(map_itor *itor) +{ + if (itor == NULL) { + return false; + } + + return rbtree_iterator_last(itor); +} + +/* function to locate next itor */ +bool map_itor_next(map_itor *itor) +{ + if (itor == NULL) { + return false; + } + + return rbtree_iterator_next(itor); +} + +/* function to locate prev itor */ +bool map_itor_prev(map_itor *itor) +{ + if (itor == NULL) { + return false; + } + + return rbtree_iterator_prev(itor); +} + +/* function to check itor is valid */ +bool map_itor_valid(const map_itor *itor) +{ + if (itor == NULL) { + return false; + } + + return rbtree_iterator_valid(itor); +} + +/* function to check itor is valid */ +void *map_itor_key(map_itor *itor) +{ + if (itor == NULL) { + return NULL; + } + + return rbtree_iterator_key(itor); +} + +/* function to check itor is valid */ +void *map_itor_value(map_itor *itor) +{ + if (itor == NULL) { + return NULL; + } + + return rbtree_iterator_value(itor); +} + +/* function to get size of map */ +size_t map_size(const map_t *map) +{ + if (map == NULL) { + return 0; + } + + return rbtree_size(map->store); +} + +/* is key int */ +static bool is_key_int(map_type_t type) +{ + return (type == MAP_INT_INT || type == MAP_INT_STR || type == MAP_INT_PTR); +} + +/* is key str */ +static bool is_key_str(map_type_t type) +{ + return (type == MAP_STR_INT || type == MAP_STR_STR || type == MAP_STR_PTR || type == MAP_STR_BOOL); +} + +/* is key ptr */ +static bool is_key_ptr(map_type_t type) +{ + return (type == MAP_PTR_INT || type == MAP_PTR_STR || type == MAP_PTR_PTR); +} + +/* is val bool */ +static bool is_val_bool(map_type_t type) +{ + return (type == MAP_STR_BOOL); +} + +/* is val int */ +static bool is_val_int(map_type_t type) +{ + return (type == MAP_INT_INT || type == MAP_STR_INT || type == MAP_PTR_INT); +} + +/* is val str */ +static bool is_val_str(map_type_t type) +{ + return (type == MAP_INT_STR || type == MAP_STR_STR || type == MAP_PTR_STR); +} + +/* is val ptr */ +static bool is_val_ptr(map_type_t type) +{ + return (type == MAP_INT_PTR || type == MAP_STR_PTR || type == MAP_PTR_PTR); +} + +static void *map_convert_key(const map_t *map, void *key) +{ + void *insert_key = NULL; + int *ikey = NULL; + char *skey = NULL; + if (is_key_ptr(map->type)) { + insert_key = key; + } else if (is_key_int(map->type)) { + ikey = util_common_calloc_s(sizeof(int)); + if (ikey == NULL) { + ERROR("out of memory"); + return NULL; + } + *ikey = *(int *)key; + insert_key = (void *)ikey; + } else if (is_key_str(map->type)) { + skey = util_strdup_s((const char *)key); + if (skey == NULL) { + ERROR("out of memory"); + return NULL; + } + insert_key = (void *)skey; + } else { + return NULL; + } + return insert_key; +} + +static void *map_convert_value(const map_t *map, void *value) +{ + void *insert_value = NULL; + bool *bvalue = NULL; + int *ivalue = NULL; + char *svalue = NULL; + if (is_val_ptr(map->type)) { + insert_value = value; + } else if (is_val_bool(map->type)) { + bvalue = util_common_calloc_s(sizeof(bool)); + if (bvalue == NULL) { + return NULL; + } + *bvalue = *(bool *)value; + insert_value = (void *)bvalue; + } else if (is_val_int(map->type)) { + ivalue = util_common_calloc_s(sizeof(int)); + if (ivalue == NULL) { + return NULL; + } + *ivalue = *(int *)value; + insert_value = (void *)ivalue; + } else if (is_val_str(map->type)) { + svalue = util_strdup_s((const char *)value); + insert_value = (void *)svalue; + } else { + return NULL; + } + return insert_value; +} + +/* function to replace key value */ +bool map_replace(const map_t *map, void *key, void *value) +{ + void *tmp = NULL; + void *tmp_value = NULL; + + if (map == NULL || key == NULL || value == NULL) { + ERROR("invalid parameter"); + return false; + } + + tmp = map_convert_key(map, key); + if (tmp == NULL) { + ERROR("failed to convert key, out of memory or invalid k-v type"); + return false; + } + + tmp_value = map_convert_value(map, value); + if (tmp_value == NULL) { + ERROR("failed to convert value, out of memory or invalid k-v type"); + if (!is_key_ptr(map->type)) { + free(tmp); + } + return false; + } + + bool ret = rbtree_replace(map->store, tmp, tmp_value); + if (!ret) { + ERROR("failed to replace node in rbtree"); + if (!is_key_ptr(map->type)) { + free(tmp); + } + if (!is_val_ptr(map->type)) { + free(tmp_value); + } + } + + return ret; +} + +/* function to insert key value */ +bool map_insert(map_t *map, void *key, void *value) +{ + void *tmp = NULL; + void *tmp_value = NULL; + + if (map == NULL || key == NULL || value == NULL) { + ERROR("invalid parameter"); + return false; + } + + tmp = map_convert_key(map, key); + if (tmp == NULL) { + ERROR("failed to convert key, out of memory or invalid k-v type"); + return false; + } + tmp_value = map_convert_value(map, value); + if (tmp_value == NULL) { + ERROR("failed to convert value, out of memory or invalid k-v type"); + if (!is_key_ptr(map->type)) { + free(tmp); + } + return false; + } + + bool ret = rbtree_insert(map->store, tmp, tmp_value); + if (!ret) { + ERROR("failed to insert node to rbtree"); + if (!is_key_ptr(map->type)) { + free(tmp); + } + if (!is_val_ptr(map->type)) { + free(tmp_value); + } + } + return ret; +} + +// malloc a new map by type +map_t *map_new(map_type_t kvtype, map_cmp_func comparator, map_kvfree_func kvfree) +{ + map_t *map = NULL; + key_comparator cmpor = NULL; + key_value_freer freer = NULL; + + map = util_common_calloc_s(sizeof(map_t)); + if (map == NULL) { + ERROR("Out of memory"); + return NULL; + } + if (kvfree == MAP_DEFAULT_FREE_FUNC) { + freer = map_free_key_value; + } else { + freer = kvfree; + } + cmpor = comparator; + if (is_key_ptr(kvtype) && (comparator == MAP_DEFAULT_CMP_FUNC)) { + cmpor = rbtree_ptr_cmp; + } else if (is_key_int(kvtype) && (comparator == MAP_DEFAULT_CMP_FUNC)) { + cmpor = rbtree_int_cmp; + } else if (is_key_str(kvtype) && (comparator == MAP_DEFAULT_CMP_FUNC)) { + cmpor = rbtree_str_cmp; + } else { + ERROR("invalid comparator!"); + free(map); + return NULL; + } + map->type = kvtype; + map->store = rbtree_new(cmpor, freer); + if (map->store == NULL) { + map_free(map); + return NULL; + } + return map; +} + +/* just clear all nodes */ +void map_clear(map_t *map) +{ + if (map != NULL && map->store != NULL) { + rbtree_clear(map->store); + } +} + +/* map free */ +void map_free(map_t *map) +{ + if (map == NULL) { + return; + } + + if (map->store != NULL) { + rbtree_free(map->store); + map->store = NULL; + } + free(map); +} + diff --git a/src/map/map.h b/src/map/map.h new file mode 100644 index 0000000..71e9bd8 --- /dev/null +++ b/src/map/map.h @@ -0,0 +1,106 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container map definition + ******************************************************************************/ +#ifndef __LCRD_MAP_H__ +#define __LCRD_MAP_H__ + +#include "rb_tree.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +typedef struct _map_t map_t; +typedef struct rb_iterator map_itor; + +#define MAP_DEFAULT_CMP_FUNC NULL +#define MAP_DEFAULT_FREE_FUNC NULL + +/* function to free key and value */ +typedef key_value_freer map_kvfree_func; + +/* function to compare key */ +typedef key_comparator map_cmp_func; + +/* function to remove element by key */ +bool map_remove(map_t *map, void *key); + +/* function to search key */ +void *map_search(const map_t *map, void *key); + +/* function to get size of map */ +size_t map_size(const map_t *map); + +/* function to replace key value */ +bool map_replace(const map_t *map, void *key, void *value); + +/* function to insert key value */ +bool map_insert(map_t *map, void *key, void *value); + +/* function to return map itor */ +map_itor *map_itor_new(const map_t *map); + +/* function to free map itor */ +void map_itor_free(map_itor *itor); + +/* function to locate first map itor */ +bool map_itor_first(map_itor *itor); + +/* function to locate last map itor */ +bool map_itor_last(map_itor *itor); + +/* function to locate next itor */ +bool map_itor_next(map_itor *itor); + +/* function to locate prev itor */ +bool map_itor_prev(map_itor *itor); + +/* function to check itor is valid */ +bool map_itor_valid(const map_itor *itor); + +/* function to check itor is valid */ +void *map_itor_key(map_itor *itor); + +/* function to check itor is valid */ +void *map_itor_value(map_itor *itor); + +typedef enum { + MAP_INT_INT = 0, + MAP_INT_STR, + MAP_INT_PTR, + MAP_STR_BOOL, + MAP_STR_INT, + MAP_STR_PTR, + MAP_STR_STR, + MAP_PTR_INT, + MAP_PTR_STR, + MAP_PTR_PTR +} map_type_t; + +struct _map_t { + map_type_t type; + rb_tree_t *store; +}; + +map_t *map_new(map_type_t kvtype, map_cmp_func comparator, map_kvfree_func kvfree); + +void map_free(map_t *map); + +void map_clear(map_t *map); + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* __LCRD_MAP_H__ */ diff --git a/src/map/rb_tree.c b/src/map/rb_tree.c new file mode 100644 index 0000000..7f71578 --- /dev/null +++ b/src/map/rb_tree.c @@ -0,0 +1,616 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide rb tree functions + ******************************************************************************/ +#include "rb_tree.h" +#include +#include +#include + +#include "log.h" +#include "utils.h" + +int rbtree_ptr_cmp(const void *first, const void *last) +{ + return ((int)(first > last) - (int)(first < last)); +} + +int rbtree_int_cmp(const void *first, const void *last) +{ + const int key1 = *(const int *)first; + const int key2 = *(const int *)last; + return ((int)(key1 > key2) - (int)(key1 < key2)); +} + +int rbtree_str_cmp(const void *first, const void *last) +{ + const char *key1 = first; + const char *key2 = last; + while (true) { + char a = *key1++; + char b = *key2++; + if (!a || a != b) { + return ((int)(a > b) - (int)(a < b)); + } + } +} + +static struct rb_node *rbtree_create_node(void *key, void *value, rb_node_t *left, rb_node_t *right, rb_node_t *parent) +{ + rb_node_t *node = NULL; + node = util_common_calloc_s(sizeof(rb_node_t)); + if (node == NULL) { + ERROR("failed to malloc rb tree node!"); + return NULL; + } + node->colour = BLACK; // default colour + node->key = key; + node->value = value; + node->left = left; + node->right = right; + node->parent = parent; + return node; +} + +static rb_node_t *rbtree_recursive_search(rb_tree_t *tree, rb_node_t *node, void *key) +{ + if (node == tree->nil || !(tree->comparator(key, node->key))) { + return node; + } + if (tree->comparator(key, node->key) < 0) { + return rbtree_recursive_search(tree, node->left, key); + } + return rbtree_recursive_search(tree, node->right, key); +} + +rb_node_t *rbtree_find(rb_tree_t *tree, void *key) +{ + if (tree == NULL || key == NULL) { + return NULL; + } + return rbtree_recursive_search(tree, tree->root, key); +} + +void *rbtree_search(rb_tree_t *tree, void *key) +{ + if (tree == NULL || key == NULL) { + return NULL; + } + rb_node_t *find = rbtree_find(tree, key); + if (find == tree->nil) { + return NULL; + } + return find->value; +} + +rb_tree_t *rbtree_new(key_comparator comparator, key_value_freer kvfreer) +{ + rb_tree_t *tree = util_common_calloc_s(sizeof(rb_tree_t)); + if (tree == NULL) { + ERROR("failed to malloc rb tree"); + return NULL; + } + tree->nil = rbtree_create_node(NULL, NULL, NULL, NULL, NULL); + tree->root = tree->nil; + tree->comparator = comparator; + tree->kvfreer = kvfreer; + return tree; +} + +static void rbtree_destory_all(rb_tree_t *tree, rb_node_t *node) +{ + if (node == tree->nil) { + return; + } + if (node->left != tree->nil) { + rbtree_destory_all(tree, node->left); + } + if (node->right != tree->nil) { + rbtree_destory_all(tree, node->right); + } + if (tree->kvfreer != NULL) { + tree->kvfreer(node->key, node->value); + } + free(node); +} + +void rbtree_clear(rb_tree_t *tree) +{ + if (tree == NULL) { + return; + } + rbtree_destory_all(tree, tree->root); +} + +void rbtree_free(rb_tree_t *tree) +{ + rbtree_clear(tree); + free(tree->nil); + tree->nil = NULL; + free(tree); +} + +/* + *----------------------------------------------------------- + * | | + * y left rot x + * / \ <=========== / \ + * x γ α y + * / \ ===========> / \ + * α β right rot β γ + *------------------------------------------------------------ + */ +static void left_rot(rb_tree_t *tree, rb_node_t *node) +{ + rb_node_t *y = node->right; + // turn y's left subtree into node's right subtree + node->right = y->left; + if (y->left != tree->nil) { + y->left->parent = node; + } + // link node's parent to y + y->parent = node->parent; + if (node->parent == tree->nil) { + tree->root = y; + } else if (node == node->parent->left) { + node->parent->left = y; + } else { + node->parent->right = y; + } + // put node on y's left + y->left = node; + node->parent = y; +} + +static void right_rot(rb_tree_t *tree, rb_node_t *node) +{ + rb_node_t *y = node->left; + // turn y's right subtree into node's left subtree + node->left = y->right; + if (y->right != tree->nil) { + y->right->parent = node; + } + // link node's parent to y + y->parent = node->parent; + if (node->parent == tree->nil) { + tree->root = y; + } else if (node == node->parent->right) { + node->parent->right = y; + } else { + node->parent->left = y; + } + // put node on y's right + y->right = node; + node->parent = y; +} + +static void rbtree_insert_fixup(rb_tree_t *tree, rb_node_t *node) +{ + while (node->parent->colour == RED) { + if (node->parent == node->parent->parent->left) { // parent node is grandparent node's left child + rb_node_t *y = NULL; + y = node->parent->parent->right; + if (y == NULL) { + return; + } + + if (y->colour == RED) { // uncle node colour is red + node->parent->colour = BLACK; + y->colour = BLACK; + node->parent->parent->colour = RED; + node = node->parent->parent; + continue; + } + + // uncle node colour is black + if (node == node->parent->right) { // current node is right child + node = node->parent; + left_rot(tree, node); + } + // current node is left node + node->parent->colour = BLACK; + node->parent->parent->colour = RED; + right_rot(tree, node->parent->parent); + } else { // parent node is grandparent node's right child + rb_node_t *y = NULL; + y = node->parent->parent->left; + if (y == NULL) { + return; + } + if (y->colour == RED) { // uncle node colour is red + node->parent->colour = BLACK; + y->colour = BLACK; + node->parent->parent->colour = RED; + node = node->parent->parent; + continue; + } + + // uncle node colour is black + if (node == node->parent->left) { // current node is left child + node = node->parent; + right_rot(tree, node); + } + // current node is left node + node->parent->colour = BLACK; + node->parent->parent->colour = RED; + left_rot(tree, node->parent->parent); + } + } + tree->root->colour = BLACK; +} + +static void insert_node(rb_tree_t *tree, rb_node_t *node) +{ + rb_node_t *previous = tree->nil; + rb_node_t *index = tree->root; + while (index != tree->nil) { + previous = index; + if (tree->comparator(node->key, index->key) < 0) { + index = index->left; + } else { + index = index->right; + } + } + node->parent = previous; + if (previous == tree->nil) { + tree->root = node; + } else { + if (tree->comparator(node->key, previous->key) < 0) { + previous->left = node; + } else { + previous->right = node; + } + } + node->left = tree->nil; + node->right = tree->nil; + node->colour = RED; + rbtree_insert_fixup(tree, node); +} + +bool rbtree_insert(rb_tree_t *tree, void *key, void *value) +{ + if (tree == NULL || key == NULL || value == NULL) { + ERROR("tree, key or value is empty!"); + return false; + } + + // unique key + if (rbtree_find(tree, key) != tree->nil) { + ERROR("the key already existed in rb tree!"); + return false; + } + rb_node_t *node = rbtree_create_node(key, value, tree->nil, tree->nil, tree->nil); + if (node == NULL) { + ERROR("failed to create rb tree node"); + return false; + } + insert_node(tree, node); + return true; +} + +bool rbtree_replace(rb_tree_t *tree, void *key, void *value) +{ + rb_node_t *node = NULL; + + if (tree == NULL || key == NULL || value == NULL) { + ERROR("tree, key or value is empty!"); + return false; + } + + // if not find, then insert + node = rbtree_find(tree, key); + if (node == tree->nil) { + return rbtree_insert(tree, key, value); + } + + if (tree->kvfreer != NULL) { + tree->kvfreer(key, node->value); + } + node->value = value; + + return true; +} + +static rb_node_t *rbtree_minimum(rb_tree_t *tree, rb_node_t *node) +{ + if (node == tree->nil) { + return tree->nil; + } + while (node->left != tree->nil) { + node = node->left; + } + return node; +} + +static rb_node_t *rbtree_maximum(rb_tree_t *tree, rb_node_t *node) +{ + if (node == tree->nil) { + return tree->nil; + } + while (node->right != tree->nil) { + node = node->right; + } + return node; +} + +void do_with_left_node_not_null(rb_tree_t *tree, rb_node_t **node) +{ + rb_node_t *sibling = (*node)->parent->right; + if (sibling->colour == RED) { + (*node)->parent->colour = RED; + sibling->colour = BLACK; + left_rot(tree, (*node)->parent); + sibling = (*node)->parent->right; + } + + bool flag = ((sibling->left == tree->nil || sibling->left->colour == BLACK) && + (sibling->right == tree->nil || sibling->right->colour == BLACK)); + if (flag) { + sibling->colour = RED; + (*node) = (*node)->parent; + return; + } + + flag = (sibling->right == tree->nil || sibling->right->colour == BLACK); + if (flag) { + sibling->colour = RED; + sibling->left->colour = BLACK; + right_rot(tree, sibling); + sibling = (*node)->parent->right; + } + sibling->colour = (*node)->parent->colour; + (*node)->parent->colour = BLACK; + sibling->right->colour = BLACK; + left_rot(tree, (*node)->parent); + (*node) = tree->root; +} + +void do_with_left_node_null(rb_tree_t *tree, rb_node_t **node) +{ + rb_node_t *sibling = (*node)->parent->left; + if (sibling->colour == RED) { + sibling->colour = BLACK; + (*node)->parent->colour = RED; + right_rot(tree, (*node)->parent); + sibling = (*node)->parent->left; + } + + bool flag = ((sibling->right == tree->nil || sibling->right->colour == BLACK) && + (sibling->left == tree->nil || sibling->left->colour == BLACK)); + + if (flag) { + sibling->colour = RED; + (*node) = (*node)->parent; + return; + } + + flag = (sibling->left == tree->nil || sibling->left->colour == BLACK); + if (flag) { + sibling->colour = RED; + sibling->right->colour = BLACK; + left_rot(tree, sibling); + sibling = (*node)->parent->left; + } + sibling->colour = (*node)->parent->colour; + (*node)->parent->colour = BLACK; + sibling->left->colour = BLACK; + right_rot(tree, (*node)->parent); + (*node) = tree->root; +} + +static void rbtree_erase_fixup(rb_tree_t *tree, rb_node_t *node) +{ + while ((node == tree->nil || node->colour == BLACK) && node != tree->root) { + if (node == node->parent->left) { + do_with_left_node_not_null(tree, &node); + } else { + do_with_left_node_null(tree, &node); + } + } + if (node != NULL) { + node->colour = BLACK; + } +} + +static void rbtree_transplant(rb_tree_t *tree, rb_node_t *u, rb_node_t *v) +{ + if (u->parent == tree->nil) { + tree->root = v; + } else if (u == u->parent->left) { + u->parent->left = v; + } else { + u->parent->right = v; + } + v->parent = u->parent; +} + +static void rbtree_erase(rb_tree_t *tree, rb_node_t *node) +{ + rb_node_t *x = NULL; + rb_node_t *y = node; + rbtree_colour y_original_colour = y->colour; + if (node->left == tree->nil) { + x = node->right; + rbtree_transplant(tree, node, node->right); + } else if (node->right == tree->nil) { + x = node->left; + rbtree_transplant(tree, node, node->left); + } else { + y = rbtree_minimum(tree, node->right); + y_original_colour = y->colour; + x = y->right; + if (y->parent == node) { + x->parent = y; + } else { + rbtree_transplant(tree, y, y->right); + y->right = node->right; + y->right->parent = y; + } + rbtree_transplant(tree, node, y); + y->left = node->left; + y->left->parent = y; + y->colour = node->colour; + } + + if (y_original_colour == BLACK) { + rbtree_erase_fixup(tree, x); + } + + if (tree->kvfreer != NULL) { + tree->kvfreer(node->key, node->value); + } + free(node); +} + +bool rbtree_remove(rb_tree_t *tree, void *key) +{ + if (tree == NULL || key == NULL) { + return false; + } + rb_node_t *node = rbtree_find(tree, key); + if (node == tree->nil) { + ERROR("no such key in rb tree"); + return false; + } + + rbtree_erase(tree, node); + return true; +} + +static size_t number_of_nodes(const rb_tree_t *tree, const rb_node_t *node) +{ + size_t count = 0; + if (node == tree->nil) { + return 0; + } + count = 1 + number_of_nodes(tree, node->left) + number_of_nodes(tree, node->right); + return count; +} +size_t rbtree_size(const rb_tree_t *tree) +{ + if (tree == NULL) { + return 0; + } + return number_of_nodes(tree, tree->root); +} + +rb_iterator_t *rbtree_iterator_new(rb_tree_t *tree) +{ + if (tree == NULL) { + return NULL; + } + rb_iterator_t *itor = NULL; + itor = util_common_calloc_s(sizeof(rb_iterator_t)); + if (itor == NULL) { + ERROR("failed to alloc memory"); + return NULL; + } + itor->tree = tree; + (void)rbtree_iterator_first(itor); + return itor; +} + +void rbtree_iterator_free(rb_iterator_t *itor) +{ + if (itor != NULL) { + free(itor); + } +} + +bool rbtree_iterator_valid(const rb_iterator_t *itor) +{ + if (itor == NULL) { + return false; + } + return itor->node != itor->tree->nil; +} + +static rb_node_t *rbtree_successor(rb_tree_t *tree, rb_node_t *node) +{ + if (tree == NULL || node == NULL) { + return NULL; + } + if (node->right != tree->nil) { + return rbtree_minimum(tree, node->right); + } + rb_node_t *successor = node->parent; + while (successor != tree->nil && node == successor->right) { + node = successor; + successor = successor->parent; + } + return successor; +} + +bool rbtree_iterator_next(rb_iterator_t *itor) +{ + if (itor == NULL) { + return false; + } + + return (itor->node != itor->tree->nil) && + (itor->node = rbtree_successor(itor->tree, itor->node)) != itor->tree->nil; +} + +static rb_node_t *rbtree_predecessor(rb_tree_t *tree, rb_node_t *node) +{ + if (node->left != tree->nil) { + return rbtree_maximum(tree, node->left); + } + rb_node_t *predecessor = node->parent; + while (predecessor != tree->nil && node == predecessor->left) { + node = predecessor; + predecessor = predecessor->parent; + } + return predecessor; +} + +bool rbtree_iterator_prev(rb_iterator_t *itor) +{ + if (itor == NULL) { + return false; + } + return (itor->node != itor->tree->nil) && + (itor->node = rbtree_predecessor(itor->tree, itor->node)) != itor->tree->nil; +} + +bool rbtree_iterator_first(rb_iterator_t *itor) +{ + if (itor == NULL) { + return false; + } + return (itor->node = rbtree_minimum(itor->tree, itor->tree->root)) != itor->tree->nil; +} + +bool rbtree_iterator_last(rb_iterator_t *itor) +{ + if (itor == NULL) { + return false; + } + return (itor->node = rbtree_maximum(itor->tree, itor->tree->root)) != itor->tree->nil; +} + +void *rbtree_iterator_key(rb_iterator_t *itor) +{ + if (itor == NULL) { + return NULL; + } + return itor->node ? itor->node->key : NULL; +} + +void *rbtree_iterator_value(rb_iterator_t *itor) +{ + if (itor == NULL) { + return NULL; + } + return itor->node ? itor->node->value : NULL; +} diff --git a/src/map/rb_tree.h b/src/map/rb_tree.h new file mode 100644 index 0000000..14d8648 --- /dev/null +++ b/src/map/rb_tree.h @@ -0,0 +1,82 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide rb tree definition + ******************************************************************************/ +#ifndef __RB_TREE_H_ +#define __RB_TREE_H_ + +#include +#include + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +typedef int (*key_comparator)(const void *, const void *); +typedef void (*key_value_freer)(void *, void *); + +typedef enum { RED = 0, BLACK } rbtree_colour; + +typedef struct rb_node { + void *key; + void *value; + rbtree_colour colour; + struct rb_node *left; + struct rb_node *right; + struct rb_node *parent; +} rb_node_t; + +typedef struct rb_tree { + rb_node_t *root; + key_comparator comparator; + key_value_freer kvfreer; + rb_node_t *nil; +} rb_tree_t; + +typedef struct rb_iterator { + rb_tree_t *tree; + rb_node_t *node; +} rb_iterator_t; + +int rbtree_ptr_cmp(const void *first, const void *last); +int rbtree_int_cmp(const void *first, const void *last); +int rbtree_str_cmp(const void *first, const void *last); + +rb_tree_t *rbtree_new(key_comparator comparator, key_value_freer kvfreer); +void rbtree_clear(rb_tree_t *tree); +void rbtree_free(rb_tree_t *tree); +bool rbtree_insert(rb_tree_t *tree, void *key, void *value); +bool rbtree_replace(rb_tree_t *tree, void *key, void *value); +bool rbtree_remove(rb_tree_t *tree, void *key); +void rbtree_destroy(rb_tree_t *tree); +rb_node_t *rbtree_find(rb_tree_t *tree, void *key); +void *rbtree_search(rb_tree_t *tree, void *key); +void rbtree_inorder(rb_tree_t *tree); +void print_rbtree(rb_tree_t *tree); +size_t rbtree_size(const rb_tree_t *tree); + +rb_iterator_t *rbtree_iterator_new(rb_tree_t *tree); +void rbtree_iterator_free(rb_iterator_t *itor); +bool rbtree_iterator_valid(const rb_iterator_t *itor); +bool rbtree_iterator_next(rb_iterator_t *itor); +bool rbtree_iterator_prev(rb_iterator_t *itor); +bool rbtree_iterator_first(rb_iterator_t *itor); +bool rbtree_iterator_last(rb_iterator_t *itor); +void *rbtree_iterator_key(rb_iterator_t *itor); +void *rbtree_iterator_value(rb_iterator_t *itor); + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* __RB_TREE_H_ */ diff --git a/src/namespace.c b/src/namespace.c new file mode 100644 index 0000000..45c1212 --- /dev/null +++ b/src/namespace.c @@ -0,0 +1,98 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide namespace functions + ******************************************************************************/ +#include "namespace.h" +#include +#include + +#include "log.h" +#include "utils.h" +#include "containers_store.h" + + +static char *connected_container(const char *mode) +{ + const char *p = mode != NULL ? (mode + strlen(SHARE_NAMESPACE_PREFIX)) : NULL; + + if (is_container(mode)) { + return util_strdup_s(p); + } + + return NULL; +} + +static char *get_host_namespace_path(const char *type) +{ + if (type == NULL) { + return NULL; + } + if (strcmp(type, TYPE_NAMESPACE_PID) == 0) { + return util_strdup_s(SHARE_NAMESPACE_PID_HOST_PATH); + } else if (strcmp(type, TYPE_NAMESPACE_NETWORK) == 0) { + return util_strdup_s(SHARE_NAMESPACE_NET_HOST_PATH); + } else if (strcmp(type, TYPE_NAMESPACE_IPC) == 0) { + return util_strdup_s(SHARE_NAMESPACE_IPC_HOST_PATH); + } else if (strcmp(type, TYPE_NAMESPACE_UTS) == 0) { + return util_strdup_s(SHARE_NAMESPACE_UTS_HOST_PATH); + } else if (strcmp(type, TYPE_NAMESPACE_MOUNT) == 0) { + return util_strdup_s(SHARE_NAMESPACE_MNT_HOST_PATH); + } else if (strcmp(type, TYPE_NAMESPACE_USER) == 0) { + return util_strdup_s(SHARE_NAMESPACE_USER_HOST_PATH); + } else if (strcmp(type, TYPE_NAMESPACE_CGROUP) == 0) { + return util_strdup_s(SHARE_NAMESPACE_CGROUP_HOST_PATH); + } + return NULL; +} + +static char *parse_share_namespace_with_prefix(const char *path) +{ + char *tmp_cid = NULL; + char *result = NULL; + container_t *cont = NULL; + + if (path == NULL) { + return NULL; + } + + tmp_cid = connected_container(path); + if (tmp_cid == NULL) { + goto out; + } + cont = containers_store_get(tmp_cid); + if (cont == NULL) { + ERROR("Invalid share path: %s", path); + goto out; + } + result = util_strdup_s(cont->common_config->id); + container_unref(cont); + +out: + free(tmp_cid); + return result; +} + +char *get_share_namespace_path(const char *type, const char *src_path) +{ + char *tmp_mode = NULL; + + if (is_none(src_path)) { + tmp_mode = NULL; + } else if (is_host(src_path)) { + tmp_mode = get_host_namespace_path(type); + } else if (is_container(src_path)) { + tmp_mode = parse_share_namespace_with_prefix(src_path); + } + + return tmp_mode; +} diff --git a/src/namespace.h b/src/namespace.h new file mode 100644 index 0000000..0c1f906 --- /dev/null +++ b/src/namespace.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide namespace definition + ******************************************************************************/ +#ifndef __NAMESPACE_H +#define __NAMESPACE_H + +#include +#include + +#define SHARE_NAMESPACE_PREFIX "container:" +#define SHARE_NAMESPACE_HOST "host" +#define SHARE_NAMESPACE_NONE "none" + +#define SHARE_NAMESPACE_PID_HOST_PATH "/proc/1/ns/pid" +#define SHARE_NAMESPACE_NET_HOST_PATH "/proc/1/ns/net" +#define SHARE_NAMESPACE_IPC_HOST_PATH "/proc/1/ns/ipc" +#define SHARE_NAMESPACE_UTS_HOST_PATH "/proc/1/ns/uts" +#define SHARE_NAMESPACE_MNT_HOST_PATH "/proc/1/ns/mnt" +#define SHARE_NAMESPACE_USER_HOST_PATH "/proc/1/ns/user" +#define SHARE_NAMESPACE_CGROUP_HOST_PATH "/proc/1/ns/cgroup" + + +#define TYPE_NAMESPACE_PID "pid" +#define TYPE_NAMESPACE_NETWORK "network" +#define TYPE_NAMESPACE_IPC "ipc" +#define TYPE_NAMESPACE_UTS "uts" +#define TYPE_NAMESPACE_MOUNT "mount" +#define TYPE_NAMESPACE_USER "user" +#define TYPE_NAMESPACE_CGROUP "cgroup" + +#define ETC_HOSTS "/etc/hosts" +#define RESOLV_CONF_PATH "/etc/resolv.conf" + +static inline bool is_host(const char *mode) +{ + if (mode != NULL && strcmp(mode, SHARE_NAMESPACE_HOST) == 0) { + return true; + } + return false; +} + +static inline bool is_none(const char *mode) +{ + if (mode != NULL && strcmp(mode, SHARE_NAMESPACE_NONE) == 0) { + return true; + } + return false; +} + +static inline bool is_container(const char *mode) +{ + if (mode != NULL && + strncmp(mode, SHARE_NAMESPACE_PREFIX, strlen(SHARE_NAMESPACE_PREFIX)) == 0) { + return true; + } + return false; +} + +char *get_share_namespace_path(const char *type, const char *src_path); + +#endif diff --git a/src/pack_config.c b/src/pack_config.c new file mode 100644 index 0000000..d2edf28 --- /dev/null +++ b/src/pack_config.c @@ -0,0 +1,2154 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container package configure functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "pack_config.h" +#include "container_custom_config.h" +#include "host_config.h" +#include "utils.h" +#include "parse_common.h" +#include "path.h" + +static bool parse_restart_policy(const char *policy, host_config_restart_policy **rp) +{ + bool ret = false; + char *dup = NULL; + char *dotpos = NULL; + + if (rp == NULL || policy == NULL) { + return true; + } + + dup = util_strdup_s(policy); + + *rp = util_common_calloc_s(sizeof(host_config_restart_policy)); + if (*rp == NULL) { + ERROR("Restart policy: Out of memory"); + goto cleanup; + } + + dotpos = strchr(dup, ':'); + if (dotpos != NULL) { + int nret; + *dotpos++ = '\0'; + if (strchr(dotpos, ':') != NULL) { + COMMAND_ERROR("Invalid restart policy format"); + goto cleanup; + } + nret = util_safe_int(dotpos, &(*rp)->maximum_retry_count); + if (nret != 0) { + COMMAND_ERROR("Maximum retry count must be an integer: %s", strerror(-nret)); + goto cleanup; + } + } + + (*rp)->name = util_strdup_s(dup); + ret = true; +cleanup: + free(dup); + return ret; +} + +static int pack_host_config_ns_change_files(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + if (dstconfig == NULL || srcconfig == NULL) { + return -1; + } + + if (srcconfig->ns_change_files_len != 0 && srcconfig->ns_change_files != NULL) { + if (srcconfig->ns_change_files_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many capabilities to add!"); + ret = -1; + goto out; + } + dstconfig->ns_change_files = util_common_calloc_s(srcconfig->ns_change_files_len * sizeof(char *)); + if (dstconfig->ns_change_files == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->ns_change_files_len; i++) { + dstconfig->ns_change_files[dstconfig->ns_change_files_len] = util_strdup_s(srcconfig->ns_change_files[i]); + dstconfig->ns_change_files_len++; + } + } + +out: + return ret; +} + +static int pack_host_config_cap_add(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + /* cap-add */ + if (srcconfig->cap_add_len != 0 && srcconfig->cap_add != NULL) { + if (srcconfig->cap_add_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many capabilities to add!"); + ret = -1; + goto out; + } + dstconfig->cap_add = util_common_calloc_s(srcconfig->cap_add_len * sizeof(char *)); + if (dstconfig->cap_add == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->cap_add_len; i++) { + dstconfig->cap_add[dstconfig->cap_add_len] = util_strdup_s(srcconfig->cap_add[i]); + dstconfig->cap_add_len++; + } + } + + for (i = 0; i < dstconfig->cap_add_len; i++) { + // skip `all` + if (strcasecmp(dstconfig->cap_add[i], "all") == 0) { + continue; + } + + if (!util_valid_cap(dstconfig->cap_add[i])) { + COMMAND_ERROR("Unknown capability to add: '%s'", dstconfig->cap_add[i]); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int pack_host_config_cap_drop(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + /* cap-drops */ + if (srcconfig->cap_drop_len != 0 && srcconfig->cap_drop != NULL) { + if (srcconfig->cap_drop_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many capabilities to drop!"); + ret = -1; + goto out; + } + dstconfig->cap_drop = util_common_calloc_s(srcconfig->cap_drop_len * sizeof(char *)); + if (dstconfig->cap_drop == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->cap_drop_len; i++) { + dstconfig->cap_drop[dstconfig->cap_drop_len] = util_strdup_s(srcconfig->cap_drop[i]); + dstconfig->cap_drop_len++; + } + } + + for (i = 0; i < dstconfig->cap_drop_len; i++) { + // skip `all` + if (strcasecmp(dstconfig->cap_drop[i], "all") == 0) { + continue; + } + + if (!util_valid_cap(dstconfig->cap_drop[i])) { + COMMAND_ERROR("Unknown capability to drop: '%s'", dstconfig->cap_drop[i]); + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int pack_host_config_caps(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + + ret = pack_host_config_cap_add(dstconfig, srcconfig); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = pack_host_config_cap_drop(dstconfig, srcconfig); + if (ret != 0) { + ret = -1; + goto out; + } + +out: + return ret; +} + +static int pack_host_network_extra_hosts(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + /* extra hosts */ + if (srcconfig->extra_hosts_len != 0 && srcconfig->extra_hosts != NULL) { + if (srcconfig->extra_hosts_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many extra hosts to add!"); + ret = -1; + goto out; + } + dstconfig->extra_hosts = util_common_calloc_s(srcconfig->extra_hosts_len * sizeof(char *)); + if (dstconfig->extra_hosts == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->extra_hosts_len; i++) { + dstconfig->extra_hosts[dstconfig->extra_hosts_len] = util_strdup_s(srcconfig->extra_hosts[i]); + dstconfig->extra_hosts_len++; + } + } +out: + return ret; +} + +static int pack_host_network_dns(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + /* dns */ + if (srcconfig->dns_len != 0 && srcconfig->dns != NULL) { + if (srcconfig->dns_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many dns to add!"); + ret = -1; + goto out; + } + dstconfig->dns = util_common_calloc_s(srcconfig->dns_len * sizeof(char *)); + if (dstconfig->dns == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->dns_len; i++) { + dstconfig->dns[dstconfig->dns_len] = util_strdup_s(srcconfig->dns[i]); + dstconfig->dns_len++; + } + } + +out: + return ret; +} + +static int pack_host_network_dns_options(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + /* dns options */ + if (srcconfig->dns_options_len != 0 && srcconfig->dns_options != NULL) { + if (srcconfig->dns_options_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many dns options to add!"); + ret = -1; + goto out; + } + dstconfig->dns_options = util_common_calloc_s(srcconfig->dns_options_len * sizeof(char *)); + if (dstconfig->dns_options == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->dns_options_len; i++) { + dstconfig->dns_options[dstconfig->dns_options_len] = util_strdup_s(srcconfig->dns_options[i]); + dstconfig->dns_options_len++; + } + } + +out: + return ret; +} + +static int pack_host_network_dns_search(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + /* dns search */ + if (srcconfig->dns_search_len != 0 && srcconfig->dns_search != NULL) { + if (srcconfig->dns_search_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many dns search to add!"); + ret = -1; + goto out; + } + dstconfig->dns_search = util_common_calloc_s(srcconfig->dns_search_len * sizeof(char *)); + if (dstconfig->dns_search == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->dns_search_len; i++) { + dstconfig->dns_search[dstconfig->dns_search_len] = util_strdup_s(srcconfig->dns_search[i]); + dstconfig->dns_search_len++; + } + } + +out: + return ret; +} + +static int pack_host_config_network(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + + if (dstconfig == NULL) { + return -1; + } + + ret = pack_host_network_extra_hosts(dstconfig, srcconfig); + if (ret != 0) { + goto out; + } + + ret = pack_host_network_dns(dstconfig, srcconfig); + if (ret != 0) { + goto out; + } + + ret = pack_host_network_dns_options(dstconfig, srcconfig); + if (ret != 0) { + goto out; + } + + ret = pack_host_network_dns_search(dstconfig, srcconfig); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +static int check_parsed_device(const host_config_devices_element *device_map) +{ + int ret = 0; + + if (device_map->path_on_host == NULL || device_map->path_in_container == NULL || + device_map->cgroup_permissions == NULL) { + ret = -1; + goto out; + } + + if (!util_file_exists(device_map->path_on_host)) { + COMMAND_ERROR( + "Error gathering device information while adding device \"%s\",stat %s:no such file or directory", + device_map->path_on_host, device_map->path_on_host); + ret = -1; + goto out; + } + +out: + return ret; +} + +static host_config_devices_element *parse_device(const char *devices) +{ + char **tmp_str = NULL; + size_t tmp_str_len = 0; + host_config_devices_element *device_map = NULL; + + if (devices == NULL || !strcmp(devices, "")) { + ERROR("devices can't be empty"); + return NULL; + } + + device_map = util_common_calloc_s(sizeof(host_config_devices_element)); + if (device_map == NULL) { + ERROR("Out of memory"); + return NULL; + } + + tmp_str = util_string_split(devices, ':'); + tmp_str_len = util_array_len(tmp_str); + + switch (tmp_str_len) { + case 3: + device_map->path_on_host = util_strdup_s(tmp_str[0]); + device_map->path_in_container = util_strdup_s(tmp_str[1]); + if (util_valid_device_mode(tmp_str[2])) { + device_map->cgroup_permissions = util_strdup_s(tmp_str[2]); + } + break; + case 2: + device_map->path_on_host = util_strdup_s(tmp_str[0]); + if (util_valid_device_mode(tmp_str[1])) { + device_map->path_in_container = util_strdup_s(tmp_str[0]); + device_map->cgroup_permissions = util_strdup_s(tmp_str[1]); + } else { + device_map->path_in_container = util_strdup_s(tmp_str[1]); + device_map->cgroup_permissions = util_strdup_s("rwm"); + } + break; + case 1: + device_map->path_on_host = util_strdup_s(tmp_str[0]); + device_map->path_in_container = util_strdup_s(tmp_str[0]); + device_map->cgroup_permissions = util_strdup_s("rwm"); + break; + default: + ERROR("Invalid parament %s", devices); + break; + } + + util_free_array(tmp_str); + + if (check_parsed_device(device_map) != 0) { + goto erro_out; + } + + return device_map; + +erro_out: + free_host_config_devices_element(device_map); + return NULL; +} + +static int check_ulimit_input(const char *val) +{ + int ret = 0; + if (val == NULL || strcmp(val, "") == 0) { + COMMAND_ERROR("ulimit argument can't be empty"); + ret = -1; + goto out; + } + + if (val[0] == '=' || val[strlen(val) - 1] == '=') { + COMMAND_ERROR("Invalid ulimit argument: \"%s\", delimiter '=' can't" + " be the first or the last character", val); + ret = -1; + } + +out: + return ret; +} + +static void get_ulimit_split_parts(const char *val, char ***parts, size_t *parts_len, char deli) +{ + *parts = util_string_split_multi(val, deli); + if (*parts == NULL) { + COMMAND_ERROR("Out of memory"); + return; + } + *parts_len = util_array_len(*parts); +} + +static int parse_soft_hard_ulimit(const char *val, char **limitvals, size_t limitvals_len, int64_t *soft, int64_t *hard) +{ + int ret = 0; + // parse soft + ret = util_safe_llong(limitvals[0], (long long *)soft); + if (ret < 0) { + COMMAND_ERROR("Invalid ulimit soft value: \"%s\", parse int64 failed: %s", val, strerror(-ret)); + ret = -1; + goto out; + } + + // parse hard if exists + if (limitvals_len > 1) { + ret = util_safe_llong(limitvals[1], (long long *)hard); + if (ret < 0) { + COMMAND_ERROR("Invalid ulimit hard value: \"%s\", parse int64 failed: %s", val, strerror(-ret)); + ret = -1; + goto out; + } + + if (*soft > *hard) { + COMMAND_ERROR("Ulimit soft limit must be less than or equal to hard limit: %lld > %lld", + (long long int)(*soft), (long long int)(*hard)); + ret = -1; + goto out; + } + } else { + *hard = *soft; // default to soft in case no hard was set + } +out: + return ret; +} + +static int check_ulimit_type(const char *type) +{ + int ret = 0; + char **tmptype = NULL; + char *ulimit_valid_type[] = { + // "as", // Disabled since this doesn't seem usable with the way Docker inits a container. + "core", "cpu", "data", "fsize", "locks", "memlock", "msgqueue", "nice", + "nofile", "nproc", "rss", "rtprio", "rttime", "sigpending", "stack", NULL + }; + + for (tmptype = ulimit_valid_type; *tmptype != NULL; tmptype++) { + if (strcmp(type, *tmptype) == 0) { + break; + } + } + + if (*tmptype == NULL) { + COMMAND_ERROR("Invalid ulimit type: %s", type); + ret = -1; + } + return ret; +} + +static host_config_ulimits_element *parse_ulimit(const char *val) +{ + int ret = 0; + int64_t soft = 0; + int64_t hard = 0; + size_t parts_len = 0; + size_t limitvals_len = 0; + char **parts = NULL; + char **limitvals = NULL; + host_config_ulimits_element *ulimit = NULL; + + ret = check_ulimit_input(val); + if (ret != 0) { + return NULL; + } + + get_ulimit_split_parts(val, &parts, &parts_len, '='); + if (parts == NULL) { + ERROR("Out of memory"); + return NULL; + } else if (parts_len != 2) { + COMMAND_ERROR("Invalid ulimit argument: %s", val); + ret = -1; + goto out; + } + + ret = check_ulimit_type(parts[0]); + if (ret != 0) { + ret = -1; + goto out; + } + + if (parts[1][0] == ':' || parts[1][strlen(parts[1]) - 1] == ':') { + COMMAND_ERROR("Invalid ulimit value: \"%s\", delimiter ':' can't be the first" + " or the last character", val); + ret = -1; + goto out; + } + + // parse value + get_ulimit_split_parts(parts[1], &limitvals, &limitvals_len, ':'); + if (limitvals == NULL) { + ret = -1; + goto out; + } + + if (limitvals_len > 2) { + COMMAND_ERROR("Too many limit value arguments - %s, can only have up to two, `soft[:hard]`", + parts[1]); + ret = -1; + goto out; + } + + ret = parse_soft_hard_ulimit(val, limitvals, limitvals_len, &soft, &hard); + if (ret < 0) { + goto out; + } + + ulimit = util_common_calloc_s(sizeof(host_config_ulimits_element)); + if (ulimit == NULL) { + ret = -1; + goto out; + } + ulimit->name = util_strdup_s(parts[0]); + ulimit->hard = hard; + ulimit->soft = soft; + +out: + util_free_array(parts); + util_free_array(limitvals); + if (ret != 0) { + free_host_config_ulimits_element(ulimit); + ulimit = NULL; + } + + return ulimit; +} + +static void pack_cgroup_resources_cpu(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + /* cgroup blkio weight */ + if (srcconfig->cr->blkio_weight) { + dstconfig->blkio_weight = srcconfig->cr->blkio_weight; + } + + /* cpu shares */ + if (srcconfig->cr->cpu_shares) { + dstconfig->cpu_shares = srcconfig->cr->cpu_shares; + } + + /* cpu period */ + if (srcconfig->cr->cpu_period) { + dstconfig->cpu_period = srcconfig->cr->cpu_period; + } + + /* cpu quota */ + if (srcconfig->cr->cpu_quota) { + dstconfig->cpu_quota = srcconfig->cr->cpu_quota; + } + + /* cpuset-cpus */ + if (util_valid_str(srcconfig->cr->cpuset_cpus)) { + dstconfig->cpuset_cpus = util_strdup_s(srcconfig->cr->cpuset_cpus); + } + + /* cpuset mems */ + if (util_valid_str(srcconfig->cr->cpuset_mems)) { + dstconfig->cpuset_mems = util_strdup_s(srcconfig->cr->cpuset_mems); + } + + if (srcconfig->cr->cpu_realtime_period) { + dstconfig->cpu_realtime_period = srcconfig->cr->cpu_realtime_period; + } + + if (srcconfig->cr->cpu_realtime_runtime) { + dstconfig->cpu_realtime_runtime = srcconfig->cr->cpu_realtime_runtime; + } +} + +static void pack_cgroup_resources_mem(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + /* memory limit */ + if (srcconfig->cr->memory) { + dstconfig->memory = srcconfig->cr->memory; + } + + /* memory swap */ + if (srcconfig->cr->memory_swap) { + dstconfig->memory_swap = srcconfig->cr->memory_swap; + } + + /* memory reservation */ + if (srcconfig->cr->memory_reservation) { + dstconfig->memory_reservation = srcconfig->cr->memory_reservation; + } + + /* kernel memory limit */ + if (srcconfig->cr->kernel_memory) { + dstconfig->kernel_memory = srcconfig->cr->kernel_memory; + } +} + +static void pack_cgroup_resources(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + pack_cgroup_resources_cpu(dstconfig, srcconfig); + + pack_cgroup_resources_mem(dstconfig, srcconfig); + + /* cgroup limit */ + dstconfig->pids_limit = srcconfig->cr->pids_limit; + dstconfig->files_limit = srcconfig->cr->files_limit; + + /* oom score adj*/ + dstconfig->oom_score_adj = (int)srcconfig->cr->oom_score_adj; +} + +static int pack_hostconfig_ulimits(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i; + + if (srcconfig->ulimits == NULL || srcconfig->ulimits_len == 0) { + goto out; + } + + if (srcconfig->ulimits_len > SIZE_MAX / sizeof(host_config_ulimits_element *)) { + COMMAND_ERROR("Too many ulimit elements in host config"); + ret = -1; + goto out; + } + dstconfig->ulimits = util_common_calloc_s(srcconfig->ulimits_len * sizeof(host_config_ulimits_element *)); + if (dstconfig->ulimits == NULL) { + COMMAND_ERROR("Out of memory"); + ret = -1; + goto out; + } + + for (i = 0; i < srcconfig->ulimits_len; i++) { + size_t j; + bool exists = false; + host_config_ulimits_element *tmp = NULL; + + tmp = parse_ulimit(srcconfig->ulimits[i]); + if (tmp == NULL) { + ret = -1; + goto out; + } + for (j = 0; j < dstconfig->ulimits_len; j++) { + if (strcmp(dstconfig->ulimits[j]->name, tmp->name) == 0) { + exists = true; + break; + } + } + if (exists) { + free_host_config_ulimits_element(dstconfig->ulimits[j]); + dstconfig->ulimits[j] = tmp; + } else { + dstconfig->ulimits[dstconfig->ulimits_len] = tmp; + dstconfig->ulimits_len++; + } + } +out: + return ret; +} + +static int pack_hostconfig_cgroup(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + + if (srcconfig->cr != NULL) { + pack_cgroup_resources(dstconfig, srcconfig); + } + + ret = pack_hostconfig_ulimits(dstconfig, srcconfig); + + return ret; +} + +static host_config_blkio_weight_device_element *pack_blkio_weight_devices(const char *devices) +{ + char **tmp_str = NULL; + unsigned int weight = 0; + size_t tmp_str_len = 0; + host_config_blkio_weight_device_element *weight_dev = NULL; + + if (devices == NULL || !strcmp(devices, "")) { + COMMAND_ERROR("Weight devices can't be empty"); + return NULL; + } + + weight_dev = util_common_calloc_s(sizeof(host_config_blkio_weight_device_element)); + if (weight_dev == NULL) { + ERROR("Out of memory"); + return NULL; + } + + tmp_str = util_string_split(devices, ':'); + if (tmp_str == NULL) { + COMMAND_ERROR("String split failed"); + goto erro_out; + } + tmp_str_len = util_array_len(tmp_str); + + if (tmp_str_len != 2) { + COMMAND_ERROR("Bad blkio weight device format: %s", devices); + goto erro_out; + } + + if (strncmp("/dev/", tmp_str[0], strlen("/dev/")) != 0) { + COMMAND_ERROR("Bad format for device path: %s", devices); + goto erro_out; + } + + if (util_safe_uint(tmp_str[1], &weight)) { + COMMAND_ERROR("Invalid weight for device: %s", devices); + goto erro_out; + } + + if (weight > 0 && (weight < 10 || weight > 1000)) { + COMMAND_ERROR("Invalid weight for device: %s", devices); + goto erro_out; + } + + weight_dev->path = util_strdup_s(tmp_str[0]); + + weight_dev->weight = (uint16_t)weight; + util_free_array(tmp_str); + + return weight_dev; + +erro_out: + util_free_array(tmp_str); + free_host_config_blkio_weight_device_element(weight_dev); + return NULL; +} + +static int parse_blkio_throttle_bps_device(const char *device, char **path, const uint64_t *rate) +{ + int ret = 0; + char **split = NULL; + + split = util_string_split_multi(device, ':'); + if (split == NULL || util_array_len(split) != 2) { + COMMAND_ERROR("bad format: %s", device); + ret = -1; + goto out; + } + + if (strncmp(split[0], "/dev/", strlen("/dev/")) != 0) { + COMMAND_ERROR("bad format for device path: %s", device); + ret = -1; + goto out; + } + + if (util_parse_byte_size_string(split[1], (int64_t *)rate) != 0) { + COMMAND_ERROR("invalid rate for device: %s. The correct format is :[]." + " Number must be a positive integer. Unit is optional and can be kb, mb, or gb", device); + ret = -1; + goto out; + } + *path = util_strdup_s(split[0]); + +out: + util_free_array(split); + return ret; +} + +// validate that the specified string has a valid device-rate format. +static host_config_blkio_device_read_bps_element *pack_throttle_read_bps_device(const char *device) +{ + char *path = NULL; + uint64_t rate = 0; + host_config_blkio_device_read_bps_element *read_bps_dev = NULL; + + if (device == NULL || !strcmp(device, "")) { + COMMAND_ERROR("blkio throttle read bps device can't be empty"); + return NULL; + } + + read_bps_dev = util_common_calloc_s(sizeof(host_config_blkio_device_read_bps_element)); + if (read_bps_dev == NULL) { + ERROR("Out of memory"); + return NULL; + } + + if (parse_blkio_throttle_bps_device(device, &path, &rate) != 0) { + goto error_out; + } + + read_bps_dev->path = path; + read_bps_dev->rate = rate; + + return read_bps_dev; + +error_out: + free(path); + free_host_config_blkio_device_read_bps_element(read_bps_dev); + return NULL; +} + +// validate that the specified string has a valid device-rate format. +static host_config_blkio_device_write_bps_element *pack_throttle_write_bps_device(const char *device) +{ + char *path = NULL; + uint64_t rate = 0; + host_config_blkio_device_write_bps_element *write_bps_dev = NULL; + + if (device == NULL || !strcmp(device, "")) { + COMMAND_ERROR("blkio throttle write bps device can't be empty"); + return NULL; + } + + write_bps_dev = util_common_calloc_s(sizeof(host_config_blkio_device_write_bps_element)); + if (write_bps_dev == NULL) { + ERROR("Out of memory"); + return NULL; + } + + if (parse_blkio_throttle_bps_device(device, &path, &rate) != 0) { + goto error_out; + } + + write_bps_dev->path = path; + write_bps_dev->rate = rate; + + return write_bps_dev; + +error_out: + free(path); + free_host_config_blkio_device_write_bps_element(write_bps_dev); + return NULL; +} + +static int split_hugetlb_limit(char *temp, char **pagesize, char **limit_value) +{ + int ret = 0; + char *saveptr = NULL; + + if (strchr(temp, ':') == NULL) { + *limit_value = temp; + goto out; + } else if (temp[0] != ':') { + *pagesize = strtok_r(temp, ":", &saveptr); + if ((*pagesize) == NULL) { + ret = -1; + goto out; + } + *limit_value = strtok_r(NULL, ":", &saveptr); + if ((*limit_value) == NULL) { + ret = -1; + goto out; + } + } else { + *limit_value = strtok_r(temp, ":", &saveptr); + if ((*limit_value) == NULL) { + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static host_config_hugetlbs_element *pase_hugetlb_limit(const char *input) +{ + int ret; + char *temp = NULL; + char *pagesize = NULL; + char *limit_value = NULL; + char *trans_page = NULL; + uint64_t limit = 0; + uint64_t page = 0; + host_config_hugetlbs_element *limit_element = NULL; + + temp = util_strdup_s(input); + if (temp == NULL) { + ERROR("Out of memory"); + goto free_out; + } + + ret = split_hugetlb_limit(temp, &pagesize, &limit_value); + if (ret != 0) { + goto free_out; + } + + ret = util_parse_byte_size_string(limit_value, (int64_t *)(&limit)); + if (ret != 0) { + COMMAND_ERROR("Parse limit value: %s failed:%s", limit_value, strerror(-ret)); + goto free_out; + } + + if (pagesize != NULL) { + ret = util_parse_byte_size_string(pagesize, (int64_t *)(&page)); + if (ret != 0) { + COMMAND_ERROR("Parse pagesize error.Invalid hugepage size: %s: %s", pagesize, strerror(-ret)); + goto free_out; + } + + trans_page = util_human_size(page); + if (trans_page == NULL) { + COMMAND_ERROR("Failed to translate page size"); + goto free_out; + } + } + + limit_element = util_common_calloc_s(sizeof(host_config_hugetlbs_element)); + if (limit_element == NULL) { + ERROR("Out of memory"); + goto free_out; + } + + limit_element->limit = limit; + limit_element->page_size = trans_page ? util_strdup_s(trans_page) : util_strdup_s(""); + +free_out: + free(temp); + free(trans_page); + + return limit_element; +} + +uint64_t get_proc_mem_size(const char *item) +{ + uint64_t sysmem_limit = 0; + FILE *fp = NULL; + size_t len = 0; + char *line = NULL; + char *p = NULL; + + fp = util_fopen("/proc/meminfo", "r"); + if (fp == NULL) { + ERROR("Failed to open /proc/meminfo: %s", strerror(errno)); + return sysmem_limit; + } + + while (getline(&line, &len, fp) != -1) { + p = strchr(line, ' '); + if (p == NULL) { + goto out; + } + *p = '\0'; + p++; + if (strcmp(line, item) == 0) { + while (*p == ' ' || *p == '\t') { + p++; + } + if (*p == '\0') { + goto out; + } + sysmem_limit = strtoull(p, NULL, 0); + break; + } + } + +out: + fclose(fp); + free(line); + return sysmem_limit * SIZE_KB; +} + +static bool parse_host_path(const char *input, const char *token, host_config_host_channel *host_channel) +{ + char real_path[PATH_MAX] = { 0 }; + + if (strcmp(token, "") == 0) { + COMMAND_ERROR("Bad host channel format: %s", input); + return false; + } + if (token[0] != '/') { + COMMAND_ERROR("Host channel host path should be absolute: %s", token); + return false; + } + if (cleanpath(token, real_path, sizeof(real_path)) == NULL) { + ERROR("Failed to clean path: '%s'", token); + return false; + } + if (util_dir_exists(real_path)) { + COMMAND_ERROR("Host path '%s' already exists", real_path); + return false; + } + host_channel->path_on_host = util_strdup_s(real_path); + return true; +} + +static bool parse_container_path(const char *input, const char *token, host_config_host_channel *host_channel) +{ + char real_path[PATH_MAX] = { 0 }; + + if (strcmp(token, "") == 0) { + COMMAND_ERROR("Bad host channel format: %s", input); + return false; + } + if (token[0] != '/') { + COMMAND_ERROR("Host channel container path should be absolute: %s", token); + return false; + } + if (cleanpath(token, real_path, sizeof(real_path)) == NULL) { + ERROR("Failed to clean path: '%s'", token); + return false; + } + host_channel->path_in_container = util_strdup_s(real_path); + return true; +} + +static bool parse_mode(const char *input, const char *token, host_config_host_channel *host_channel) +{ + if (strcmp(token, "") == 0) { + host_channel->permissions = util_strdup_s("rw"); + return true; + } + if (!util_valid_mount_mode(token)) { + COMMAND_ERROR("Invalid mount mode for host channel: %s", input); + return false; + } + host_channel->permissions = util_strdup_s(token); + return true; +} + +static bool parse_size(const char *input, const char *token, host_config_host_channel *host_channel) +{ + uint64_t size = 0; + uint64_t mem_total_size = 0; + uint64_t mem_available_size = 0; + + if (strcmp(token, "") == 0) { + host_channel->size = 64 * SIZE_MB; + return true; + } + if (util_parse_byte_size_string(token, (int64_t *)(&size))) { + COMMAND_ERROR("Invalid size limit for host channel: %s", input); + return false; + } + if (size < HOST_CHANNLE_MIN_SIZE) { + COMMAND_ERROR("Invalid size, larger than 4KB is allowed"); + return false; + } + mem_total_size = get_proc_mem_size("MemTotal:"); + if (size > mem_total_size / 2) { + COMMAND_ERROR("Required host channel size %llu is larger than half of total memory %llu", + (unsigned long long)size, (unsigned long long)mem_total_size); + return false; + } + mem_available_size = get_proc_mem_size("MemAvailable:"); + if (size > mem_available_size) { + COMMAND_ERROR("Required host channel size %llu is larger than available memory %llu", (unsigned long long)size, + (unsigned long long)mem_available_size); + return false; + } + host_channel->size = size; + return true; +} + +host_config_host_channel *parse_host_channel(const char *input) +{ + int count = 0; + char *tmp_str = NULL; + char *save_ptr = NULL; + char *token = NULL; + host_config_host_channel *host_channel = NULL; + + host_channel = util_common_calloc_s(sizeof(host_config_host_channel)); + if (host_channel == NULL) { + ERROR("Out of memory"); + return NULL; + } + tmp_str = util_strdup_s(input); + save_ptr = tmp_str; + token = util_str_token(&tmp_str, ":"); + while (token != NULL) { + count++; + if (count > 4) { + COMMAND_ERROR("Bad host channel format: %s", input); + goto erro_out; + } + switch (count) { + case 1: + if (!parse_host_path(input, token, host_channel)) { + goto erro_out; + } + break; + case 2: + if (!parse_container_path(input, token, host_channel)) { + goto erro_out; + } + break; + case 3: + if (!parse_mode(input, token, host_channel)) { + goto erro_out; + } + break; + case 4: + if (!parse_size(input, token, host_channel)) { + goto erro_out; + } + break; + default: + break; + } + free(token); + token = util_str_token(&tmp_str, ":"); + } + if (count < 4) { + COMMAND_ERROR("Bad host channel format: %s", input); + goto erro_out; + } + free(save_ptr); + return host_channel; + +erro_out: + free(token); + free(save_ptr); + free_host_config_host_channel(host_channel); + return NULL; +} + +static int parse_seccomp(const lcrc_host_config_t *srcconfig, host_config *dstconfig) +{ + int ret = 0; + int nret = 0; + int seccomp_count = 0; + size_t i = 0; + size_t size = 0; + char *seccomp_file = NULL; + char *seccomp_json = NULL; + char *tmp_str = NULL; + docker_seccomp *seccomp_spec = NULL; + parser_error err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + + for (i = 0; i < srcconfig->security_len; i++) { + // add seccomp + if (strncmp(srcconfig->security[i], "seccomp=", strlen("seccomp=")) == 0) { + seccomp_file = srcconfig->security[i] + strlen("seccomp") + 1; + if (strcmp(seccomp_file, "unconfined") == 0) { + dstconfig->security_opt[seccomp_count++] = util_strdup_s(srcconfig->security[i]); + dstconfig->security_opt_len++; + continue; + } + seccomp_spec = get_seccomp_security_opt_spec(seccomp_file); + if (seccomp_spec == NULL) { + ERROR("Failed to parse docker format seccomp specification file \"%s\", error message: %s", + seccomp_file, err); + COMMAND_ERROR("failed to parse seccomp file: %s", seccomp_file); + ret = -1; + goto out; + } + + seccomp_json = docker_seccomp_generate_json(seccomp_spec, &ctx, &err); + if (seccomp_json == NULL) { + COMMAND_ERROR("failed to generate seccomp json!"); + ret = -1; + goto out; + } + + free_docker_seccomp(seccomp_spec); + seccomp_spec = NULL; + if (strlen(seccomp_json) > (SIZE_MAX - strlen("seccomp=")) - 1) { + COMMAND_ERROR("seccomp json is too big!"); + ret = -1; + goto out; + } + size = strlen("seccomp=") + strlen(seccomp_json) + 1; + tmp_str = util_common_calloc_s(size); + if (tmp_str == NULL) { + COMMAND_ERROR("out of memory"); + ret = -1; + goto out; + } + nret = sprintf_s(tmp_str, size, "seccomp=%s", seccomp_json); + if (nret < 0) { + COMMAND_ERROR("failed to sprintf buffer!"); + ret = -1; + goto out; + } + dstconfig->security_opt[seccomp_count++] = util_strdup_s(tmp_str); + dstconfig->security_opt_len++; + free(tmp_str); + tmp_str = NULL; + free(err); + err = NULL; + free(seccomp_json); + seccomp_json = NULL; + } + } +out: + free(seccomp_json); + free(tmp_str); + free_docker_seccomp(seccomp_spec); + free(err); + + return ret; +} + +static int parse_no_new_privileges(const lcrc_host_config_t *srcconfig, host_config *dstconfig) +{ + int ret = 0; + size_t i = 0; + size_t new_size; + size_t old_size; + char **tmp_security_opt = NULL; + + for (i = 0; i < srcconfig->security_len; i++) { + // no new privileges + if (strcmp(srcconfig->security[i], "no-new-privileges") == 0) { + if (dstconfig->security_opt_len > (SIZE_MAX / sizeof(char *)) - 1) { + COMMAND_ERROR("Out of memory"); + return -1; + } + new_size = (dstconfig->security_opt_len + 1) * sizeof(char *); + old_size = dstconfig->security_opt_len * sizeof(char *); + ret = mem_realloc((void **)(&tmp_security_opt), new_size, (void *)dstconfig->security_opt, old_size); + if (ret != 0) { + COMMAND_ERROR("Out of memory"); + return ret; + } + dstconfig->security_opt = tmp_security_opt; + dstconfig->security_opt[dstconfig->security_opt_len++] = util_strdup_s(srcconfig->security[i]); + break; + } + } + + return ret; +} + +int generate_storage_opts(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t j; + + if (srcconfig->storage_opts == NULL || dstconfig == NULL) { + goto out; + } + + (*dstconfig)->storage_opt = util_common_calloc_s(sizeof(json_map_string_string)); + if ((*dstconfig)->storage_opt == NULL) { + ret = -1; + goto out; + } + for (j = 0; j < srcconfig->storage_opts->len; j++) { + ret = append_json_map_string_string((*dstconfig)->storage_opt, + srcconfig->storage_opts->keys[j], + srcconfig->storage_opts->values[j]); + if (ret != 0) { + ERROR("Append map failed"); + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int generate_sysctls(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t j; + + if (srcconfig->sysctls == NULL || dstconfig == NULL) { + goto out; + } + + (*dstconfig)->sysctls = util_common_calloc_s(sizeof(json_map_string_string)); + if ((*dstconfig)->sysctls == NULL) { + ret = -1; + goto out; + } + for (j = 0; j < srcconfig->sysctls->len; j++) { + ret = append_json_map_string_string((*dstconfig)->sysctls, srcconfig->sysctls->keys[j], + srcconfig->sysctls->values[j]); + if (ret < 0) { + goto out; + } + } +out: + return ret; +} + +int generate_devices(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + if (srcconfig->devices == NULL || srcconfig->devices_len == 0) { + goto out; + } + + if (srcconfig->devices_len > SIZE_MAX / sizeof(host_config_devices_element *)) { + ERROR("Too many devices to be populated into container"); + ret = -1; + goto out; + } + (*dstconfig)->devices = util_common_calloc_s(sizeof(host_config_devices_element *) * srcconfig->devices_len); + if ((*dstconfig)->devices == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->devices_len; i++) { + (*dstconfig)->devices[i] = parse_device(srcconfig->devices[i]); + if ((*dstconfig)->devices[i] == NULL) { + ERROR("Failed to parse devices:%s", srcconfig->devices[i]); + ret = -1; + goto out; + } + + (*dstconfig)->devices_len++; + } +out: + return ret; +} + +static int generate_blkio_weight_device(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + if (srcconfig->blkio_weight_device == NULL || srcconfig->blkio_weight_device_len == 0) { + goto out; + } + + if (srcconfig->blkio_weight_device_len > SIZE_MAX / sizeof(host_config_blkio_weight_device_element *)) { + ERROR("Too many blkio weight devies to get!"); + ret = -1; + goto out; + } + + (*dstconfig)->blkio_weight_device = + util_common_calloc_s(srcconfig->blkio_weight_device_len * sizeof(host_config_blkio_weight_device_element *)); + if ((*dstconfig)->blkio_weight_device == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->blkio_weight_device_len; i++) { + (*dstconfig)->blkio_weight_device[(*dstconfig)->blkio_weight_device_len] = + pack_blkio_weight_devices(srcconfig->blkio_weight_device[i]); + if ((*dstconfig)->blkio_weight_device[(*dstconfig)->blkio_weight_device_len] == NULL) { + ERROR("Failed to get blkio weight devies"); + ret = -1; + goto out; + } + + (*dstconfig)->blkio_weight_device_len++; + } +out: + return ret; +} + +static int generate_blkio_throttle_read_bps_device(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + if (dstconfig == NULL || *dstconfig == NULL) { + goto out; + } + + if (srcconfig->blkio_throttle_read_bps_device == NULL || srcconfig->blkio_throttle_read_bps_device_len == 0) { + goto out; + } + + if (srcconfig->blkio_throttle_read_bps_device_len > + SIZE_MAX / sizeof(host_config_blkio_device_read_bps_element *)) { + ERROR("Too many blkio throttle read bps devies to get!"); + ret = -1; + goto out; + } + + (*dstconfig)->blkio_device_read_bps = + util_common_calloc_s(srcconfig->blkio_throttle_read_bps_device_len * + sizeof(host_config_blkio_device_read_bps_element *)); + if ((*dstconfig)->blkio_device_read_bps == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->blkio_throttle_read_bps_device_len; i++) { + (*dstconfig)->blkio_device_read_bps[(*dstconfig)->blkio_device_read_bps_len] = + pack_throttle_read_bps_device(srcconfig->blkio_throttle_read_bps_device[i]); + if ((*dstconfig)->blkio_device_read_bps[(*dstconfig)->blkio_device_read_bps_len] == NULL) { + ERROR("Failed to get blkio throttle read bps devices"); + ret = -1; + goto out; + } + + (*dstconfig)->blkio_device_read_bps_len++; + } +out: + return ret; +} + +static int generate_blkio_throttle_write_bps_device(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + if (dstconfig == NULL || *dstconfig == NULL) { + goto out; + } + + if (srcconfig->blkio_throttle_write_bps_device == NULL || srcconfig->blkio_throttle_write_bps_device_len == 0) { + goto out; + } + + + if (srcconfig->blkio_throttle_write_bps_device_len > + SIZE_MAX / sizeof(host_config_blkio_device_write_bps_element *)) { + ERROR("Too many blkio throttle write bps devies to get!"); + ret = -1; + goto out; + } + + (*dstconfig)->blkio_device_write_bps = util_common_calloc_s(srcconfig->blkio_throttle_write_bps_device_len * + sizeof(host_config_blkio_device_write_bps_element *)); + if ((*dstconfig)->blkio_device_write_bps == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->blkio_throttle_write_bps_device_len; i++) { + (*dstconfig)->blkio_device_write_bps[(*dstconfig)->blkio_device_write_bps_len] = + pack_throttle_write_bps_device(srcconfig->blkio_throttle_write_bps_device[i]); + if ((*dstconfig)->blkio_device_write_bps[(*dstconfig)->blkio_device_write_bps_len] == NULL) { + ERROR("Failed to get blkio throttle write bps devices"); + ret = -1; + goto out; + } + + (*dstconfig)->blkio_device_write_bps_len++; + } +out: + return ret; +} + +static int generate_blkio(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret; + + /* blkio weight devies */ + ret = generate_blkio_weight_device(dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + /* blkio throttle read bps devies */ + ret = generate_blkio_throttle_read_bps_device(dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + /* blkio throttle write bps devies */ + ret = generate_blkio_throttle_write_bps_device(dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + +out: + return ret; +} + +int generate_hugetlb_limits(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + if (srcconfig->hugetlbs_len == 0 || srcconfig->hugetlbs == NULL) { + goto out; + } + + if (srcconfig->hugetlbs_len > SIZE_MAX / sizeof(host_config_hugetlbs_element *)) { + ERROR("Too many hugepage limits to get!"); + ret = -1; + goto out; + } + + (*dstconfig)->hugetlbs = util_common_calloc_s(srcconfig->hugetlbs_len * sizeof(host_config_hugetlbs_element *)); + if ((*dstconfig)->hugetlbs == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->hugetlbs_len; i++) { + (*dstconfig)->hugetlbs[(*dstconfig)->hugetlbs_len] = pase_hugetlb_limit(srcconfig->hugetlbs[i]); + if ((*dstconfig)->hugetlbs[(*dstconfig)->hugetlbs_len] == NULL) { + ERROR("Failed to get hugepage limits"); + ret = -1; + goto out; + } + + (*dstconfig)->hugetlbs_len++; + } +out: + return ret; +} + +int generate_binds(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + if (srcconfig->binds == NULL || srcconfig->binds_len == 0) { + goto out; + } + + if (srcconfig->binds_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many binds to mount!"); + ret = -1; + goto out; + } + + (*dstconfig)->binds = util_common_calloc_s(srcconfig->binds_len * sizeof(char *)); + if ((*dstconfig)->binds == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->binds_len; i++) { + (*dstconfig)->binds[(*dstconfig)->binds_len] = util_strdup_s(srcconfig->binds[i]); + (*dstconfig)->binds_len++; + } + +out: + return ret; +} + +int generate_groups(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + size_t i = 0; + + if (srcconfig->group_add == NULL || srcconfig->group_add_len == 0 || dstconfig == NULL) { + goto out; + } + + if (srcconfig->group_add_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many groups to add!"); + ret = -1; + goto out; + } + + (*dstconfig)->group_add = util_common_calloc_s(srcconfig->group_add_len * sizeof(char *)); + if ((*dstconfig)->group_add == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < srcconfig->group_add_len; i++) { + (*dstconfig)->group_add[(*dstconfig)->group_add_len] = util_strdup_s(srcconfig->group_add[i]); + (*dstconfig)->group_add_len++; + } +out: + return ret; +} + +int generate_security(host_config **dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + + if (srcconfig->security == NULL || srcconfig->security_len == 0) { + goto out; + } + + if (srcconfig->security_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many security opts!"); + ret = -1; + goto out; + } + + (*dstconfig)->security_opt = util_common_calloc_s(srcconfig->security_len * sizeof(char *)); + if ((*dstconfig)->security_opt == NULL) { + ret = -1; + goto out; + } + + ret = parse_seccomp(srcconfig, (*dstconfig)); + if (ret < 0) { + goto out; + } + + ret = parse_no_new_privileges(srcconfig, (*dstconfig)); + if (ret < 0) { + goto out; + } + +out: + return ret; +} + +static inline void check_and_strdup_s(char **dst_item, const char *src_item) +{ + if (src_item != NULL && dst_item != NULL) { + (*dst_item) = util_strdup_s((src_item)); + } +} + +static int pack_host_config_common(host_config *dstconfig, const lcrc_host_config_t *srcconfig) +{ + int ret = 0; + + ret = pack_host_config_ns_change_files(dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + ret = pack_hostconfig_cgroup(dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + ret = pack_host_config_caps(dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + ret = generate_storage_opts(&dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + ret = generate_sysctls(&dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + ret = pack_host_config_network(dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + /* devices which will be populated into container */ + ret = generate_devices(&dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + /* blkio device */ + ret = generate_blkio(&dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + /* hugepage limits */ + ret = generate_hugetlb_limits(&dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + /* binds to mount */ + ret = generate_binds(&dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + /* groups to add */ + ret = generate_groups(&dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + /* security opt */ + ret = generate_security(&dstconfig, srcconfig); + if (ret < 0) { + goto out; + } +out: + return ret; +} + +int generate_hostconfig(const lcrc_host_config_t *srcconfig, char **hostconfigstr) +{ + int ret = 0; + parser_error err = NULL; + host_config *dstconfig = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + + dstconfig = util_common_calloc_s(sizeof(*dstconfig)); + if (dstconfig == NULL) { + ret = -1; + goto out; + } + + dstconfig->privileged = srcconfig->privileged; + dstconfig->system_container = srcconfig->system_container; + dstconfig->auto_remove = srcconfig->auto_remove; + dstconfig->auto_remove_bak = srcconfig->auto_remove; + dstconfig->readonly_rootfs = srcconfig->readonly_rootfs; + dstconfig->oom_kill_disable = srcconfig->oom_kill_disable; + dstconfig->shm_size = srcconfig->shm_size; + + ret = pack_host_config_common(dstconfig, srcconfig); + if (ret < 0) { + goto out; + } + + check_and_strdup_s(&dstconfig->network_mode, srcconfig->network_mode); + check_and_strdup_s(&dstconfig->ipc_mode, srcconfig->ipc_mode); + check_and_strdup_s(&dstconfig->userns_mode, srcconfig->userns_mode); + check_and_strdup_s(&dstconfig->user_remap, srcconfig->user_remap); + check_and_strdup_s(&dstconfig->uts_mode, srcconfig->uts_mode); + check_and_strdup_s(&dstconfig->pid_mode, srcconfig->pid_mode); + /* hook-spec file */ + check_and_strdup_s(&dstconfig->hook_spec, srcconfig->hook_spec); + /* env target file */ + check_and_strdup_s(&dstconfig->env_target_file, srcconfig->env_target_file); + /* cgroup parent */ + check_and_strdup_s(&dstconfig->cgroup_parent, srcconfig->cgroup_parent); + + if (!parse_restart_policy(srcconfig->restart_policy, &dstconfig->restart_policy)) { + ERROR("Invalid restart policy"); + ret = -1; + goto out; + } + + if (srcconfig->host_channel != NULL) { + dstconfig->host_channel = parse_host_channel(srcconfig->host_channel); + if (dstconfig->host_channel == NULL) { + ERROR("Invalid host channel"); + ret = -1; + goto out; + } + } + *hostconfigstr = host_config_generate_json(dstconfig, &ctx, &err); + if (*hostconfigstr == NULL) { + COMMAND_ERROR("Failed to generate hostconfig json:%s", err); + ret = -1; + goto out; + } + +out: + free(err); + free_host_config(dstconfig); + return ret; +} + +static int pack_container_custom_config_log(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf) +{ + int ret = 0; + + /* log config */ + custom_spec->log_config = util_common_calloc_s(sizeof(container_custom_config_log_config)); + if (custom_spec->log_config == NULL) { + ret = -1; + goto out; + } + if (custom_conf->log_file != NULL) { + custom_spec->log_config->log_file = util_strdup_s(custom_conf->log_file); + } + + if (custom_conf->log_file_size != NULL) { + custom_spec->log_config->log_file_size = util_strdup_s(custom_conf->log_file_size); + } + + if (custom_conf->log_file_rotate) { + custom_spec->log_config->log_file_rotate = custom_conf->log_file_rotate; + } +out: + return ret; +} + +static int pack_container_custom_config_args(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf) +{ + int ret = 0; + int i; + + /* entrypoint */ + if (util_valid_str(custom_conf->entrypoint)) { + custom_spec->entrypoint = util_common_calloc_s(sizeof(char *)); + if (custom_spec->entrypoint == NULL) { + ret = -1; + goto out; + } + custom_spec->entrypoint[0] = util_strdup_s(custom_conf->entrypoint); + custom_spec->entrypoint_len++; + } + + /* commands */ + if ((custom_conf->cmd_len != 0 && custom_conf->cmd)) { + if (custom_conf->cmd_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("The length of cmd is too long!"); + ret = -1; + goto out; + } + custom_spec->cmd = util_common_calloc_s(custom_conf->cmd_len * sizeof(char *)); + if (custom_spec->cmd == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < (int)custom_conf->cmd_len; i++) { + custom_spec->cmd[custom_spec->cmd_len] = util_strdup_s(custom_conf->cmd[i]); + custom_spec->cmd_len++; + } + } + +out: + return ret; +} + +static int pack_container_custom_config_mounts(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf) +{ + int ret = 0; + int i = 0; + + /* mounts to mount filesystem */ + if (custom_conf->mounts != NULL && custom_conf->mounts_len > 0) { + if (custom_conf->mounts_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many mounts to mount filesystem!"); + ret = -1; + goto out; + } + custom_spec->mounts = util_common_calloc_s(custom_conf->mounts_len * sizeof(char *)); + if (custom_spec->mounts == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < (int)custom_conf->mounts_len; i++) { + custom_spec->mounts[custom_spec->mounts_len] = util_strdup_s(custom_conf->mounts[i]); + custom_spec->mounts_len++; + } + } +out: + return ret; +} + +static int pack_container_custom_config_array(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf) +{ + int ret = 0; + int i = 0; + + /* environment variables */ + if (custom_conf->env_len != 0 && custom_conf->env) { + if (custom_conf->env_len > SIZE_MAX / sizeof(char *)) { + COMMAND_ERROR("Too many environment variables"); + return -1; + } + custom_spec->env = util_common_calloc_s(custom_conf->env_len * sizeof(char *)); + if (custom_spec->env == NULL) { + ret = -1; + goto out; + } + for (i = 0; i < (int)custom_conf->env_len; i++) { + custom_spec->env[custom_spec->env_len] = util_strdup_s(custom_conf->env[i]); + custom_spec->env_len++; + } + } + +out: + return ret; +} + +static bool have_health_check(const lcrc_container_config_t *custom_conf) +{ + bool have_health_settings = false; + + if ((custom_conf->health_cmd != NULL && strlen(custom_conf->health_cmd) != 0) || + custom_conf->health_interval != 0 || custom_conf->health_timeout != 0 || + custom_conf->health_start_period != 0 || custom_conf->health_retries != 0) { + have_health_settings = true; + } + + return have_health_settings; +} + +static int pack_custom_no_health_check(container_custom_config *custom_spec, bool have_health_settings, + defs_health_check *health_config) +{ + int ret = 0; + + if (have_health_settings) { + COMMAND_ERROR("--no-healthcheck conflicts with --health-* options"); + ret = -1; + goto out; + } + health_config->test = util_common_calloc_s(sizeof(char *)); + if (health_config->test == NULL) { + ret = -1; + goto out; + } + health_config->test[health_config->test_len++] = util_strdup_s("NONE"); + custom_spec->health_check = health_config; + +out: + return ret; +} + +static int pack_custom_with_health_check(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf, bool have_health_settings, + defs_health_check *health_config) +{ + int ret = 0; + + if (custom_conf->health_cmd != NULL && strlen(custom_conf->health_cmd) != 0) { + health_config->test = util_common_calloc_s(2 * sizeof(char *)); + if (health_config->test == NULL) { + ret = -1; + goto out; + } + health_config->test[health_config->test_len++] = util_strdup_s("CMD-SHELL"); + health_config->test[health_config->test_len++] = util_strdup_s(custom_conf->health_cmd); + } else { + COMMAND_ERROR("--health-cmd required!"); + ret = -1; + goto out; + } + health_config->interval = custom_conf->health_interval; + health_config->timeout = custom_conf->health_timeout; + health_config->start_period = custom_conf->health_start_period; + health_config->retries = custom_conf->health_retries; + health_config->exit_on_unhealthy = custom_conf->exit_on_unhealthy; + if (custom_spec->health_check != NULL) { + free_defs_health_check(custom_spec->health_check); + } + custom_spec->health_check = health_config; + +out: + return ret; +} + +static int pack_container_custom_config_health(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf) +{ + int ret = 0; + bool have_health_settings = false; + defs_health_check *health_config = NULL; + + if (custom_spec == NULL || custom_conf == NULL) { + return 0; + } + + have_health_settings = have_health_check(custom_conf); + + health_config = util_common_calloc_s(sizeof(defs_health_check)); + if (health_config == NULL) { + ret = -1; + goto out; + } + + if (custom_conf->no_healthcheck) { + ret = pack_custom_no_health_check(custom_spec, have_health_settings, health_config); + if (ret != 0) { + goto out; + } + } else if (have_health_settings) { + ret = pack_custom_with_health_check(custom_spec, custom_conf, have_health_settings, health_config); + if (ret != 0) { + goto out; + } + } else { + goto out; + } + + return ret; + +out: + free_defs_health_check(health_config); + return ret; +} + +static int pack_container_custom_config_annotation(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf) +{ + int ret = 0; + size_t j; + + custom_spec->annotations = util_common_calloc_s(sizeof(json_map_string_string)); + if (custom_spec->annotations == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + if (custom_conf->annotations != NULL) { + for (j = 0; j < custom_conf->annotations->len; j++) { + if (append_json_map_string_string(custom_spec->annotations, custom_conf->annotations->keys[j], + custom_conf->annotations->values[j])) { + ERROR("Append map failed"); + ret = -1; + goto out; + } + } + } +out: + return ret; +} + +static int pack_container_custom_config_pre(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf) +{ + int ret = 0; + + ret = pack_container_custom_config_log(custom_spec, custom_conf); + if (ret != 0) { + goto out; + } + + ret = pack_container_custom_config_args(custom_spec, custom_conf); + if (ret != 0) { + goto out; + } + + ret = pack_container_custom_config_mounts(custom_spec, custom_conf); + if (ret != 0) { + goto out; + } + + ret = pack_container_custom_config_array(custom_spec, custom_conf); + if (ret != 0) { + goto out; + } + + ret = pack_container_custom_config_health(custom_spec, custom_conf); + if (ret != 0) { + goto out; + } +out: + return ret; +} + +/* translate create_custom_config to container_custom_config */ +static int pack_container_custom_config(container_custom_config *custom_spec, + const lcrc_container_config_t *custom_conf) +{ + int ret = -1; + + if (custom_spec == NULL || custom_conf == NULL) { + return ret; + } + + ret = pack_container_custom_config_pre(custom_spec, custom_conf); + if (ret != 0) { + goto out; + } + + if (custom_conf->hostname != NULL) { + custom_spec->hostname = util_strdup_s(custom_conf->hostname); + } + + /* console config */ + custom_spec->tty = custom_conf->tty; + custom_spec->open_stdin = custom_conf->open_stdin; + custom_spec->attach_stdin = custom_conf->attach_stdin; + custom_spec->attach_stdout = custom_conf->attach_stdout; + custom_spec->attach_stderr = custom_conf->attach_stderr; + + /* user and group */ + if (custom_conf->user != NULL) { + custom_spec->user = util_strdup_s(custom_conf->user); + } + + /* settings for system container */ + if (custom_conf->system_container) { + custom_spec->system_container = custom_conf->system_container; + } + + if (custom_conf->ns_change_opt != NULL) { + custom_spec->ns_change_opt = util_strdup_s(custom_conf->ns_change_opt); + } + + ret = pack_container_custom_config_annotation(custom_spec, custom_conf); + if (ret != 0) { + goto out; + } + + if (custom_conf->workdir != NULL) { + custom_spec->working_dir = util_strdup_s(custom_conf->workdir); + } + +out: + return ret; +} + +int generate_container_config(const lcrc_container_config_t *custom_conf, char **custom_config_str) +{ + int ret = 0; + container_custom_config *custom_spec = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = NULL; + + /* step 1: malloc the container_custom_config */ + custom_spec = util_common_calloc_s(sizeof(container_custom_config)); + if (custom_spec == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + + /* step 2: pack the container custom config */ + ret = pack_container_custom_config(custom_spec, custom_conf); + if (ret != 0) { + ERROR("Failed to pack the container custom config"); + ret = -1; + goto out; + } + + /* step 3: generate the config string */ + *custom_config_str = container_custom_config_generate_json(custom_spec, &ctx, &err); + if (*custom_config_str == NULL) { + ERROR("Failed to generate OCI specification json string"); + ret = -1; + goto out; + } + +out: + free_container_custom_config(custom_spec); + free(err); + + return ret; +} + diff --git a/src/pack_config.h b/src/pack_config.h new file mode 100644 index 0000000..16f9518 --- /dev/null +++ b/src/pack_config.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container package configure definition + ******************************************************************************/ +#ifndef __PACK_CONFIG_H__ +#define __PACK_CONFIG_H__ + +#include "liblcrc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int generate_hostconfig(const lcrc_host_config_t *srcconfig, char **hostconfigstr); + +int generate_container_config(const lcrc_container_config_t *custom_conf, + char **custom_config_str); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/path.c b/src/path.c new file mode 100644 index 0000000..3c4c357 --- /dev/null +++ b/src/path.c @@ -0,0 +1,673 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container path functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "path.h" +#include "utils.h" +#include "securec.h" + +#define ISSLASH(C) ((C) == '/') +#define IS_ABSOLUTE_FILE_NAME(F) (ISSLASH ((F)[0])) +#define IS_RELATIVE_FILE_NAME(F) (!IS_ABSOLUTE_FILE_NAME (F)) + +static bool do_clean_path_continue(const char *endpos, const char *stpos, const char *respath, char **dst) +{ + if (endpos - stpos == 1 && stpos[0] == '.') { + return true; + } else if (endpos - stpos == 2 && stpos[0] == '.' && stpos[1] == '.') { + char *dest = *dst; + if (dest <= respath + 1) { + return true; + } + for (--dest; dest > respath && !ISSLASH(dest[-1]); --dest) { + continue; + } + *dst = dest; + return true; + } + return false; +} + +static int do_clean_path(const char *respath, const char *limit_respath, + const char *stpos, char **dst) +{ + char *dest = *dst; + const char *endpos = NULL; + errno_t ret; + + for (endpos = stpos; *stpos; stpos = endpos) { + while (ISSLASH(*stpos)) { + ++stpos; + } + + for (endpos = stpos; *endpos && !ISSLASH(*endpos); ++endpos) { + } + + if (endpos - stpos == 0) { + break; + } else if (do_clean_path_continue(endpos, stpos, respath, &dest)) { + continue; + } + + if (!ISSLASH(dest[-1])) { + *dest++ = '/'; + } + + if (dest + (endpos - stpos) >= limit_respath) { + ERROR("Path is too long"); + if (dest > respath + 1) { + dest--; + } + *dest = '\0'; + return -1; + } + + ret = memcpy_s(dest, (size_t)(endpos - stpos), stpos, (size_t)(endpos - stpos)); + if (ret != EOK) { + ERROR("Failed at cleanpath memcpy"); + return -1; + } + dest += endpos - stpos; + *dest = '\0'; + } + *dst = dest; + return 0; +} + +char *cleanpath(const char *path, char *realpath, size_t realpath_len) +{ + char *respath = NULL; + char *dest = NULL; + const char *stpos = NULL; + const char *limit_respath = NULL; + errno_t ret; + + if (path == NULL || path[0] == '\0' || \ + realpath == NULL || (realpath_len < PATH_MAX)) { + return NULL; + } + + respath = realpath; + + ret = memset_s(respath, realpath_len, 0, realpath_len); + if (ret != EOK) { + ERROR("Failed at cleanpath memset"); + goto error; + } + limit_respath = respath + PATH_MAX; + + if (!IS_ABSOLUTE_FILE_NAME(path)) { + if (!getcwd(respath, PATH_MAX)) { + ERROR("Failed to getcwd"); + respath[0] = '\0'; + goto error; + } + dest = strchr(respath, '\0'); + if (dest == NULL) { + ERROR("Failed to get the end of respath"); + goto error; + } + ret = strcat_s(respath, PATH_MAX, path); + if (ret != EOK) { + ERROR("Failed at cleanpath strcat"); + goto error; + } + stpos = path; + } else { + dest = respath; + *dest++ = '/'; + stpos = path; + } + + if (do_clean_path(respath, limit_respath, stpos, &dest)) { + goto error; + } + + if (dest > respath + 1 && ISSLASH(dest[-1])) { + --dest; + } + *dest = '\0'; + + return respath; + +error: + return NULL; +} + +static int do_path_realloc(const char *start, const char *end, + char **rpath, char **dest, const char **rpath_limit) +{ + int nret = 0; + size_t new_size; + size_t gap = 0; + long long dest_offset = *dest - *rpath; + char *new_rpath = NULL; + + if (*dest + (end - start) < *rpath_limit) { + return 0; + } + + gap = (size_t)(end - start) + 1; + new_size = (size_t)(*rpath_limit - *rpath); + if (new_size > SIZE_MAX - gap) { + ERROR("Out of range!"); + return -1; + } + + if (gap > PATH_MAX) { + new_size += gap; + } else { + new_size += PATH_MAX; + } + nret = mem_realloc((void **)(&new_rpath), new_size, *rpath, PATH_MAX); + if (nret) { + ERROR("Failed to realloc memory for files limit variables"); + return -1; + } + *rpath = new_rpath; + *rpath_limit = *rpath + new_size; + + *dest = *rpath + dest_offset; + + return 0; +} + +static int do_get_symlinks_copy_buf(const char *buf, const char *prefix, size_t prefix_len, + char **rpath, char **dest) +{ + if (IS_ABSOLUTE_FILE_NAME(buf)) { + if (prefix_len) { + if (memcpy_s(*rpath, PATH_MAX, prefix, prefix_len) != EOK) { + ERROR("Memory copy failed!"); + return -1; + } + } + *dest = *rpath + prefix_len; + *(*dest)++ = '/'; + } else { + if (*dest > *rpath + prefix_len + 1) { + for (--(*dest); *dest > *rpath && !ISSLASH((*dest)[-1]); --(*dest)) { + continue; + } + } + } + return 0; +} + +static int do_get_symlinks(const char **fullpath, const char *prefix, size_t prefix_len, + char **rpath, char **dest, const char **end, + int *num_links, char **extra_buf) +{ + int ret = -1; + size_t len; + ssize_t n; + errno_t rc = EOK; + char *buf = NULL; + + if (++(*num_links) > MAXSYMLINKS) { + ERROR("Too many links in '%s'", *fullpath); + goto out; + } + + buf = util_common_calloc_s(PATH_MAX); + if (buf == NULL) { + ERROR("Out of memory"); + goto out; + } + + n = readlink(*rpath, buf, PATH_MAX - 1); + if (n < 0) { + goto out; + } + buf[n] = '\0'; + + if (*extra_buf == NULL) { + *extra_buf = util_common_calloc_s(PATH_MAX); + if (*extra_buf == NULL) { + ERROR("Out of memory"); + goto out; + } + } + + len = strlen(*end); + if (len >= PATH_MAX - (size_t)n) { + ERROR("Path is too long"); + goto out; + } + + rc = memmove_s(&(*extra_buf)[n], (size_t)(PATH_MAX - n), *end, len + 1); + if (rc != EOK) { + ERROR("Memory move failed!"); + goto out; + } + rc = memcpy_s(*extra_buf, PATH_MAX, buf, (size_t)n); + if (rc != EOK) { + ERROR("Memory copy failed!"); + goto out; + } + *fullpath = *end = *extra_buf; + + if (do_get_symlinks_copy_buf(buf, prefix, prefix_len, rpath, dest) != 0) { + goto out; + } + + ret = 0; +out: + free(buf); + return ret; +} + +static bool do_eval_symlinks_in_scope_is_symlink(const char *path) +{ + struct stat st; + + if (lstat(path, &st) < 0) { + return true; + } + + if (!S_ISLNK(st.st_mode)) { + return true; + } + return false; +} + +static void do_eval_symlinks_skip_slash(const char **start, const char **end) +{ + while (ISSLASH(**start)) { + ++(*start); + } + + for (*end = *start; **end && !ISSLASH(**end); ++(*end)) { + } +} + +static inline void skip_dest_trailing_slash(char **dest, char **rpath, size_t prefix_len) +{ + if (*dest > *rpath + prefix_len + 1) { + for (--(*dest); *dest > *rpath && !ISSLASH((*dest)[-1]); --(*dest)) { + continue; + } + } +} + +static inline bool is_current_char(const char c) +{ + return c == '.'; +} + +static inline bool is_specify_current(const char *end, const char *start) +{ + return (end - start == 1) && is_current_char(start[0]); +} + +static inline bool is_specify_parent(const char *end, const char *start) +{ + return (end - start == 2) && is_current_char(start[0]) && is_current_char(start[1]); +} + +static int do_eval_symlinks_in_scope(const char *fullpath, const char *prefix, + size_t prefix_len, + char **rpath, char **dest, const char *rpath_limit) +{ + int nret = 0; + int num_links = 0; + const char *start = NULL; + const char *end = NULL; + char *extra_buf = NULL; + errno_t rc = EOK; + + start = fullpath + prefix_len; + for (end = start; *start; start = end) { + do_eval_symlinks_skip_slash(&start, &end); + if (end - start == 0) { + break; + } else if (is_specify_current(end, start)) { + continue; + } else if (is_specify_parent(end, start)) { + skip_dest_trailing_slash(dest, rpath, prefix_len); + } else { + if (!ISSLASH((*dest)[-1])) { + *(*dest)++ = '/'; + } + + nret = do_path_realloc(start, end, rpath, dest, &rpath_limit); + if (nret != 0) { + nret = -1; + goto out; + } + + rc = memcpy_s(*dest, (size_t)(end - start), start, (size_t)(end - start)); + if (rc != EOK) { + ERROR("Out of memory"); + nret = -1; + goto out; + } + *dest += end - start; + **dest = '\0'; + + if (do_eval_symlinks_in_scope_is_symlink(*rpath)) { + continue; + } + + nret = do_get_symlinks(&fullpath, prefix, prefix_len, rpath, dest, &end, &num_links, &extra_buf); + if (nret != 0) { + nret = -1; + goto out; + } + } + } +out: + free(extra_buf); + return nret; +} + +static char *eval_symlinks_in_scope(const char *fullpath, const char *rootpath) +{ + char *root = NULL; + char *rpath = NULL; + char *dest = NULL; + char *prefix = NULL; + const char *rpath_limit = NULL; + size_t prefix_len; + errno_t rc = EOK; + char resroot[PATH_MAX] = { 0 }; + + if (fullpath == NULL || rootpath == NULL) { + return NULL; + } + + root = cleanpath(rootpath, resroot, sizeof(resroot)); + if (root == NULL) { + ERROR("Failed to get cleaned path"); + return NULL; + } + + if (strcmp(fullpath, root) == 0) { + return util_strdup_s(fullpath); + } + + if (strstr(fullpath, root) == NULL) { + ERROR("Path '%s' is not in '%s'", fullpath, root); + return NULL; + } + + rpath = util_common_calloc_s(PATH_MAX); + if (rpath == NULL) { + ERROR("Out of memory"); + goto out; + } + rpath_limit = rpath + PATH_MAX; + + prefix = root; + prefix_len = (size_t)strlen(prefix); + if (strcmp(prefix, "/") == 0) { + prefix_len = 0; + } + + dest = rpath; + if (prefix_len) { + rc = memcpy_s(rpath, PATH_MAX, prefix, prefix_len); + if (rc != EOK) { + ERROR("Out of memory"); + goto out; + } + dest += prefix_len; + } + *dest++ = '/'; + + if (do_eval_symlinks_in_scope(fullpath, prefix, prefix_len, &rpath, &dest, + rpath_limit)) { + goto out; + } + + if (dest > rpath + prefix_len + 1 && ISSLASH(dest[-1])) { + --dest; + } + *dest = '\0'; + return rpath; + +out: + free(rpath); + return NULL; +} + +char *follow_symlink_in_scope(const char *fullpath, const char *rootpath) +{ + char resfull[PATH_MAX] = { 0 }; + char *full = NULL; + char resroot[PATH_MAX] = { 0 }; + char *root = NULL; + + full = cleanpath(fullpath, resfull, sizeof(resfull)); + if (full == NULL) { + ERROR("Failed to get cleaned path"); + return NULL; + } + + root = cleanpath(rootpath, resroot, sizeof(resroot)); + if (root == NULL) { + ERROR("Failed to get cleaned path"); + return NULL; + } + + return eval_symlinks_in_scope(full, root); +} + +bool specify_current_dir(const char *path) +{ + char *dup = NULL; + char *base = NULL; + bool res = false; + + if (path == NULL) { + return false; + } + + if (path[0] == '\0') { + return true; + } + + dup = util_strdup_s(path); + base = basename(dup); + res = (base != NULL) && (strcmp(base, ".") == 0); + free(dup); + return res; +} + +bool has_trailing_path_separator(const char *path) +{ + return path != NULL && strlen(path) > 0 && (path[strlen(path) - 1] == '/'); +} + +static void set_char_to_separator(char *p) +{ + *p = '/'; +} + +char *preserve_trailing_dot_or_separator(const char *cleanedpath, const char *originalpath) +{ + int nret; + char respath[PATH_MAX + 3] = { 0 }; + + nret = sprintf_s(respath, PATH_MAX, "%s", cleanedpath); + if (nret < 0) { + ERROR("Failed to print string"); + return NULL; + } + + if (!specify_current_dir(cleanedpath) && specify_current_dir(originalpath)) { + if (!has_trailing_path_separator(respath)) { + set_char_to_separator(&respath[strlen(respath)]); + } + respath[strlen(respath)] = '.'; + } + + if (!has_trailing_path_separator(respath) && has_trailing_path_separator(originalpath)) { + set_char_to_separator(&respath[strlen(respath)]); + } + + return util_strdup_s(respath); +} + +int filepath_split(const char *path, char **dir, char **base) +{ + ssize_t i; + char *dup = NULL; + + if (path == NULL) { + return -1; + } + + dup = util_strdup_s(path); + i = (ssize_t)strlen(dup) - 1; + while (i >= 0 && dup[i] != '/') { + i--; + } + + if (i != -1) { + char cache = dup[i + 1]; + dup[i + 1] = '\0'; + if (dir != NULL) { + *dir = util_strdup_s(dup); + } + dup[i + 1] = cache; + } else { + if (dir != NULL) { + *dir = util_strdup_s("."); + } + } + + if (base != NULL) { + *base = util_strdup_s(dup + i + 1); + } + free(dup); + return 0; +} + +int split_dir_and_base_name(const char *path, char **dir, char **base) +{ + char *dupdir = NULL; + char *dupbase = NULL; + + if (path == NULL) { + return -1; + } + + dupdir = util_strdup_s(path); + dupbase = util_strdup_s(path); + if (dir != NULL) { + *dir = util_strdup_s(dirname(dupdir)); + } + if (base != NULL) { + *base = util_strdup_s(basename(dupbase)); + } + free(dupdir); + free(dupbase); + return 0; +} + +char *get_resource_path(const char *rootpath, const char *path) +{ + int nret; + char tmppath[PATH_MAX] = { 0 }; + char fullpath[PATH_MAX] = { 0 }; + + nret = sprintf_s(tmppath, sizeof(tmppath), "/%s/%s", rootpath, path); + if (nret < 0) { + return NULL; + } + + if (cleanpath(tmppath, fullpath, sizeof(fullpath)) == NULL) { + return NULL; + } + + return follow_symlink_in_scope(fullpath, rootpath); +} + +int resolve_path(const char *rootpath, const char *path, char **resolvedpath, char **abspath) +{ + int ret = -1; + int nret; + size_t len; + char *dirpath = NULL; + char *basepath = NULL; + char *resolved_dir_path = NULL; + char tmppath[PATH_MAX] = { 0 }; + char cleanedpath[PATH_MAX] = { 0 }; + + *resolvedpath = NULL; + *abspath = NULL; + + nret = sprintf_s(tmppath, sizeof(tmppath), "/%s", path); + if (nret < 0) { + ERROR("Failed to print string"); + return -1; + } + + if (cleanpath(tmppath, cleanedpath, sizeof(cleanedpath)) == NULL) { + ERROR("Failed to get cleaned path: %s", tmppath); + return -1; + } + + *abspath = preserve_trailing_dot_or_separator(cleanedpath, tmppath); + if (*abspath == NULL) { + ERROR("Failed to preserve path"); + goto cleanup; + } + + nret = filepath_split(*abspath, &dirpath, &basepath); + if (nret < 0) { + ERROR("Failed to split path"); + goto cleanup; + } + + resolved_dir_path = get_resource_path(rootpath, dirpath); + if (resolved_dir_path == NULL) { + ERROR("Failed to get resource path"); + goto cleanup; + } + len = strlen(resolved_dir_path) + strlen("/") + strlen(basepath) + (size_t)1; + *resolvedpath = util_common_calloc_s(len); + if (*resolvedpath == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + nret = sprintf_s(*resolvedpath, len, "%s/%s", resolved_dir_path, basepath); + if (nret < 0) { + ERROR("Failed to print string"); + goto cleanup; + } + + ret = 0; +cleanup: + free(dirpath); + free(basepath); + free(resolved_dir_path); + + if (ret != 0) { + free(*abspath); + *abspath = NULL; + free(*resolvedpath); + *resolvedpath = NULL; + } + return ret; +} + diff --git a/src/path.h b/src/path.h new file mode 100644 index 0000000..6852912 --- /dev/null +++ b/src/path.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container path function definition + ******************************************************************************/ +#ifndef __LCRD_PATH_H_ +#define __LCRD_PATH_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * cleanpath is similar to realpath of glibc, but not expands symbolic links, + * and not check the existence of components of the path. + */ +char *cleanpath(const char *path, char *realpath, size_t realpath_len); + +bool specify_current_dir(const char *path); + +char *follow_symlink_in_scope(const char *fullpath, const char *rootpath); + +int split_dir_and_base_name(const char *path, char **dir, char **base); + +int filepath_split(const char *path, char **dir, char **base); + +char *get_resource_path(const char *rootpath, const char *path); + +int resolve_path(const char *rootpath, const char *path, char **resolvedpath, char **abspath); + +bool has_trailing_path_separator(const char *path); + +char *preserve_trailing_dot_or_separator(const char *cleanedpath, const char *originalpath); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/plugin/CMakeLists.txt b/src/plugin/CMakeLists.txt new file mode 100644 index 0000000..338a368 --- /dev/null +++ b/src/plugin/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_plugin_srcs) + +set(PLUGIN_SRCS + ${local_plugin_srcs} + PARENT_SCOPE + ) diff --git a/src/plugin/plugin.c b/src/plugin/plugin.c new file mode 100644 index 0000000..9f5b996 --- /dev/null +++ b/src/plugin/plugin.c @@ -0,0 +1,1914 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: jingrui + * Create: 2018-12-01 + * Description: provide plugin definition + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "plugin.h" +#include "pspec.h" +#include "utils.h" +#include "parser.h" +#include "buffer.h" +#include "lcrd_config.h" +#include "specs.h" +#include "specs_extend.h" +#include "rest_common.h" +#include "containers_store.h" +#include "constants.h" + +#include "plugin_activate_plugin_request.h" +#include "plugin_activate_plugin_response.h" +#include "plugin_init_plugin_request.h" +#include "plugin_init_plugin_response.h" +#include "plugin_event_pre_create_request.h" +#include "plugin_event_pre_create_response.h" +#include "plugin_event_pre_start_request.h" +#include "plugin_event_pre_start_response.h" +#include "plugin_event_post_stop_request.h" +#include "plugin_event_post_stop_response.h" +#include "plugin_event_post_remove_request.h" +#include "plugin_event_post_remove_response.h" + +#define plugin_socket_path "/run/lcrd/plugins" +#define plugin_socket_file_regex ".*.sock$" + +// suffix is '.sock' +#define PLUGIN_SOCKET_FILE_SUFFIX_LEN 5 + +#define PLUGIN_ACTIVATE_MAX_RETRY 3 + +#ifndef RestHttpHead +#define RestHttpHead "http://localhost" +#endif +#define PluginServiceActivate "/PluginService/Activate" +#define PluginServiceInit "/PluginService/Init" +#define PluginServicePreCreate "/PluginService/PreCreate" +#define PluginServicePreStart "/PluginService/PreStart" +#define PluginServicePostStop "/PluginService/PostStop" +#define PluginServicePostRemove "/PluginService/PostRemove" + +static int pm_init_plugin(const plugin_t *plugin); + +static int plugin_event_pre_start_handle(const plugin_t *plugin, const char *cid); +static int plugin_event_post_stop_handle(const plugin_t *plugin, const char *cid); +static int plugin_event_post_remove_handle(const plugin_t *plugin, const char *cid); + +enum plugin_action { ACTIVE_PLUGIN, DEACTIVE_PLUGIN }; + +static int check_err(int err, const char *msg) +{ + if (err) { + return -1; + } + if (msg != NULL && strlen(msg)) { + return -1; + } + return 0; +} + +static char *dup_cid(const container_t *cont) +{ + return util_strdup_s(cont->common_config->id); +} + +/* + * return container status, defined by Container_Status. + */ +static int get_status(const container_t *cont) +{ + return (int)state_get_status(cont->state); +} + +/* + * join , seperated string into one. + */ +static char *join_enable_plugins(const char *plugins) +{ + char *default_plugins = NULL; + char *tmp = NULL; + char *ep = NULL; + + default_plugins = conf_get_enable_plugins(); + + if ((default_plugins == NULL) && (plugins == NULL)) { + return NULL; + } + + if (plugins == NULL) { + return default_plugins; + } + + if (default_plugins == NULL) { + return util_strdup_s(plugins); + } + + tmp = util_string_append(LCRD_ENABLE_PLUGINS_SEPERATOR, default_plugins); + if (tmp == NULL) { + ERROR("string append failed %s -> %s", LCRD_ENABLE_PLUGINS_SEPERATOR, default_plugins); + goto out; + } + + ep = util_string_append(plugins, tmp); + if (ep == NULL) { + ERROR("string append failed %s -> %s", plugins, tmp); + goto out; + } + +out: + free(default_plugins); + free(tmp); + return ep; +} + +static char *get_uniq_enable_plugins(const oci_runtime_spec *oci) +{ + char *names = NULL; + char *full = NULL; + char **raw = NULL; + char **arr = NULL; + size_t i = 0; + + if (oci == NULL) { + goto failed; + } + + names = oci_container_get_env(oci, LCRD_ENABLE_PLUGINS); + full = join_enable_plugins(names); + if (full == NULL) { + INFO("no plugins enabled"); + goto failed; + } + + raw = util_string_split(full, LCRD_ENABLE_PLUGINS_SEPERATOR_CHAR); + if (raw == NULL) { + ERROR("split plugin name failed"); + goto failed; + } + UTIL_FREE_AND_SET_NULL(names); + UTIL_FREE_AND_SET_NULL(full); + + for (i = 0; i < util_array_len(raw); i++) { + if (strings_in_slice((const char **)arr, util_array_len(arr), raw[i])) { + continue; + } + if (util_array_append(&arr, raw[i]) != 0) { + ERROR("append uniq plugin name failed"); + goto failed; + } + } + + names = util_string_join(LCRD_ENABLE_PLUGINS_SEPERATOR, (const char **)arr, util_array_len(arr)); + if (names == NULL) { + ERROR("join uniq plugin name failed"); + goto failed; + } + + full = util_string_append(names, LCRD_ENABLE_PLUGINS "="); + if (full == NULL) { + ERROR("init uniq enable plugins env failed"); + goto failed; + } + + util_free_array(raw); + util_free_array(arr); + free(names); + return full; + +failed: + util_free_array(raw); + util_free_array(arr); + free(names); + free(full); + return NULL; +} + +static int set_env_enable_plugins(oci_runtime_spec *oci) +{ + char *uniq = NULL; + + if (oci == NULL) { + ERROR("BUG oci should not be nil"); + goto failed; + } + + if (oci->process == NULL) { + ERROR("BUG oci->process should not be nil"); + oci->process = util_common_calloc_s(sizeof(oci_runtime_spec_process)); + if (oci->process == NULL) { + ERROR("out of memory"); + goto failed; + } + } + + uniq = get_uniq_enable_plugins(oci); + if (uniq == NULL) { + goto failed; + } + + if (util_env_insert(&oci->process->env, &oci->process->env_len, LCRD_ENABLE_PLUGINS, strlen(LCRD_ENABLE_PLUGINS), + uniq)) { + ERROR("set env %s failed", uniq); + } + + free(uniq); + return 0; + +failed: + free(uniq); + return -1; +} + +static char **get_enable_plugins(const char *plugins) +{ + char **arr = NULL; + size_t i, arr_len; + char **dst = NULL; + size_t dst_len = 0; + + if (plugins == NULL) { + return dst; + } + + arr = util_string_split(plugins, LCRD_ENABLE_PLUGINS_SEPERATOR_CHAR); + if (arr == NULL) { + ERROR("Out of memory"); + } + arr_len = util_array_len(arr); + + for (i = 0; i < arr_len; i++) { + if (strings_in_slice((const char **)dst, dst_len, arr[i])) { + continue; + } + if (util_array_append(&dst, arr[i]) != 0) { + util_free_array(dst); + dst = NULL; + goto out; + } + dst_len = util_array_len(dst); + } + + if (arr_len != dst_len) { + ERROR("enable plugins not unique: %s", plugins); + } + +out: + util_free_array(arr); + return dst; +} + +static uint64_t plugin_get_init_type(const plugin_t *p) +{ + if (p == NULL) { + return 0; + } + + if (p->manifest == NULL) { + return 0; + } + + return p->manifest->init_type; +} + +static int get_plugin_dir(char *plugin_dir) +{ + int ret = 0; + char *statedir = NULL; + + if (plugin_dir == NULL) { + return -1; + } + + statedir = conf_get_lcrd_statedir(); + if (statedir == NULL) { + ERROR("failed get statedir"); + return -1; + } + + ret = sprintf_s(plugin_dir, PATH_MAX, "%s/plugins", statedir); + if (ret < 0) { + goto failed; + } + + ret = util_mkdir_p(plugin_dir, DEFAULT_SECURE_FILE_MODE); + if (ret < 0) { + goto failed; + } + + free(statedir); + return 0; + +failed: + free(statedir); + return -1; +} + +// wait_events wait until inotify +static int wait_events(int inotify_fd) +{ + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(inotify_fd, &rfds); + return select(FD_SETSIZE, &rfds, NULL, NULL, NULL); +} + +static int verify_plugin_address(const char *plugin_addr) +{ + struct stat fileStat = { 0 }; + // add file permission check?? + // only owner root mode 600 is allowed + // + // check weather it is socket file + if (!stat(plugin_addr, &fileStat)) { + if (S_ISSOCK(fileStat.st_mode)) { + return 0; + } else { + INFO("Skip %s, mode(%o).", plugin_addr, fileStat.st_mode & S_IFMT); + return -1; + } + } else { + ERROR("Failed to get(%s) file stat.", plugin_addr); + return -1; + } +} + +static int get_plugin_addr_and_name(char *plugin_addr, char *plugin_name, const char *filename, const char *plugin_dir, + int action) +{ + size_t str_length = 0; + int nret = 0; + + if (filename == NULL) { + return -1; + } + str_length = strlen(filename); + + if (util_reg_match(plugin_socket_file_regex, filename)) { + ERROR("not plugin filename %s", filename); + goto out; + } + nret = strcpy_s(plugin_name, PATH_MAX, filename); + if (nret < 0) { + ERROR("get plugin name failed %s", filename); + goto out; + } + plugin_name[str_length - PLUGIN_SOCKET_FILE_SUFFIX_LEN] = 0; + nret = sprintf_s(plugin_addr, PATH_MAX, "%s/%s", plugin_dir, filename); + if (nret < 0) { + ERROR("get plugin addr failed %s", filename); + goto out; + } + + if (action == DEACTIVE_PLUGIN) { + return 0; + } + return verify_plugin_address(plugin_addr); +out: + ERROR("invalid plugin socket(%s), skipping..", filename); + return -1; +} + +static int pm_activate_plugin_with_retry(plugin_t *plugin, size_t retry) +{ + size_t i = 0; + int err = 0; + + for (i = 0; i < retry; i++) { + err = pm_activate_plugin(plugin); + if (!err) { + return 0; + } + sleep((unsigned int)i + 1); + } + + return err; +} + +static void pm_rdlock(void) +{ + int errcode; + + errcode = pthread_rwlock_rdlock(&g_plugin_manager->pm_rwlock); + if (errcode != 0) { + ERROR("Read lock failed: %s", strerror(errcode)); + } +} + +static void pm_wrlock(void) +{ + int errcode; + + errcode = pthread_rwlock_wrlock(&g_plugin_manager->pm_rwlock); + if (errcode != 0) { + ERROR("Write lock failed: %s", strerror(errcode)); + } +} + +static void pm_unlock(void) +{ + int errcode; + + errcode = pthread_rwlock_unlock(&g_plugin_manager->pm_rwlock); + if (errcode != 0) { + ERROR("Unlock failed: %s", strerror(errcode)); + } +} + +static void free_plugin(plugin_t *plugin) +{ + if (plugin == NULL) { + return; + } + UTIL_FREE_AND_SET_NULL(plugin->name); + UTIL_FREE_AND_SET_NULL(plugin->addr); + UTIL_FREE_AND_SET_NULL(plugin->manifest); + UTIL_FREE_AND_SET_NULL(plugin->activated_errmsg); + free(plugin); +} + +static int do_get_plugin(const char *name, plugin_t **rplugin) +{ + plugin_t *plugin = NULL; + + pm_rdlock(); + plugin = map_search(g_plugin_manager->np, (void *)name); + pm_unlock(); + + *rplugin = plugin; + + if (plugin == NULL) { + return -1; + } + + plugin_get(plugin); + return 0; +} + +static int pm_register_plugin(const char *name, const char *addr) +{ + int err; + plugin_t *plugin = NULL; + + /* + * this function called in reload_plugin, remember dont call reload_plugin + * agaim. + */ + err = do_get_plugin(name, &plugin); + if (err == 0) { /* plugin already exist */ + pm_put_plugin(plugin); + DEBUG("skip register exist plugin %s", name); + return 0; + } + + plugin = plugin_new(name, addr); + if (plugin == NULL) { + ERROR("alloc plugin failed"); + goto failed; + } + err = pm_activate_plugin_with_retry(plugin, PLUGIN_ACTIVATE_MAX_RETRY); + if (err != 0) { + ERROR("active plugin failed"); + goto failed; + } + + if (plugin_get_init_type(plugin) != PLUGIN_INIT_SKIP) { + err = pm_init_plugin(plugin); + if (err != 0) { + ERROR("init plugin failed"); + goto failed; + } + } + + err = pm_add_plugin(plugin); + if (err != 0) { + ERROR("add plugin to map failed"); + goto failed; + } + + INFO("add activated plugin %s 0x%x", plugin->name, plugin->manifest->watch_event); + return 0; + +failed: + free_plugin(plugin); + return -1; +} + +static int pm_unregister_plugin(const char *name, const char *addr) +{ + int err = 0; + plugin_t *plugin = NULL; + + err = pm_get_plugin(name, &plugin); + if (err != 0) { + ERROR("plugin %s not exist in manager", name); + return -1; + } + + err = pm_deactivate_plugin(plugin); + if (err != 0) { /* ignore errors */ + ERROR("deactivate plugin %s failed", name); + } + + pm_put_plugin(plugin); + + err = pm_del_plugin(plugin); + if (err != 0) { + ERROR("can not del plugin %s", name); + return -1; + } + + return 0; +} + +static int handle_plugin_event(const char *event_name, const char *plugin_dir, int action) +{ + char addr[PATH_MAX] = { 0 }; + char name[PATH_MAX] = { 0 }; + + if (get_plugin_addr_and_name(addr, name, event_name, plugin_dir, action) < 0) { + return -1; + } + switch (action) { + case ACTIVE_PLUGIN: + INFO("Activate plugin: %s...", name); + pm_register_plugin(name, addr); + break; + case DEACTIVE_PLUGIN: + INFO("Deactivate plugin: %s...", name); + pm_unregister_plugin(name, addr); + break; + default: + INFO("Unsupport action, skip..."); + } + return 0; +} + +static int reload_plugin(const char *name) +{ + char plugin_dir[PATH_MAX] = { 0 }; + char filename[PATH_MAX] = { 0 }; + int ret = 0; + + INFO("reload plugin %s ...", name); + + if (get_plugin_dir(plugin_dir) < 0) { + ERROR("get plugin dir failed"); + return -1; + } + + ret = sprintf_s(filename, PATH_MAX, "%s.sock", name); + if (ret < 0) { + ERROR("get plugin addr failed %s", filename); + return -1; + } + + return handle_plugin_event(filename, plugin_dir, ACTIVE_PLUGIN); +} + +static int scan_existing_plugins(const char *dir) +{ + DIR *midir = NULL; + struct dirent *info_archivo = NULL; + + midir = opendir(dir); + if (midir == NULL) { + ERROR("scan_existing_plugins : Error in opendir"); + return -1; + } + + info_archivo = readdir(midir); + while (info_archivo != 0) { + // skip . .. + if (strncmp(info_archivo->d_name, ".", PATH_MAX) == 0 || strncmp(info_archivo->d_name, "..", PATH_MAX) == 0) { + info_archivo = readdir(midir); + continue; + } + + handle_plugin_event(info_archivo->d_name, dir, ACTIVE_PLUGIN); + info_archivo = readdir(midir); + } + closedir(midir); + return 0; +} + +static int process_plugin_events(int inotify_fd, const char *plugin_dir) +{ + ssize_t events_length = 0; + ssize_t events_index = 0; + struct inotify_event *plugin_event = NULL; + char buffer[8192 + 1] = { 0 }; + int action = 0; + events_length = read(inotify_fd, buffer, 8192); + + if (events_length <= 0) { + ERROR("Failed to wait events"); + return -1; + } + + while (events_index < events_length) { + plugin_event = (struct inotify_event *)(&buffer[events_index]); + ssize_t event_size = (ssize_t)(plugin_event->len) + (ssize_t)offsetof(struct inotify_event, name); + // should deal with events_index > events_length?? + if (event_size == 0 || event_size > (events_length - events_index)) { + break; + } + events_index += event_size; + if (plugin_event->mask & IN_CREATE) { + action = ACTIVE_PLUGIN; + } else if (plugin_event->mask & IN_DELETE) { + action = DEACTIVE_PLUGIN; + } else { + continue; + } + + handle_plugin_event(plugin_event->name, plugin_dir, action); + } + return 0; +} + +/* + * plugin_manager_routine manages the lifecycles of plugins + * include: discovery, active and deactive + * */ +static void *plugin_manager_routine(void *arg) +{ + int inotify_fd = 0; + int wd = 0; + char plugin_dir[PATH_MAX] = { 0 }; + int errcode = 0; + + errcode = pthread_detach(pthread_self()); + if (errcode != 0) { + ERROR("Detach thread failed: %s", strerror(errcode)); + return NULL; + } + if (pm_init() < 0) { + ERROR("init pm failed"); + return NULL; + } + if (get_plugin_dir(plugin_dir) < 0) { + ERROR("Failed to create plugin dir"); + return NULL; + } + if (scan_existing_plugins(plugin_dir) < 0) { + ERROR("Failed to scan existing plugins"); + return NULL; + } + // initilize inotify instance + inotify_fd = inotify_init(); + if (inotify_fd < 0) { + ERROR("Failed to initalize inotify instance"); + return NULL; + } + // add plugin_dir to watch + wd = inotify_add_watch(inotify_fd, plugin_dir, IN_CREATE | IN_DELETE); + if (wd < 0) { + ERROR("Failed to watch plugin dir"); + return NULL; + } + DEBUG("Watching %s for plugin disovery", plugin_dir); + for (;;) { + if (wait_events(inotify_fd) < 0) { + ERROR("Failed to wait events"); + // something abnormal occurs, wait for 1 second and continue + sleep(1); + continue; + } + process_plugin_events(inotify_fd, plugin_dir); + } +} + +int start_plugin_manager(void) +{ + pthread_t thread = 0; + int ret = 0; + ret = pthread_create(&thread, NULL, plugin_manager_routine, NULL); + if (ret) { + ERROR("Thread creation failed"); + return -1; + } + return 0; +} + +static void plugin_rdlock(plugin_t *plugin) +{ + int errcode; + + errcode = pthread_rwlock_rdlock(&plugin->lock); + if (errcode != 0) { + ERROR("Plugin read lock failed: %s", strerror(errcode)); + } +} + +static void plugin_wrlock(plugin_t *plugin) +{ + int errcode; + + errcode = pthread_rwlock_wrlock(&plugin->lock); + if (errcode != 0) { + ERROR("Plugin write lock failed: %s", strerror(errcode)); + } +} + +static void plugin_unlock(plugin_t *plugin) +{ + int errcode; + + errcode = pthread_rwlock_unlock(&plugin->lock); + if (errcode != 0) { + ERROR("Plugin unlock failed: %s", strerror(errcode)); + } +} + +plugin_t *plugin_new(const char *name, const char *addr) +{ + plugin_t *plugin = NULL; + int errcode = 0; + + plugin = util_common_calloc_s(sizeof(plugin_t)); + if (plugin == NULL) { + goto bad; + } + + errcode = pthread_rwlock_init(&plugin->lock, NULL); + if (errcode != 0) { + ERROR("Plugin init lock failed: %s", strerror(errcode)); + goto bad; + } + plugin->name = util_strdup_s(name); + plugin->addr = util_strdup_s(addr); + + plugin->manifest = util_common_calloc_s(sizeof(plugin_manifest_t)); + if (plugin->manifest == NULL) { + goto bad; + } + + return plugin; + +bad: + free_plugin(plugin); + return NULL; +} + +int plugin_set_activated(plugin_t *plugin, bool activated, const char *errmsg) +{ + plugin_wrlock(plugin); + plugin->activated = activated; + if (errmsg != NULL) { + plugin->activated_errcnt++; + UTIL_FREE_AND_SET_NULL(plugin->activated_errmsg); + plugin->activated_errmsg = util_strdup_s(errmsg); + } else { + plugin->activated_errcnt = 0; + UTIL_FREE_AND_SET_NULL(plugin->activated_errmsg); + } + plugin_unlock(plugin); + return 0; +} + +int plugin_set_manifest(plugin_t *plugin, const plugin_manifest_t *manifest) +{ + if (manifest == NULL) { + return -1; + } + + plugin_wrlock(plugin); + plugin->manifest->init_type = manifest->init_type; + plugin->manifest->watch_event = manifest->watch_event; + plugin_unlock(plugin); + return 0; +} + +void plugin_get(plugin_t *plugin) +{ + if (plugin == NULL) { + return; + } + + atomic_int_inc(&plugin->ref); +} + +void plugin_put(plugin_t *plugin) +{ + if (plugin == NULL) { + return; + } + + if (!atomic_int_dec_test(&plugin->ref)) { + return; + } + + free_plugin(plugin); + return; +} + +bool plugin_is_watching(plugin_t *plugin, uint64_t pe) +{ + bool ok = 0; + + if (plugin == NULL) { + ERROR("nil plugin"); + return 0; + } + + plugin_rdlock(plugin); + if (plugin->manifest == NULL) { + ERROR("nil manifest"); + ok = 0; + } else { + ok = plugin->manifest->watch_event & pe; + } + plugin_unlock(plugin); + + INFO("plugin %s watching=%s for event 0x%x", plugin->name, (ok ? "true" : "false"), pe); + + return ok; +} + +static int unpack_activate_response(const struct parsed_http_message *message, void *arg) +{ + int ret = 0; + plugin_manifest_t *manifest = arg; + plugin_activate_plugin_response *resp = NULL; + parser_error err = NULL; + + ret = check_status_code(message->status_code); + if (ret != 0) { + ret = -1; + goto out; + } + + resp = plugin_activate_plugin_response_parse_data(message->body, NULL, &err); + if (resp == NULL) { + ERROR("parse activate response failed"); + ret = -1; + goto out; + } + + if (resp->err_message != NULL) { + ERROR("activate response error massge %s", resp->err_message); + ret = -1; + goto out; + } + + INFO("get resp 0x%x", resp->watch_event); + manifest->init_type = resp->init_type; + manifest->watch_event = resp->watch_event; + +out: + free(err); + free_plugin_activate_plugin_response(resp); + + return ret; +} + +int pm_activate_plugin(plugin_t *plugin) +{ + int ret = 0; + plugin_activate_plugin_request reqs = { 0 }; + char *body = NULL; + size_t body_len = 0; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + Buffer *output = NULL; + char *errmsg = NULL; + plugin_manifest_t manifest = { 0 }; + char socket[PATH_MAX] = { 0 }; + + body = plugin_activate_plugin_request_generate_json(&reqs, &ctx, &err); + if (body == NULL) { + ERROR("marshal activate request to %s failed", plugin->addr); + ret = -1; + goto out; + } + + body_len = strlen(body) + 1; + ret = sprintf_s(socket, PATH_MAX, "unix://%s", plugin->addr); + if (ret < 0) { + ERROR("get plugin socket failed"); + ret = -1; + goto out; + } + + ret = rest_send_requst(socket, RestHttpHead PluginServiceActivate, body, body_len, &output); + if (ret != 0) { + ERROR("send activate request to %s failed", plugin->addr); + goto out; + } + + ret = get_response(output, unpack_activate_response, (void *)(&manifest)); + if (ret != 0) { + ERROR("unpack activate response from %s failed", plugin->addr); + goto out; + } + +out: + plugin_set_activated(plugin, ret == 0, errmsg); + plugin_set_manifest(plugin, &manifest); + + buffer_free(output); + free(err); + free(body); + + return ret; +} + +int pm_deactivate_plugin(plugin_t *plugin) +{ + return 0; +} + +static bool plugin_useby_container(const plugin_t *plugin, const container_t *cont) +{ + bool ok = false; + char *plugin_names = NULL; + char **pnames = NULL; + size_t i = 0; + + if (plugin == NULL || cont == NULL) { + return ok; + } + + if (plugin->name == NULL) { + return ok; + } + + plugin_names = container_get_env_nolock(cont, LCRD_ENABLE_PLUGINS); + pnames = get_enable_plugins(plugin_names); + + for (i = 0; i < util_array_len(pnames); i++) { + if (strcmp(pnames[i], plugin->name) == 0) { + ok = true; + break; + } + } + + free(plugin_names); + free(pnames); + return ok; +} + +static int unpack_init_response(const struct parsed_http_message *message, void *arg) +{ + int ret = 0; + plugin_init_plugin_response *resp = NULL; + parser_error err = NULL; + + ret = check_status_code(message->status_code); + if (ret != 0) { + ret = -1; + goto out; + } + + resp = plugin_init_plugin_response_parse_data(message->body, NULL, &err); + if (resp == NULL) { + ret = -1; + ERROR("parse init response failed"); + goto out; + } + + ret = check_err(resp->err_code, resp->err_message); + if (ret != 0) { + lcrd_set_error_message(resp->err_message); + ERROR("init response error massge (%d)%s", resp->err_code, resp->err_message); + goto out; + } + + INFO("plugin init ok"); +out: + free(err); + free_plugin_init_plugin_response(resp); + + return ret; +} + +/* + * add container info into reqs.elem. + */ +static int pm_prepare_init_reqs(const plugin_t *plugin, plugin_init_plugin_request *reqs, const char *cid) +{ + int ret = 0; + container_t *cont = NULL; + oci_runtime_spec *ocic = NULL; + plugin_init_plugin_request_containers_element *elem = NULL; + + cont = containers_store_get(cid); + if (cont == NULL) { /* container not exist, nothing to do */ + return 0; + } + + if (!plugin_useby_container(plugin, cont)) { + goto out; + } + + if (plugin_get_init_type(plugin) == PLUGIN_INIT_SKIP) { + goto out; + } + + if (plugin_get_init_type(plugin) == PLUGIN_INIT_WITH_CONTAINER_RUNNING && + get_status(cont) != CONTAINER_STATUS_RUNNING) { + goto out; + } + + elem = util_common_calloc_s(sizeof(plugin_init_plugin_request_containers_element)); + if (elem == NULL) { + ret = -1; + ERROR("Out of memory"); + goto out; + } + + elem->id = dup_cid(cont); + if (elem->id == NULL) { + ret = -1; + ERROR("Out of memory"); + goto out; + } + + elem->status = get_status(cont); + + ocic = read_oci_config(cont->root_path, elem->id); + if (ocic == NULL) { + ret = -1; + ERROR("read oci config failed"); + goto out; + } + + elem->pspec = get_pspec(ocic); + if (elem->pspec == NULL) { + ret = -1; + ERROR("marshal pspec failed"); + goto out; + } + + reqs->containers[reqs->containers_len] = elem; + elem = NULL; + reqs->containers_len++; + +out: + free_plugin_init_plugin_request_containers_element(elem); + container_unref(cont); + + free_oci_runtime_spec(ocic); + return ret; +} + +static int pm_init_plugin(const plugin_t *plugin) +{ + int ret = 0; + char **cnames = NULL; + size_t container_num = 0; + plugin_init_plugin_request reqs = { 0 }; + char *body = NULL; + size_t body_len = 0; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + Buffer *output = NULL; + char socket[PATH_MAX] = { 0 }; + size_t i = 0; + + cnames = containers_store_list_ids(); + container_num = util_array_len(cnames); + + /* + * send init request no matter containers exist or not, plugin should + * prepare or delete dirty resource. + */ + if (container_num) { + if (container_num > SIZE_MAX / sizeof(plugin_init_plugin_request_containers_element *)) { + ERROR("Invalid container nums"); + ret = -1; + goto out; + } + reqs.containers = util_common_calloc_s(container_num * sizeof(plugin_init_plugin_request_containers_element *)); + if (reqs.containers == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + } + /* + * add elem to reqs, if no containers availabe add no elem. + */ + for (i = 0; i < container_num; i++) { + ret = pm_prepare_init_reqs(plugin, &reqs, cnames[i]); + if (ret != 0) { + ret = -1; + ERROR("failed prepare init reqs"); + goto out; + } + } + + body = plugin_init_plugin_request_generate_json(&reqs, &ctx, &err); + if (body == NULL) { + ERROR("marshal plugin init request to %s failed %s", plugin->addr, err); + ret = -1; + goto out; + } + + body_len = strlen(body) + 1; + ret = sprintf_s(socket, PATH_MAX, "unix://%s", plugin->addr); + if (ret < 0) { + ERROR("get plugin socket failed %s", plugin->addr); + ret = -1; + goto out; + } + ret = rest_send_requst(socket, RestHttpHead PluginServiceInit, body, body_len, &output); + if (ret != 0) { + ret = -1; + ERROR("plugin init request to %s failed", plugin->addr); + goto out; + } + + ret = get_response(output, unpack_init_response, NULL); + if (ret != 0) { + ret = -1; + ERROR("unpack plugin init response from %s failed", plugin->addr); + goto out; + } + +out: + util_free_array(cnames); + cnames = NULL; + for (i = 0; i < reqs.containers_len; i++) { + UTIL_FREE_AND_SET_NULL(reqs.containers[i]->id); + UTIL_FREE_AND_SET_NULL(reqs.containers[i]->pspec); + UTIL_FREE_AND_SET_NULL(reqs.containers[i]); + } + UTIL_FREE_AND_SET_NULL(reqs.containers); + + buffer_free(output); + + free(err); + free(body); + return ret; +} + +int pm_add_plugin(plugin_t *plugin) +{ + int ok = 0; + pm_wrlock(); + ok = map_insert(g_plugin_manager->np, (void *)plugin->name, plugin); + pm_unlock(); + + if (!ok) { + return -1; + } + + /* plugin_put() called in pm_del_plugin() */ + plugin_get(plugin); + return 0; +} + +int pm_del_plugin(const plugin_t *plugin) +{ + int ok; + pm_wrlock(); + /* plugin_put() called in map_remove() by pm_np_item_free() */ + ok = map_remove(g_plugin_manager->np, (void *)plugin->name); + pm_unlock(); + if (!ok) { + return -1; + } + + return 0; +} + +int pm_get_plugin(const char *name, plugin_t **rplugin) +{ + if (do_get_plugin(name, rplugin) == 0) { + return 0; + } + + if (reload_plugin(name)) { + return -1; + } + + return do_get_plugin(name, rplugin); +} + +void pm_put_plugin(plugin_t *plugin) +{ + plugin_put(plugin); +} + +int pm_get_plugins_nolock(uint64_t pe, plugin_t ***rplugins, size_t *count) +{ + int ret = 0; + int i = 0; + size_t size = 0; + plugin_t **plugins = NULL; + map_itor *itor = NULL; + + size = map_size(g_plugin_manager->np); + if (size == 0) { /* empty */ + return 0; + } + if (size > SIZE_MAX / sizeof(plugin_t *)) { + ret = -1; + ERROR("Invalid plugins size"); + goto out; + } + + plugins = util_common_calloc_s(sizeof(plugin_t *) * size); + if (plugins == NULL) { + ret = -1; + ERROR("Out of memory"); + goto out; + } + + itor = map_itor_new(g_plugin_manager->np); + if (itor == NULL) { + ret = -1; + ERROR("Out of memory"); + goto out; + } + + for (i = 0; i < (int)size && map_itor_valid(itor); i++, map_itor_next(itor)) { + plugins[i] = map_itor_value(itor); + /* plugin_put() called in pm_put_plugins() */ + plugin_get(plugins[i]); + } + + *rplugins = plugins; + *count = (size_t)i; + +out: + map_itor_free(itor); + itor = NULL; + + if (ret < 0) { + UTIL_FREE_AND_SET_NULL(plugins); + } + + return ret; +} + +static void pm_np_item_free(void *key, void *val) +{ + plugin_t *plugin = val; + free(key); + plugin_put(plugin); +} + +static void pm_free(plugin_manager_t *gpm) +{ + if (gpm == NULL) { + return; + } + map_free(gpm->np); + pthread_rwlock_destroy(&gpm->pm_rwlock); + free(gpm); +} + +int pm_init(void) +{ + int ret = 0; + plugin_manager_t *gpm = NULL; + + if (g_plugin_manager != NULL) { + return 0; + } + + gpm = util_common_calloc_s(sizeof(plugin_manager_t)); + if (gpm == NULL) { + return -1; + } + + ret = pthread_rwlock_init(&gpm->pm_rwlock, NULL); + if (ret != 0) { + ret = -1; + goto bad; + } + + gpm->np = map_new(MAP_STR_PTR, MAP_DEFAULT_CMP_FUNC, pm_np_item_free); + if (gpm->np == NULL) { + goto bad; + } + + g_plugin_manager = gpm; + + return 0; +bad: + pm_free(gpm); + gpm = NULL; + + return -1; +} + +static int plugin_event_handle_dispath_impl(const char *cid, const char *plugins, uint64_t pe) +{ + int ret = 0; + plugin_t *plugin = NULL; + char **pnames = NULL; + size_t i = 0; + + pnames = get_enable_plugins(plugins); + if (pnames == NULL) { + goto out; + } + + pm_rdlock(); + for (i = 0; i < util_array_len(pnames); i++) { + if (pm_get_plugin(pnames[i], &plugin)) { /* plugin not found */ + ERROR("plugin %s not registered.", pnames[i]); + ret = -1; + continue; + } + if (!plugin_is_watching(plugin, pe)) { + pm_put_plugin(plugin); + continue; + } + + switch (pe) { + case PLUGIN_EVENT_CONTAINER_PRE_START: + ret = plugin_event_pre_start_handle(plugin, cid); + break; + case PLUGIN_EVENT_CONTAINER_POST_STOP: + ret = plugin_event_post_stop_handle(plugin, cid); + break; + case PLUGIN_EVENT_CONTAINER_POST_REMOVE: + ret = plugin_event_post_remove_handle(plugin, cid); + break; + default: + ERROR("plugin event %d not support.", pe); + ret = -1; + break; + } + + pm_put_plugin(plugin); + if (ret != 0) { + ret = -1; + continue; + } + } + pm_unlock(); + +out: + util_free_array(pnames); + return ret; +} + +static int plugin_event_handle_dispath(const container_t *cont, uint64_t pe) +{ + int ret = 0; + char *cid = NULL; + char *plugins = NULL; + + cid = dup_cid(cont); + if (cid == NULL) { + return 0; + } + + plugins = container_get_env_nolock(cont, LCRD_ENABLE_PLUGINS); + ret = plugin_event_handle_dispath_impl(cid, plugins, pe); + free(cid); + free(plugins); + return ret; +} + +static int unpack_event_pre_create_response(const struct parsed_http_message *message, void *arg) +{ + int ret = 0; + char **pspec = arg; + char *dst = NULL; + plugin_event_pre_create_response *resp = NULL; + parser_error err = NULL; + + ret = check_status_code(message->status_code); + if (ret != 0) { + ret = -1; + goto out; + } + + resp = plugin_event_pre_create_response_parse_data(message->body, NULL, &err); + if (resp == NULL) { + ERROR("parse pre-create response failed"); + ret = -1; + goto out; + } + + ret = check_err(resp->err_code, resp->err_message); + if (ret) { + lcrd_set_error_message(resp->err_message); + ret = -1; + ERROR("pre-create response error massge (%d)%s", resp->err_code, resp->err_message); + goto out; + } + + INFO("pre-create %s ok", resp->id); + + if (resp->pspec == NULL) { + ret = -1; + ERROR("plugin pre-create response missing pspec"); + goto out; + } + + dst = merge_pspec(*pspec, resp->pspec); + if (dst == NULL) { + ERROR("plugin pre-create failed to merge pspec"); + goto out; + } + + *pspec = dst; + dst = NULL; + +out: + free(dst); + free(err); + free_plugin_event_pre_create_response(resp); + return ret; +} + +static int plugin_event_pre_create_handle(const plugin_t *plugin, const char *cid, char **base) +{ + int ret = 0; + char *body = NULL; + size_t body_len = 0; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + Buffer *output = NULL; + char *dst = NULL; + char *new = NULL; + char socket[PATH_MAX] = { 0 }; + plugin_event_pre_create_request reqs = { 0 }; + + reqs.id = (char *)cid; + reqs.pspec = *base; + + body = plugin_event_pre_create_request_generate_json(&reqs, &ctx, &err); + if (body == NULL) { + ERROR("marshal event precreate request to %s failed", plugin->addr); + ret = -1; + goto out; + } + + body_len = strlen(body) + 1; + ret = sprintf_s(socket, sizeof(socket), "unix://%s", plugin->addr); + if (ret < 0) { + ERROR("get plugin socket failed %s", plugin->addr); + ret = -1; + goto out; + } + + ret = rest_send_requst(socket, RestHttpHead PluginServicePreCreate, body, body_len, &output); + if (ret != 0) { + ret = -1; + ERROR("send event precreate request to %s failed", plugin->addr); + goto out; + } + + ret = get_response(output, unpack_event_pre_create_response, (void *)(&new)); + if (ret != 0) { + ret = -1; + ERROR("unpack event precreate response from %s failed", plugin->addr); + goto out; + } + + dst = merge_pspec(*base, new); + if (dst == NULL) { + ret = -1; + ERROR("update pspec json failed"); + goto out; + } + + free(*base); + *base = dst; + dst = NULL; + +out: + free(dst); + free(new); + buffer_free(output); + free(err); + free(body); + return ret; +} + +int plugin_event_container_pre_create(const char *cid, oci_runtime_spec *ocic) +{ + int ret = 0; + plugin_t *plugin = NULL; + char **pnames = NULL; + size_t i = 0; + char *plugin_names = NULL; + char *pspec = NULL; + + if (cid == NULL) { + ERROR("cid is nil pointer"); + return -1; + } + + if (ocic == NULL) { + ERROR("oci spec nil pointer"); + return -1; + } + + if (ocic->process == NULL) { + ERROR("oci spec missing process field"); + return -1; + } + + set_env_enable_plugins(ocic); + plugin_names = oci_container_get_env(ocic, LCRD_ENABLE_PLUGINS); + pnames = get_enable_plugins(plugin_names); + if (pnames == NULL) { + goto out; + } + + pspec = get_pspec(ocic); + if (pspec == NULL) { + ret = -1; + ERROR("failed generate json for pspec"); + goto out; + } + pm_rdlock(); + for (i = 0; i < util_array_len(pnames); i++) { + if (pm_get_plugin(pnames[i], &plugin)) { /* plugin not found */ + ERROR("plugin %s not registered.", pnames[i]); + ret = -1; + break; + } + if (!plugin_is_watching(plugin, (uint64_t)PLUGIN_EVENT_CONTAINER_PRE_CREATE)) { + pm_put_plugin(plugin); + continue; + } + ret = plugin_event_pre_create_handle(plugin, cid, &pspec); + pm_put_plugin(plugin); + if (ret != 0) { + ret = -1; + break; + } + } + pm_unlock(); + + if (ret == 0) { /* all plugins works fine */ + ret = set_pspec(ocic, pspec); + if (ret != 0) { + ERROR("plugin pre-create failed to set pspec into oci"); + } + } else { + ERROR("plugin pre-create failed"); + } + +out: + free(pspec); + free(plugin_names); + util_free_array(pnames); + return ret; +} + +static int unpack_event_pre_start_response(const struct parsed_http_message *message, void *arg) +{ + int ret = 0; + plugin_event_pre_start_response *resp = NULL; + parser_error err = NULL; + + ret = check_status_code(message->status_code); + if (ret != 0) { + ret = -1; + goto out; + } + + resp = plugin_event_pre_start_response_parse_data(message->body, NULL, &err); + if (resp == NULL) { + ret = -1; + ERROR("parse pre-start response failed"); + goto out; + } + + ret = check_err(resp->err_code, resp->err_message); + if (ret != 0) { + lcrd_set_error_message(resp->err_message); + ERROR("pre-start response error massge (%d)%s", resp->err_code, resp->err_message); + goto out; + } + + INFO("pre-start %s ok", resp->id); + +out: + free(err); + free_plugin_event_pre_start_response(resp); + return ret; +} + +static int plugin_event_pre_start_handle(const plugin_t *plugin, const char *cid) +{ + int ret = 0; + char *body = NULL; + size_t body_len = 0; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + Buffer *output = NULL; + char socket[PATH_MAX] = { 0 }; + plugin_event_pre_start_request reqs = { 0 }; + + reqs.id = (char *)cid; + + body = plugin_event_pre_start_request_generate_json(&reqs, &ctx, &err); + if (body == NULL) { + ERROR("marshal event prestart request to %s failed", plugin->addr); + ret = -1; + goto out; + } + + body_len = strlen(body) + 1; + ret = sprintf_s(socket, sizeof(socket), "unix://%s", plugin->addr); + if (ret < 0) { + ERROR("get plugin socket failed %s", plugin->addr); + ret = -1; + goto out; + } + + ret = rest_send_requst(socket, RestHttpHead PluginServicePreStart, body, body_len, &output); + if (ret != 0) { + ret = -1; + ERROR("send event prestart request to %s failed", plugin->addr); + goto out; + } + + ret = get_response(output, unpack_event_pre_start_response, NULL); + if (ret != 0) { + ret = -1; + ERROR("unpack event prestart response from %s failed", plugin->addr); + goto out; + } + +out: + buffer_free(output); + + free(err); + free(body); + return ret; +} + +int plugin_event_container_pre_start(const container_t *cont) +{ + if (cont == NULL) { + ERROR("container nil pointer"); + return 0; + } + + return plugin_event_handle_dispath(cont, (uint64_t)PLUGIN_EVENT_CONTAINER_PRE_START); +} + +static int unpack_event_post_stop_response(const struct parsed_http_message *message, void *arg) +{ + int ret = 0; + plugin_event_post_stop_response *resp = NULL; + parser_error err = NULL; + + ret = check_status_code(message->status_code); + if (ret != 0) { + ret = -1; + goto out; + } + + resp = plugin_event_post_stop_response_parse_data(message->body, NULL, &err); + if (resp == NULL) { + ret = -1; + ERROR("plugin event post_stop response parse failed"); + goto out; + } + + ret = check_err(resp->err_code, resp->err_message); + if (ret != 0) { + lcrd_set_error_message(resp->err_message); + ERROR("post-stop response error massge (%d)%s", resp->err_code, resp->err_message); + goto out; + } + + INFO("post-stop %s ok", resp->id); + +out: + free(err); + + free_plugin_event_post_stop_response(resp); + + return ret; +} + +static int plugin_event_post_stop_handle(const plugin_t *plugin, const char *cid) +{ + int ret = 0; + char *body = NULL; + size_t body_len = 0; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + Buffer *output = NULL; + char socket[PATH_MAX] = { 0 }; + plugin_event_post_stop_request reqs = { 0 }; + + reqs.id = (char *)cid; + + body = plugin_event_post_stop_request_generate_json(&reqs, &ctx, &err); + if (body == NULL) { + ERROR("marshal event post_stop request to %s failed", plugin->addr); + ret = -1; + goto out; + } + + body_len = strlen(body) + 1; + ret = sprintf_s(socket, sizeof(socket), "unix://%s", plugin->addr); + if (ret < 0) { + ERROR("get plugin socket failed %s", plugin->addr); + ret = -1; + goto out; + } + + ret = rest_send_requst(socket, RestHttpHead PluginServicePostStop, body, body_len, &output); + if (ret != 0) { + ret = -1; + ERROR("send event post_stop request to %s failed", plugin->addr); + goto out; + } + + ret = get_response(output, unpack_event_post_stop_response, NULL); + if (ret != 0) { + ret = -1; + ERROR("unpack event post_stop response from %s failed", plugin->addr); + goto out; + } + +out: + buffer_free(output); + free(err); + free(body); + return ret; +} + +int plugin_event_container_post_stop(const container_t *cont) +{ + if (cont == NULL) { + ERROR("container nil pointer"); + return 0; + } + + return plugin_event_handle_dispath(cont, (uint64_t)PLUGIN_EVENT_CONTAINER_POST_STOP); +} + +static int unpack_event_post_remove_response(const struct parsed_http_message *message, void *arg) +{ + int ret = 0; + plugin_event_post_remove_response *resp = NULL; + parser_error err = NULL; + + ret = check_status_code(message->status_code); + if (ret != 0) { + ret = -1; + goto out; + } + + resp = plugin_event_post_remove_response_parse_data(message->body, NULL, &err); + if (resp == NULL) { + ret = -1; + ERROR("plugin event post_remove response parse failed"); + goto out; + } + + ret = check_err(resp->err_code, resp->err_message); + if (ret != 0) { + lcrd_set_error_message(resp->err_message); + ERROR("post-remove response error massge (%d)%s", resp->err_code, resp->err_message); + goto out; + } + + INFO("post-remove %s ok", resp->id); + +out: + free(err); + + free_plugin_event_post_remove_response(resp); + + return ret; +} + +static int plugin_event_post_remove_handle(const plugin_t *plugin, const char *cid) +{ + int ret = 0; + char *body = NULL; + size_t body_len = 0; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + Buffer *output = NULL; + char socket[PATH_MAX] = { 0 }; + plugin_event_post_remove_request reqs = { 0 }; + + reqs.id = (char *)cid; + + body = plugin_event_post_remove_request_generate_json(&reqs, &ctx, &err); + if (body == NULL) { + ERROR("marshal event post_remove request to %s failed", plugin->addr); + ret = -1; + goto out; + } + + body_len = strlen(body) + 1; + ret = sprintf_s(socket, sizeof(socket), "unix://%s", plugin->addr); + if (ret < 0) { + ERROR("get plugin socket failed %s", plugin->addr); + ret = -1; + goto out; + } + + ret = rest_send_requst(socket, RestHttpHead PluginServicePostRemove, body, body_len, &output); + if (ret != 0) { + ret = -1; + ERROR("send event post_remove request to %s failed", plugin->addr); + goto out; + } + + ret = get_response(output, unpack_event_post_remove_response, NULL); + if (ret != 0) { + ret = -1; + ERROR("unpack event post_remove response from %s failed", plugin->addr); + goto out; + } + +out: + buffer_free(output); + free(err); + free(body); + return ret; +} + +int plugin_event_container_post_remove(const container_t *cont) +{ + if (cont == NULL) { + ERROR("container nil pointer"); + return 0; + } + + return plugin_event_handle_dispath(cont, (uint64_t)PLUGIN_EVENT_CONTAINER_POST_REMOVE); +} + +int plugin_event_container_post_remove2(const char *cid, const oci_runtime_spec *oci) +{ + char *plugins = NULL; + char *cidx = NULL; + int ret = 0; + + if (cid == NULL) { + ERROR("cid nil pointer"); + return 0; + } + + if (oci == NULL) { + ERROR("oci nil pointer"); + return 0; + } + + if (oci->process == NULL) { + ERROR("oci->process nil pointer"); + return 0; + } + + plugins = oci_container_get_env(oci, LCRD_ENABLE_PLUGINS); + cidx = util_strdup_s(cid); + if (cidx == NULL) { + ERROR("out of memory"); + goto out; + } + + ret = plugin_event_handle_dispath_impl(cidx, plugins, (uint64_t)PLUGIN_EVENT_CONTAINER_POST_REMOVE); + +out: + free(cidx); + free(plugins); + return ret; +} + diff --git a/src/plugin/plugin.h b/src/plugin/plugin.h new file mode 100644 index 0000000..fe6e25d --- /dev/null +++ b/src/plugin/plugin.h @@ -0,0 +1,116 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: jingrui + * Create: 2018-12-01 + * Description: provide plugin definition + ******************************************************************************/ + +#ifndef _PLUGIN_H_ +#define _PLUGIN_H_ /* _PLUGIN_H_ */ + +#include + +#include "map.h" +#include "specs.h" /* oci_runtime_spec */ +#include "container_unix.h" /* container_t */ + +/* + * returned int should means: + * 0 success + * -1 failed + * if not or has other values, please add comment to the function prototype. + * + * when check return value, if want to ingore err, please reset err=0 and add + * comment. + */ + +#define PLUGIN_INIT_SKIP 0 +#define PLUGIN_INIT_WITH_CONTAINER_RUNNING 1 +#define PLUGIN_INIT_WITH_CONTAINER_ALL 2 + +#define PLUGIN_EVENT_CONTAINER_PRE_CREATE 1UL +#define PLUGIN_EVENT_CONTAINER_PRE_START (1UL << 1) +#define PLUGIN_EVENT_CONTAINER_POST_STOP (1UL << 2) +#define PLUGIN_EVENT_CONTAINER_POST_REMOVE (1UL << 3) + +typedef struct plugin_manifest { + uint64_t init_type; + uint64_t watch_event; +} plugin_manifest_t; + +typedef struct plugin { + pthread_rwlock_t lock; + + const char *name; + const char *addr; + plugin_manifest_t *manifest; + + bool activated; + size_t activated_errcnt; + char *activated_errmsg; + + uint64_t ref; +} plugin_t; + +/* + * plugin_new() will take initial get. when the plugin should free, one + * more plugint_put() shall be called. + */ +plugin_t *plugin_new(const char *name, const char *addr); +void plugin_get(plugin_t *plugin); /* ref++ */ +void plugin_put(plugin_t *plugin); /* ref-- */ + +int plugin_set_activated(plugin_t *plugin, bool activated, const char *errmsg); +int plugin_set_manifest(plugin_t *plugin, const plugin_manifest_t *manifest); +bool plugin_is_watching(plugin_t *plugin, uint64_t pe); + +typedef struct plugin_manager { + pthread_rwlock_t pm_rwlock; + map_t *np; /* name:plugin */ + map_t *eps; /* watch_event:plugins */ +} plugin_manager_t; + +plugin_manager_t *g_plugin_manager; + +/* + * init at lcrd start, scan and init/sync all plugins + */ +int pm_init(void); +int pm_scan(void); +/* + * destroy at lcrd exit + */ +int pm_destroy(); +/* + * init plugin manifest + */ +int pm_activate_plugin(plugin_t *plugin); +int pm_deactivate_plugin(plugin_t *plugin); + +int pm_add_plugin(plugin_t *plugin); +int pm_del_plugin(const plugin_t *plugin); + +/* + * make sure get and put called in-pairs. + * if not, please add comment. + */ +int pm_get_plugin(const char *name, plugin_t **rplugin); +void pm_put_plugin(plugin_t *plugin); +int pm_get_plugins_nolock(uint64_t pe, plugin_t ***rplugins, size_t *count); + +int start_plugin_manager(void); +int plugin_event_container_pre_create(const char *cid, oci_runtime_spec *ocic); +int plugin_event_container_pre_start(const container_t *cont); +int plugin_event_container_post_stop(const container_t *cont); +int plugin_event_container_post_remove(const container_t *cont); +int plugin_event_container_post_remove2(const char *cid, const oci_runtime_spec *oci); + +#endif /* _PLUGIN_H_ */ diff --git a/src/plugin/pspec.c b/src/plugin/pspec.c new file mode 100644 index 0000000..07f5a1e --- /dev/null +++ b/src/plugin/pspec.c @@ -0,0 +1,256 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: jingrui + * Create: 2018-12-01 + * Description: provide plugin definition + ******************************************************************************/ + +#include "utils.h" +#include "log.h" +#include "oci_runtime_spec.h" +#include "oci_runtime_pspec.h" +#include "plugin.h" +#include "pspec.h" + +/* + * update field in old & clear in new. + * field must be *ptr. + */ +#define PTR_UPDATE(old, new, field, ffree) \ + do { \ + if ((new)->field == NULL) { \ + break; \ + } \ + ffree((old)->field); \ + (old)->field = (new)->field; \ + (new)->field = NULL; \ + } while (0) + +/* + * update field in old & clear in new. + * field must be **ptr. + */ +#define PPR_UPDATE(old, new, field, len_field, ffree) \ + do { \ + if ((new) == NULL || (new)->field == NULL) { \ + break; \ + } \ + if ((old)->field != NULL) { \ + size_t ix_ = 0; \ + for (ix_ = 0; ix_ < (old)->len_field; ix_++) { \ + ffree((old)->field[ix_]); \ + (old)->field[ix_] = NULL; \ + } \ + free((old)->field); \ + (old)->field = NULL; \ + } \ + (old)->field = (new)->field; \ + (new)->field = NULL; \ + (old)->len_field = (new)->len_field; \ + (new)->len_field = 0; \ + } while (0) + +static oci_runtime_pspec *raw_get_pspec(oci_runtime_spec *oci) +{ + oci_runtime_pspec *pspec = NULL; + + if (oci == NULL) { + return NULL; + } + + pspec = util_common_calloc_s(sizeof(oci_runtime_pspec)); + if (pspec == NULL) { + ERROR("Out of memory"); + return NULL; + } + if (oci->linux->resources != NULL && pspec->resources == NULL) { + pspec->resources = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources)); + if (pspec->resources == NULL) { + ERROR("Out of memory"); + free_oci_runtime_pspec(pspec); + return NULL; + } + } + + PTR_UPDATE(pspec, oci, annotations, free_json_map_string_string); + PTR_UPDATE(pspec, oci, root, free_oci_runtime_spec_root); + + PPR_UPDATE(pspec, oci, mounts, mounts_len, free_defs_mount); + PPR_UPDATE(pspec, oci->process, env, env_len, free); + PPR_UPDATE(pspec, oci->linux, devices, devices_len, free_oci_runtime_defs_linux_device); + + PPR_UPDATE(pspec->resources, oci->linux->resources, devices, devices_len, + free_oci_runtime_defs_linux_device_cgroup); + + return pspec; +} + +static void raw_set_pspec(oci_runtime_spec *oci, oci_runtime_pspec *pspec) +{ + if (oci == NULL || pspec == NULL) { + return; + } + if (pspec->resources != NULL && oci->linux->resources == NULL) { + oci->linux->resources = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources)); + if (oci->linux->resources == NULL) { + ERROR("out of memory"); + return; + } + } + + PTR_UPDATE(oci, pspec, annotations, free_json_map_string_string); + PTR_UPDATE(oci, pspec, root, free_oci_runtime_spec_root); + + PPR_UPDATE(oci, pspec, mounts, mounts_len, free_defs_mount); + PPR_UPDATE(oci->process, pspec, env, env_len, free); + PPR_UPDATE(oci->linux, pspec, devices, devices_len, free_oci_runtime_defs_linux_device); + + PPR_UPDATE(oci->linux->resources, pspec->resources, devices, devices_len, + free_oci_runtime_defs_linux_device_cgroup); +} + +char *get_pspec(oci_runtime_spec *oci) +{ + oci_runtime_pspec *pspec = NULL; + char *data = NULL; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + + if (oci == NULL) { + return NULL; + } + + pspec = raw_get_pspec(oci); + if (pspec == NULL) { + ERROR("failed load pspec"); + return NULL; + } + + data = oci_runtime_pspec_generate_json(pspec, &ctx, &err); + if (data == NULL) { + ERROR("failed gernerate json for pspec error=%s", err); + } + UTIL_FREE_AND_SET_NULL(err); + + if (pspec != NULL) { + raw_set_pspec(oci, pspec); /* make sure oci not modified before + return. */ + free_oci_runtime_pspec(pspec); + } + + return data; +} + +int set_pspec(oci_runtime_spec *oci, const char *data) +{ + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + oci_runtime_pspec *pspec = NULL; + + if (data == NULL) { + return 0; + } + + pspec = oci_runtime_pspec_parse_data(data, &ctx, &err); + UTIL_FREE_AND_SET_NULL(err); + if (pspec == NULL) { + ERROR("failed parse json for pspec"); + goto failed; + } + + raw_set_pspec(oci, pspec); + free_oci_runtime_pspec(pspec); + return 0; + +failed: + return -1; +} + +static void raw_update_pspec(oci_runtime_pspec *base, oci_runtime_pspec *new) +{ + if (base == NULL || new == NULL) { + return; + } + if (new->resources != NULL && base->resources == NULL) { + base->resources = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources)); + if (base->resources == NULL) { + ERROR("Out of memory"); + return; + } + } + + PTR_UPDATE(base, new, annotations, free_json_map_string_string); + PTR_UPDATE(base, new, root, free_oci_runtime_spec_root); + + PPR_UPDATE(base, new, mounts, mounts_len, free_defs_mount); + PPR_UPDATE(base, new, env, env_len, free); + PPR_UPDATE(base, new, devices, devices_len, free_oci_runtime_defs_linux_device); + + PPR_UPDATE(base->resources, new->resources, devices, devices_len, free_oci_runtime_defs_linux_device_cgroup); +} + +char *merge_pspec(const char *base, const char *data) +{ + char *dst = NULL; + oci_runtime_pspec *old = NULL; + oci_runtime_pspec *new = NULL; + struct parser_context ctx = { + OPT_GEN_SIMPLIFY, + 0, + }; + parser_error err = NULL; + + if (base == NULL && data == NULL) { + return NULL; + } + + if (base != NULL) { + old = oci_runtime_pspec_parse_data(base, &ctx, &err); + UTIL_FREE_AND_SET_NULL(err); + if (old == NULL) { + ERROR("failed parse old json for pspec"); + return NULL; + } + } + + if (data != NULL) { + new = oci_runtime_pspec_parse_data(data, &ctx, &err); + UTIL_FREE_AND_SET_NULL(err); + if (new == NULL) { + ERROR("failed parse new json for pspec"); + goto revert_free_old; + } + } + + raw_update_pspec(old, new); + if (old == NULL) { + old = new; + new = NULL; + } + + dst = oci_runtime_pspec_generate_json(old, &ctx, &err); + UTIL_FREE_AND_SET_NULL(err); + if (dst == NULL) { + ERROR("failed gernerate json for runtime_info"); + } + + free_oci_runtime_pspec(new); +revert_free_old: + free_oci_runtime_pspec(old); + + return dst; +} diff --git a/src/plugin/pspec.h b/src/plugin/pspec.h new file mode 100644 index 0000000..2a34465 --- /dev/null +++ b/src/plugin/pspec.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: jingrui + * Create: 2018-12-01 + * Description: provide plugin definition + ******************************************************************************/ + +#ifndef PSPEC_H +#define PSPEC_H /* PSPEC_H */ + +#include "oci_runtime_spec.h" +#include "oci_runtime_pspec.h" + +/* + * extract pspec from oci. + * return NULL when failed. oci not modified. + */ +char *get_pspec(oci_runtime_spec *oci); + +/* + * set pspec into oci. + * return -1 when failed. return 0 means ok. + */ +int set_pspec(oci_runtime_spec *oci, const char *data); + +/* + * generate new pspec using base and data. + * return NULL when failed. + * if field in both base and data, using data. + * if field in base and missing in data, using base. + */ +char *merge_pspec(const char *base, const char *data); + +#endif /* PSPEC_H */ diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt new file mode 100644 index 0000000..da75cfd --- /dev/null +++ b/src/services/CMakeLists.txt @@ -0,0 +1,31 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/ services_top_srcs) +set(local_services_srcs ${services_top_srcs}) +set(local_services_incs ${CMAKE_CURRENT_SOURCE_DIR}) +if (GRPC_CONNECTOR) + add_subdirectory(cri) + list(APPEND local_services_srcs ${CRI_SRCS}) + list(APPEND local_services_incs ${CMAKE_CURRENT_SOURCE_DIR}/cri) +endif() +add_subdirectory(execution) +list(APPEND local_services_srcs ${EXECUTION_SRCS}) +list(APPEND local_services_incs ${EXECUTION_INCS}) + +if (NOT DISABLE_OCI) + add_subdirectory(graphdriver) + list(APPEND local_services_srcs ${GRAPHDRIVER_SRCS}) + list(APPEND local_services_incs ${GRAPHDRIVER_INCS}) +endif() + +add_subdirectory(image) +list(APPEND local_services_srcs ${IMAGE_SRCS}) +list(APPEND local_services_incs ${CMAKE_CURRENT_SOURCE_DIR}/image) + +set(SERVICES_SRCS + ${local_services_srcs} + PARENT_SCOPE + ) +set(SERVICES_INCS + ${local_services_incs} + PARENT_SCOPE + ) diff --git a/src/services/callback.c b/src/services/callback.c new file mode 100644 index 0000000..fe03f78 --- /dev/null +++ b/src/services/callback.c @@ -0,0 +1,35 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container callback functions + ******************************************************************************/ +#include "callback.h" + +#include "image_cb.h" +#include "execution.h" +#include "securec.h" + +service_callback_t g_isulad_servicecallback; + +/* service callback */ +int service_callback_init(void) +{ + container_callback_init(&g_isulad_servicecallback.container); + image_callback_init(&g_isulad_servicecallback.image); + return 0; +} + +/* get service callback */ +service_callback_t *get_service_callback(void) +{ + return &g_isulad_servicecallback; +} diff --git a/src/services/callback.h b/src/services/callback.h new file mode 100644 index 0000000..0bf5997 --- /dev/null +++ b/src/services/callback.h @@ -0,0 +1,177 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-08 + * Description: provide container callback definition + ******************************************************************************/ +#ifndef __SERVICE_CALLBACK_H_ +#define __SERVICE_CALLBACK_H_ + +#include "liblcrd.h" +#include "console.h" +#include "container_get_id_request.h" +#include "container_get_id_response.h" +#include "container_create_request.h" +#include "container_create_response.h" +#include "container_start_request.h" +#include "container_start_response.h" +#include "container_top_request.h" +#include "container_top_response.h" +#include "container_stop_request.h" +#include "container_stop_response.h" +#include "container_update_request.h" +#include "container_update_response.h" +#include "container_kill_request.h" +#include "container_kill_response.h" +#include "container_delete_request.h" +#include "container_delete_response.h" +#include "container_restart_request.h" +#include "container_restart_response.h" +#include "container_exec_request.h" +#include "container_exec_response.h" +#include "container_inspect_request.h" +#include "container_inspect_response.h" +#include "container_attach_request.h" +#include "container_attach_response.h" +#include "container_pause_request.h" +#include "container_pause_response.h" +#include "container_list_request.h" +#include "container_list_response.h" +#include "container_resume_request.h" +#include "container_resume_response.h" +#include "container_stats_request.h" +#include "container_stats_response.h" +#include "container_wait_request.h" +#include "container_wait_response.h" +#include "container_version_request.h" +#include "container_version_response.h" +#include "container_path_stat.h" +#include "container_copy_to_request.h" +#include "host_info_request.h" +#include "host_info_response.h" +#include "image_delete_image_request.h" +#include "image_delete_image_response.h" +#include "image_load_image_request.h" +#include "image_load_image_response.h" +#include "image_inspect_request.h" +#include "image_inspect_response.h" +#include "image_list_images_request.h" +#include "image_list_images_response.h" +#include "container_export_request.h" +#include "container_export_response.h" +#include "image_login_request.h" +#include "image_login_response.h" +#include "image_logout_request.h" +#include "image_logout_response.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + int(*subscribe)(char *name, types_timestamp_t *since, + types_timestamp_t *until, struct lcrd_events_format ***events); + + int(*wait)(struct lcrd_events_request *request, stream_func_wrapper *stream); +} service_container_events_callback_t; + +typedef struct { + int(*version)(const container_version_request *request, container_version_response **response); + + int(*info)(const host_info_request *request, host_info_response **response); + + int(*get_id)(const container_get_id_request *request, container_get_id_response **response); + + int(*create)(const container_create_request *request, container_create_response **response); + + int(*start)(const container_start_request *request, container_start_response **response, + int stdinfd, struct io_write_wrapper *stdout, struct io_write_wrapper *stderr); + + int(*top)(container_top_request *request, container_top_response **response); + + int(*stop)(const container_stop_request *request, container_stop_response **response); + + int(*restart)(const container_restart_request *request, container_restart_response **response); + + int(*kill)(const container_kill_request *request, container_kill_response **response); + + int(*remove)(const container_delete_request *request, container_delete_response **response); + + int(*list)(const container_list_request *request, container_list_response **response); + + int(*exec)(const container_exec_request *request, container_exec_response **response, + int stdinfd, struct io_write_wrapper *stdout); + + int(*attach)(const container_attach_request *request, container_attach_response **response, + int stdinfd, struct io_write_wrapper *stdout, struct io_write_wrapper *stderr); + + int(*update)(const container_update_request *request, container_update_response **response); + + int(*stats)(const container_stats_request *request, container_stats_response **response); + + int(*pause)(const container_pause_request *request, container_pause_response **response); + + int(*resume)(const container_resume_request *request, container_resume_response **response); + + int(*inspect)(const container_inspect_request *request, container_inspect_response **response); + + int(*conf)(const struct lcrd_container_conf_request *request, struct lcrd_container_conf_response **response); + + int(*wait)(const container_wait_request *request, container_wait_response **response); + + int(*events)(const struct lcrd_events_request *request, const stream_func_wrapper *stream); + + int(*export_rootfs)(const container_export_request *request, container_export_response **response); + + int(*copy_from_container)(const struct lcrd_copy_from_container_request *request, const stream_func_wrapper *stream, + char **err); + + int(*copy_to_container)(const container_copy_to_request *request, stream_func_wrapper *stream, char **err); + + int(*rename)(const struct lcrd_container_rename_request *request, struct lcrd_container_rename_response **response); + + int(*logs)(const struct lcrd_logs_request *request, + stream_func_wrapper *stream, struct lcrd_logs_response **response); +} service_container_callback_t; + +typedef struct { + int(*list)(const image_list_images_request *request, image_list_images_response **response); + + int(*remove)(const image_delete_image_request *request, image_delete_image_response **response); + + int(*load)(const image_load_image_request *request, image_load_image_response **response); + + int(*inspect)(const image_inspect_request *request, image_inspect_response **response); + + int(*login)(const image_login_request *request, image_login_response **response); + + int(*logout)(const image_logout_request *request, image_logout_response **response); +} service_image_callback_t; + +typedef struct { + int(*check)(const struct lcrd_health_check_request *request, struct lcrd_health_check_response *response); +} service_health_callback_t; + +typedef struct { + service_container_callback_t container; + service_image_callback_t image; + service_health_callback_t health; +} service_callback_t; + +int service_callback_init(void); + +service_callback_t *get_service_callback(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/cri/CMakeLists.txt b/src/services/cri/CMakeLists.txt new file mode 100644 index 0000000..5679e69 --- /dev/null +++ b/src/services/cri/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_cri_srcs) + +set(CRI_SRCS + ${local_cri_srcs} + PARENT_SCOPE + ) diff --git a/src/services/cri/checkpoint_handler.cc b/src/services/cri/checkpoint_handler.cc new file mode 100644 index 0000000..6d72146 --- /dev/null +++ b/src/services/cri/checkpoint_handler.cc @@ -0,0 +1,366 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide checkpoint handler functions + *********************************************************************************/ +#include "checkpoint_handler.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "utils.h" +#include "log.h" +#include "cri_helpers.h" +#include "cri_checkpoint.h" + +namespace cri { +PortMapping &PortMapping::operator=(const PortMapping &obj) +{ + if (&obj == this) { + return *this; + } + delete m_protocol; + if (obj.m_protocol != nullptr) { + m_protocol = new std::string(*(obj.m_protocol)); + } else { + m_protocol = nullptr; + } + delete m_containerPort; + if (obj.m_containerPort != nullptr) { + m_containerPort = new int32_t(*(obj.m_containerPort)); + } else { + m_containerPort = nullptr; + } + delete m_hostPort; + if (obj.m_hostPort != nullptr) { + m_hostPort = new int32_t(*(obj.m_hostPort)); + } else { + m_hostPort = nullptr; + } + return *this; +} + +PortMapping::PortMapping(const PortMapping &obj) +{ + if (obj.m_protocol != nullptr) { + m_protocol = new std::string(*(obj.m_protocol)); + } + if (obj.m_containerPort != nullptr) { + m_containerPort = new int32_t(*(obj.m_containerPort)); + } + if (obj.m_hostPort != nullptr) { + m_hostPort = new int32_t(*(obj.m_hostPort)); + } +} + +PortMapping::~PortMapping() +{ + delete m_protocol; + delete m_containerPort; + delete m_hostPort; +} + +const std::string *PortMapping::GetProtocol() const +{ + return m_protocol; +} +void PortMapping::SetProtocol(const std::string &protocol) +{ + if (m_protocol != nullptr) { + *m_protocol = protocol; + } else { + m_protocol = new std::string(protocol); + } +} + +const int32_t *PortMapping::GetContainerPort() const +{ + return m_containerPort; +} + +void PortMapping::SetContainerPort(int32_t containerPort) +{ + if (m_containerPort != nullptr) { + *m_containerPort = containerPort; + } else { + m_containerPort = new int32_t(containerPort); + } +} + +const int32_t *PortMapping::GetHostPort() const +{ + return m_hostPort; +} + +void PortMapping::SetHostPort(int32_t hostPort) +{ + if (m_hostPort != nullptr) { + *m_hostPort = hostPort; + } else { + m_hostPort = new int32_t(hostPort); + } +} + +void PortMapping::PortMappingToCStruct(cri_port_mapping **pmapping, Errors &error) +{ + if (pmapping == nullptr) { + return; + } + *pmapping = (cri_port_mapping *)util_common_calloc_s(sizeof(cri_port_mapping)); + if (*pmapping == nullptr) { + error.SetError("Out of memory"); + goto out; + } + if (m_protocol != nullptr) { + (*pmapping)->protocol = util_strdup_s(m_protocol->c_str()); + } + if (m_containerPort != nullptr) { + (*pmapping)->container_port = (int32_t *)util_common_calloc_s(sizeof(int32_t)); + if ((*pmapping)->container_port == nullptr) { + error.SetError("Out of memory"); + goto out; + } + *((*pmapping)->container_port) = *m_containerPort; + } + if (m_hostPort != nullptr) { + (*pmapping)->host_port = (int32_t *)util_common_calloc_s(sizeof(int32_t)); + if ((*pmapping)->host_port == nullptr) { + error.SetError("Out of memory"); + goto out; + } + *((*pmapping)->host_port) = *m_hostPort; + } + + return; +out: + free_cri_port_mapping(*pmapping); + *pmapping = nullptr; +} + +void PortMapping::CStructToPortMapping(const cri_port_mapping *pmapping, Errors &error) +{ + if (pmapping == nullptr) { + return; + } + if (pmapping->protocol != nullptr) { + m_protocol = new std::string(pmapping->protocol); + } + if (pmapping->container_port != nullptr) { + m_containerPort = new int32_t(*(pmapping->container_port)); + } + if (pmapping->host_port != nullptr) { + m_hostPort = new int32_t(*(pmapping->host_port)); + } +} + +const std::vector &CheckpointData::GetPortMappings() const +{ + return m_portMappings; +} + +void CheckpointData::InsertPortMapping(const PortMapping &portMapping) +{ + m_portMappings.push_back(portMapping); +} + +bool CheckpointData::GetHostNetwork() +{ + return m_hostNetwork; +} + +void CheckpointData::SetHostNetwork(bool hostNetwork) +{ + m_hostNetwork = hostNetwork; +} + +void CheckpointData::CheckpointDataToCStruct(cri_checkpoint_data **data, Errors &error) +{ + size_t len = m_portMappings.size(); + + if (data == nullptr) { + return; + } + *data = (cri_checkpoint_data *)util_common_calloc_s(sizeof(cri_checkpoint_data)); + if (*data == nullptr) { + error.SetError("Out of memory"); + goto out; + } + (*data)->host_network = m_hostNetwork; + if (len > 0) { + if (len > SIZE_MAX / sizeof(cri_port_mapping *)) { + error.SetError("Invalid port mapping size"); + goto out; + } + (*data)->port_mappings = (cri_port_mapping **)util_common_calloc_s(sizeof(cri_port_mapping *) * len); + if ((*data)->port_mappings == nullptr) { + error.SetError("Out of memory"); + goto out; + } + for (size_t i = 0; i < len; i++) { + cri_port_mapping *tmp = nullptr; + m_portMappings[i].PortMappingToCStruct(&tmp, error); + if (error.NotEmpty()) { + goto out; + } + (*data)->port_mappings[i] = tmp; + (*data)->port_mappings_len++; + } + } + return; +out: + free_cri_checkpoint_data(*data); +} + +void CheckpointData::CStructToCheckpointData(const cri_checkpoint_data *data, Errors &error) +{ + if (data == nullptr) { + return; + } + m_hostNetwork = data->host_network; + if (data->port_mappings && data->port_mappings_len > 0) { + for (size_t i = 0; i < data->port_mappings_len; i++) { + PortMapping tmpPortMap; + tmpPortMap.CStructToPortMapping(data->port_mappings[i], error); + if (error.NotEmpty()) { + goto out; + } + m_portMappings.push_back(tmpPortMap); + } + } + return; +out: + m_hostNetwork = false; + m_portMappings.clear(); +} + +const std::string &PodSandboxCheckpoint::GetVersion() const +{ + return m_version; +} + +void PodSandboxCheckpoint::SetVersion(const std::string &version) +{ + m_version = version; +} + +const std::string &PodSandboxCheckpoint::GetName() const +{ + return m_name; +} + +void PodSandboxCheckpoint::SetName(const std::string &name) +{ + m_name = name; +} + +const std::string &PodSandboxCheckpoint::GetNamespace() const +{ + return m_namespace; +} + +void PodSandboxCheckpoint::SetNamespace(const std::string &ns) +{ + m_namespace = ns; +} + +std::shared_ptr PodSandboxCheckpoint::GetData() +{ + return m_data; +} + +void PodSandboxCheckpoint::SetData(CheckpointData *data) +{ + m_data = std::shared_ptr(data); +} + +const std::string &PodSandboxCheckpoint::GetCheckSum() const +{ + return m_checkSum; +} + +void PodSandboxCheckpoint::SetCheckSum(const std::string &checkSum) +{ + m_checkSum = checkSum; +} + +void PodSandboxCheckpoint::CheckpointToCStruct(cri_checkpoint **checkpoint, Errors &error) +{ + cri_checkpoint_data *cpData = nullptr; + + if (checkpoint == nullptr) { + return; + } + *checkpoint = (cri_checkpoint *)util_common_calloc_s(sizeof(cri_checkpoint)); + if (*checkpoint == nullptr) { + error.SetError("Out of memory"); + goto out; + } + + if (m_data != nullptr) { + m_data->CheckpointDataToCStruct(&cpData, error); + if (error.NotEmpty()) { + goto out; + } + } + + (*checkpoint)->data = cpData; + (*checkpoint)->version = util_strdup_s(m_version.c_str()); + (*checkpoint)->name = util_strdup_s(m_name.c_str()); + (*checkpoint)->ns = util_strdup_s(m_namespace.c_str()); + (*checkpoint)->checksum = util_strdup_s(m_checkSum.c_str()); + + return; +out: + free_cri_checkpoint(*checkpoint); + *checkpoint = nullptr; +} + +void PodSandboxCheckpoint::CStructToCheckpoint(const cri_checkpoint *checkpoint, Errors &error) +{ + if (checkpoint == nullptr) { + return; + } + + if (checkpoint->data != nullptr) { + m_data = std::make_shared(); + m_data->CStructToCheckpointData(checkpoint->data, error); + if (error.NotEmpty()) { + m_data = nullptr; + return; + } + } + + if (checkpoint->version != nullptr) { + m_version = checkpoint->version; + } + + if (checkpoint->name != nullptr) { + m_name = checkpoint->name; + } + + if (checkpoint->ns != nullptr) { + m_namespace = checkpoint->ns; + } + + if (checkpoint->checksum != nullptr) { + m_checkSum = checkpoint->checksum; + } +} + +} // namespace cri + diff --git a/src/services/cri/checkpoint_handler.h b/src/services/cri/checkpoint_handler.h new file mode 100644 index 0000000..1547fc1 --- /dev/null +++ b/src/services/cri/checkpoint_handler.h @@ -0,0 +1,91 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide checkpoint handler function definition + *********************************************************************************/ +#ifndef _CRI_CHECKPOINT_H +#define _CRI_CHECKPOINT_H +#include +#include +#include + +#include "errors.h" +#include "cri_checkpoint.h" + +namespace cri { +const std::string SANDBOX_CHECKPOINT_DIR { "sandbox" }; + +class PortMapping { +public: + PortMapping() = default; + PortMapping(const PortMapping &obj); + PortMapping &operator=(const PortMapping &); + ~PortMapping(); + void PortMappingToCStruct(cri_port_mapping **pmapping, Errors &error); + void CStructToPortMapping(const cri_port_mapping *pmapping, Errors &error); + + const std::string *GetProtocol() const; + void SetProtocol(const std::string &protocol); + const int32_t *GetContainerPort() const; + void SetContainerPort(int32_t containerPort); + const int32_t *GetHostPort() const; + void SetHostPort(int32_t hostPort); + +private: + std::string *m_protocol { nullptr }; + int32_t *m_containerPort { nullptr }; + int32_t *m_hostPort { nullptr }; +}; + +class CheckpointData { +public: + void CheckpointDataToCStruct(cri_checkpoint_data **data, Errors &error); + void CStructToCheckpointData(const cri_checkpoint_data *data, Errors &error); + + const std::vector &GetPortMappings() const; + void InsertPortMapping(const PortMapping &portMapping); + bool GetHostNetwork(); + void SetHostNetwork(bool hostNetwork); + +private: + std::vector m_portMappings; + bool m_hostNetwork { false }; +}; + +class PodSandboxCheckpoint { +public: + PodSandboxCheckpoint() = default; + ~PodSandboxCheckpoint() = default; + void CheckpointToCStruct(cri_checkpoint **checkpoint, Errors &error); + void CStructToCheckpoint(const cri_checkpoint *checkpoint, Errors &error); + + const std::string &GetVersion() const; + void SetVersion(const std::string &version); + const std::string &GetName() const; + void SetName(const std::string &name); + const std::string &GetNamespace() const; + void SetNamespace(const std::string &ns); + std::shared_ptr GetData(); + void SetData(CheckpointData *data); + const std::string &GetCheckSum() const; + void SetCheckSum(const std::string &checkSum); + +private: + std::string m_version { "v1" }; + std::string m_name; + std::string m_namespace; + std::shared_ptr m_data { nullptr }; + std::string m_checkSum; +}; + +} // namespace cri +#endif diff --git a/src/services/cri/cni_network_plugin.cc b/src/services/cri/cni_network_plugin.cc new file mode 100644 index 0000000..2809a70 --- /dev/null +++ b/src/services/cri/cni_network_plugin.cc @@ -0,0 +1,719 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cni network plugin function definition + **********************************************************************************/ +#include "cni_network_plugin.h" +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "cri_helpers.h" + +namespace Network { +static std::string VendorCNIDir(const std::string &prefix, const std::string &pluginType) +{ + return prefix + "/opt/" + pluginType + "/bin"; +} + +static std::unique_ptr GetLoNetwork(const std::string &binDir, const std::string &vendorDirPrefix) +{ + const std::string loNetConfListJson { "{\"cniVersion\": \"0.3.0\", \"name\": \"cni-loopback\"," + "\"plugins\":[{\"type\": \"loopback\" }]}" }; + + char *cerr { nullptr }; + struct cni_network_list_conf *loConf { + nullptr + }; + if (cni_conflist_from_bytes(loNetConfListJson.c_str(), &loConf, &cerr) != 0) { + if (cerr != nullptr) { + ERROR("invalid lo config: %s", cerr); + free(cerr); + } + char **traces = get_backtrace(); + if (traces != nullptr) { + ERROR("show backtrace: "); + for (char **sym = traces; sym && *sym; sym++) { + ERROR("%s", *sym); + } + util_free_array(traces); + } + sync(); + exit(1); + } + + auto result = std::unique_ptr(new CNINetwork("lo", loConf)); + result->InsertPath(VendorCNIDir(vendorDirPrefix, "loopback")); + result->InsertPath(binDir); + + return result; +} + +CNINetwork::CNINetwork(const std::string &name, struct cni_network_list_conf *list) + : m_name(name) + , m_networkConfig(list) +{ +} + +CNINetwork::~CNINetwork() +{ + free_cni_network_list_conf(m_networkConfig); +} + +char **CNINetwork::GetPaths(Errors &err) +{ + char **paths = CRIHelpers::StringVectorToCharArray(m_path); + if (paths == nullptr) { + err.SetError("Get char ** path failed"); + } + return paths; +} + +void ProbeNetworkPlugins(const std::string &pluginDir, const std::string &binDir, + std::vector> *plugins) +{ + const std::string useBinDir = binDir.empty() ? DEFAULT_CNI_DIR : binDir; + auto plugin = std::make_shared(useBinDir, pluginDir); + plugin->SetLoNetwork(GetLoNetwork(useBinDir, "")); + plugins->push_back(plugin); +} + +void CniNetworkPlugin::SetLoNetwork(std::unique_ptr lo) +{ + if (lo != nullptr) { + m_loNetwork = std::move(lo); + } +} + +CniNetworkPlugin::CniNetworkPlugin(const std::string &binDir, const std::string &pluginDir, + const std::string &vendorCNIDirPrefix) + : m_pluginDir(pluginDir) + , m_vendorCNIDirPrefix(vendorCNIDirPrefix) + , m_binDir(binDir) +{ +} + +CniNetworkPlugin::~CniNetworkPlugin() +{ + m_networks.clear(); +} + +void CniNetworkPlugin::PlatformInit(Errors &error) +{ + char *tpath { nullptr }; + char *serr { nullptr }; + tpath = look_path(const_cast("nsenter"), &serr); + if (tpath == nullptr) { + error.SetError(serr); + return; + } + m_nsenterPath = tpath; + free(tpath); + return; +} + +int CniNetworkPlugin::GetCNIConfFiles(const std::string &pluginDir, std::vector &vect_files, Errors &err) +{ + int ret { 0 }; + std::string usePluginDir { pluginDir }; + const char *exts[] { ".conf", ".conflist", ".json" }; + char **files { nullptr }; + char *serr { nullptr }; + + if (usePluginDir.empty()) { + usePluginDir = DEFAULT_NET_DIR; + } + + ret = cni_conf_files(usePluginDir.c_str(), exts, sizeof(exts) / sizeof(char *), &files, &serr); + if (ret != 0) { + err.Errorf("get conf files: %s", serr); + ret = -1; + goto out; + } + + if (util_array_len(files) == 0) { + err.Errorf("No networks found in %s", usePluginDir.c_str()); + ret = -1; + goto out; + } + + vect_files = std::vector(files, files + util_array_len(files)); + +out: + free(serr); + util_free_array(files); + return ret; +} + +int CniNetworkPlugin::LoadCNIConfigFileList(const std::string &elem, struct cni_network_list_conf **n_list) +{ + int ret { 0 }; + std::size_t found = elem.rfind(".conflist"); + char *serr { nullptr }; + struct cni_network_conf *n_conf { + nullptr + }; + + if (found != std::string::npos && found + strlen(".conflist") == elem.length()) { + if (cni_conflist_from_file(elem.c_str(), n_list, &serr) != 0) { + WARN("Error loading CNI config list file %s: %s", elem.c_str(), serr); + ret = -1; + goto out; + } + } else { + if (cni_conf_from_file(elem.c_str(), &n_conf, &serr) != 0) { + WARN("Error loading CNI config file %s: %s", elem.c_str(), serr); + ret = -1; + goto out; + } + if (n_conf->type == nullptr || strcmp(n_conf->type, "") == 0) { + WARN("Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", elem.c_str()); + ret = -1; + goto out; + } + if (cni_conflist_from_conf(n_conf, n_list, &serr) != 0) { + WARN("Error converting CNI config file %s to list: %s", elem.c_str(), serr); + ret = -1; + goto out; + } + } +out: + if (n_conf != nullptr) { + free_cni_network_conf(n_conf); + } + free(serr); + return ret; +} + +int CniNetworkPlugin::InsertConfNameToAllPanes(struct cni_network_list_conf *n_list, std::set &allPanes, + Errors &err) +{ + int ret { 0 }; + std::string confName { "" }; + + if (n_list == nullptr) { + err.Errorf("Invalid arguments"); + return -1; + } + if (n_list->first_plugin_name != nullptr) { + confName = n_list->first_plugin_name; + } + + if (confName.empty() || allPanes.find(confName) != allPanes.end()) { + free_cni_network_list_conf(n_list); + n_list = nullptr; + ret = -1; + ERROR("Invalid cni network name: %s, it may be duplicated or empty.", confName.c_str()); + err.Errorf("Invalid cni network name: %s, it may be duplicated or empty.", confName.c_str()); + goto out; + } + allPanes.insert(confName); + +out: + return ret; +} + +int CniNetworkPlugin::InsertNewNetwork(struct cni_network_list_conf *n_list, + std::map> &newNets, + const std::string &binDir, const std::string &vendorCNIDirPrefix, Errors &err) +{ + std::string confType { "" }; + + if (n_list == nullptr) { + err.Errorf("Invalid arguments"); + return -1; + } + if (n_list->first_plugin_type != nullptr) { + confType = n_list->first_plugin_type; + } + + std::string tpath = VendorCNIDir(vendorCNIDirPrefix, confType); + if (tpath.empty()) { + free_cni_network_list_conf(n_list); + n_list = nullptr; + err.SetError("Out of memory"); + return -1; + } + std::unique_ptr network(new CNINetwork(n_list->name, n_list)); + network->InsertPath(tpath); + network->InsertPath(binDir); + + std::string n_key(network->GetName()); + newNets.insert(std::pair>(n_key, std::move(network))); + DEBUG("---parse cni network: %s finish ----", n_key.c_str()); + + return 0; +} + +void CniNetworkPlugin::ResetCNINetwork(std::map> &newNets, Errors &err) +{ + std::string pluginNames { "map[" }; + + m_networks.clear(); + for (auto iter = newNets.begin(); iter != newNets.end(); iter++) { + m_networks[iter->first] = std::move(iter->second); + pluginNames += (iter->first + " "); + } + INFO("Loaded cni plugins successfully, all plugins: %s]", pluginNames.substr(0, pluginNames.length() - 1).c_str()); +} + +void CniNetworkPlugin::GetCNINetwork(const std::string &pluginDir, const std::string &binDir, + const std::string &vendorCNIDirPrefix, Errors &err) +{ + std::vector files; + std::set allPanes; + std::map> newNets; + + if (GetCNIConfFiles(pluginDir, files, err) != 0) { + goto free_out; + } + + for (auto elem : files) { + struct cni_network_list_conf *n_list = nullptr; + + if (LoadCNIConfigFileList(elem, &n_list) != 0) { + continue; + } + + if (n_list == nullptr || n_list->plugin_len == 0) { + WARN("CNI config list %s has no networks, skipping", elem.c_str()); + free_cni_network_list_conf(n_list); + n_list = nullptr; + continue; + } + + if (InsertConfNameToAllPanes(n_list, allPanes, err) != 0) { + goto free_out; + } + + if (InsertNewNetwork(n_list, newNets, binDir, vendorCNIDirPrefix, err) != 0) { + goto free_out; + } + } + + if (newNets.size() == 0) { + err.Errorf("No valid networks found in %s", pluginDir.c_str()); + goto free_out; + } + ResetCNINetwork(newNets, err); + +free_out: + newNets.clear(); + return; +} + +void CniNetworkPlugin::CheckInitialized(Errors &err) +{ + RLockNetworkMap(err); + if (err.NotEmpty()) { + ERROR("%s", err.GetCMessage()); + return; + } + size_t len = m_networks.size(); + UnlockNetworkMap(err); + if (len == 0) { + err.AppendError("cni config uninitialized"); + } +} + +void CniNetworkPlugin::SyncNetworkConfig() +{ + Errors err; + WLockNetworkMap(err); + if (err.NotEmpty()) { + ERROR("%s", err.GetCMessage()); + return; + } + GetCNINetwork(m_pluginDir, m_binDir, m_vendorCNIDirPrefix, err); + if (err.NotEmpty()) { + WARN("Unable to update cni config: %s", err.GetCMessage()); + } + UnlockNetworkMap(err); + if (err.NotEmpty()) { + ERROR("%s", err.GetCMessage()); + } +} + +void CniNetworkPlugin::Init(CRIRuntimeServiceImpl *criImpl, const std::string &hairpinMode, + const std::string &nonMasqueradeCIDR, int mtu, Errors &error) +{ + UNUSED(hairpinMode); + UNUSED(nonMasqueradeCIDR); + UNUSED(mtu); + + if (criImpl == nullptr) { + error.Errorf("Empty runtime service"); + return; + } + PlatformInit(error); + if (error.NotEmpty()) { + return; + } + m_criImpl = criImpl; + SyncNetworkConfig(); + + return; +} + +const std::string &CniNetworkPlugin::Name() const +{ + return CNI_PLUGIN_NAME; +} + +void CniNetworkPlugin::Status(Errors &err) +{ + SyncNetworkConfig(); + + CheckInitialized(err); +} + +void CniNetworkPlugin::SetUpPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &id, + const std::map &annotations, Errors &err) +{ + CheckInitialized(err); + if (err.NotEmpty()) { + return; + } + std::string netnsPath = m_criImpl->GetNetNS(id, err); + if (err.NotEmpty()) { + ERROR("CNI failed to retrieve network namespace path: %s", err.GetCMessage()); + return; + } + auto iter = annotations.find(CRIHelpers::Constants::POD_CHECKPOINT_KEY); + std::string jsonCheckpoint { "" }; + if (iter != annotations.end()) { + jsonCheckpoint = iter->second; + } + DEBUG("add checkpoint: ", jsonCheckpoint.c_str()); + + struct result *preResult = nullptr; + if (m_loNetwork != nullptr) { + AddToNetwork(m_loNetwork.get(), jsonCheckpoint, name, ns, interfaceName, id, netnsPath, &preResult, err); + free_result(preResult); + preResult = nullptr; + if (err.NotEmpty()) { + ERROR("Error while adding to cni lo network: %s", err.GetCMessage()); + return; + } + } + + RLockNetworkMap(err); + if (err.NotEmpty()) { + ERROR("%s", err.GetCMessage()); + return; + } + auto netIter = m_networks.find(networkPlane); + if (netIter == m_networks.end()) { + ERROR("Can't find cni plugin for network plane %s.", networkPlane.c_str()); + err.Errorf("Can't find cni plugin for network plane %s.", networkPlane.c_str()); + goto unlock_out; + } + + AddToNetwork((netIter->second).get(), jsonCheckpoint, name, ns, interfaceName, id, netnsPath, &preResult, err); + free_result(preResult); + preResult = nullptr; + if (err.NotEmpty()) { + ERROR("Error while adding to cni network: %s", err.GetCMessage()); + } + +unlock_out: + UnlockNetworkMap(err); +} + +void CniNetworkPlugin::TearDownPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &id, + const std::map &annotations, Errors &err) +{ + CheckInitialized(err); + if (err.NotEmpty()) { + return; + } + + std::string netnsPath = m_criImpl->GetNetNS(id, err); + if (err.NotEmpty()) { + WARN("CNI failed to retrieve network namespace path: %s", err.GetCMessage()); + err.Clear(); + } + + auto iter = annotations.find(CRIHelpers::Constants::POD_CHECKPOINT_KEY); + std::string jsonCheckpoint = ""; + if (iter != annotations.end()) { + jsonCheckpoint = iter->second; + } + DEBUG("delete checkpoint: ", jsonCheckpoint.c_str()); + RLockNetworkMap(err); + if (err.NotEmpty()) { + ERROR("%s", err.GetCMessage()); + return; + } + auto netIter = m_networks.find(networkPlane); + if (netIter == m_networks.end()) { + ERROR("Can't find cni plugin for network plane %s.", networkPlane.c_str()); + err.Errorf("Can't find cni plugin for network plane %s.", networkPlane.c_str()); + goto unlock_out; + } + + DeleteFromNetwork((netIter->second).get(), jsonCheckpoint, name, ns, interfaceName, id, netnsPath, err); + +unlock_out: + UnlockNetworkMap(err); +} + +std::map *CniNetworkPlugin::Capabilities() +{ + return m_noop.Capabilities(); +} + +void CniNetworkPlugin::Event(const std::string &name, std::map &details) +{ + m_noop.Event(name, details); +} + +void CniNetworkPlugin::GetPodNetworkStatus(const std::string &ns, const std::string &name, + const std::string &interfaceName, const std::string &podSandboxID, + PodNetworkStatus &status, Errors &err) +{ + std::string netnsPath, ip; + Errors tmpErr; + + if (podSandboxID.empty()) { + err.SetError("Empty podsandbox ID"); + goto out; + } + + netnsPath = m_criImpl->GetNetNS(podSandboxID, tmpErr); + if (tmpErr.NotEmpty()) { + err.Errorf("CNI failed to retrieve network namespace path: %s", tmpErr.GetCMessage()); + goto out; + } + if (netnsPath.empty()) { + err.Errorf("Cannot find the network namespace, skipping pod network status for container %s", + podSandboxID.c_str()); + goto out; + } + ip = GetPodIP(m_nsenterPath, netnsPath, interfaceName, err); + if (err.NotEmpty()) { + ERROR("GetPodIP failed: %s", err.GetCMessage()); + goto out; + } + status.SetIP(ip); + +out: + INFO("get_pod_network_status: %s", podSandboxID.c_str()); +} + +void CniNetworkPlugin::AddToNetwork(CNINetwork *snetwork, const std::string &jsonCheckpoint, const std::string &podName, + const std::string &podNamespace, const std::string &interfaceName, + const std::string &podSandboxID, const std::string &podNetnsPath, + struct result **presult, Errors &err) +{ + struct runtime_conf *rc { + nullptr + }; + + if (snetwork == nullptr || presult == nullptr) { + err.Errorf("Invalid arguments"); + ERROR("Invalid arguments"); + return; + } + BuildCNIRuntimeConf(podName, jsonCheckpoint, podNamespace, interfaceName, podSandboxID, podNetnsPath, &rc, err); + if (err.NotEmpty()) { + ERROR("Error adding network when building cni runtime conf: %s", err.GetCMessage()); + return; + } + + INFO("About to add CNI network %s (type=%s)", snetwork->GetName().c_str(), snetwork->GetNetworkType().c_str()); + + char **paths = snetwork->GetPaths(err); + if (paths == nullptr) { + ERROR("Empty cni bin path"); + free_runtime_conf(rc); + return; + } + char *serr = nullptr; + int nret = cni_add_network_list(snetwork->GetNetworkConfigJsonStr().c_str(), rc, paths, presult, &serr); + if (nret != 0) { + ERROR("Error adding network: %s", serr); + err.SetError(serr); + } + + util_free_array(paths); + free_runtime_conf(rc); + free(serr); +} + +void CniNetworkPlugin::DeleteFromNetwork(CNINetwork *network, const std::string &jsonCheckpoint, + const std::string &podName, const std::string &podNamespace, + const std::string &interfaceName, const std::string &podSandboxID, + const std::string &podNetnsPath, Errors &err) +{ + struct runtime_conf *rc { + nullptr + }; + + if (network == nullptr) { + err.Errorf("Invalid arguments"); + ERROR("Invalid arguments"); + return; + } + BuildCNIRuntimeConf(podName, jsonCheckpoint, podNamespace, interfaceName, podSandboxID, podNetnsPath, &rc, err); + if (err.NotEmpty()) { + ERROR("Error deleting network when building cni runtime conf: %s", err.GetCMessage()); + return; + } + + INFO("About to del CNI network %s (type=%s)", network->GetName().c_str(), network->GetNetworkType().c_str()); + + char **paths = network->GetPaths(err); + if (paths == nullptr) { + free_runtime_conf(rc); + ERROR("Empty cni bin path"); + return; + } + char *serr = nullptr; + int nret = cni_del_network_list(network->GetNetworkConfigJsonStr().c_str(), rc, paths, &serr); + if (nret != 0) { + ERROR("Error deleting network: %s", serr); + err.Errorf("Error deleting network: %s", serr); + } + + util_free_array(paths); + free_runtime_conf(rc); + free(serr); +} + +void CniNetworkPlugin::BuildCNIRuntimeConf(const std::string &podName, const std::string &jsonCheckpoint, + const std::string &podNs, const std::string &interfaceName, + const std::string &podSandboxID, const std::string &podNetnsPath, + struct runtime_conf **cni_rc, Errors &err) +{ + std::vector portMappings; + INFO("Got netns path %s", podNetnsPath.c_str()); + INFO("Using podns path %s", podNs.c_str()); + + if (cni_rc == nullptr) { + err.Errorf("Invalid arguments"); + ERROR("Invalid arguments"); + return; + } + struct runtime_conf *rt = (struct runtime_conf *)util_common_calloc_s(sizeof(struct runtime_conf)); + if (rt == nullptr) { + ERROR("Out of memory"); + err.SetError("Out of memory"); + return; + } + + rt->container_id = util_strdup_s(podSandboxID.c_str()); + rt->netns = util_strdup_s(podNetnsPath.c_str()); + rt->ifname = util_strdup_s(interfaceName.c_str()); + + rt->args = (char *(*)[2])util_common_calloc_s(sizeof(char *) * 2 * 4); + if (rt->args == nullptr) { + ERROR("Out of memory"); + err.SetError("Out of memory"); + goto free_out; + } + rt->args_len = 4; + rt->args[0][0] = util_strdup_s("IgnoreUnknown"); + rt->args[0][1] = util_strdup_s("1"); + rt->args[1][0] = util_strdup_s("K8S_POD_NAMESPACE"); + rt->args[1][1] = util_strdup_s(podNs.c_str()); + rt->args[2][0] = util_strdup_s("K8S_POD_NAME"); + rt->args[2][1] = util_strdup_s(podName.c_str()); + rt->args[3][0] = util_strdup_s("K8S_POD_INFRA_CONTAINER_ID"); + rt->args[3][1] = util_strdup_s(podSandboxID.c_str()); + + if (!jsonCheckpoint.empty()) { + cri::PodSandboxCheckpoint checkpoint; + CRIHelpers::GetCheckpoint(jsonCheckpoint, checkpoint, err); + if (err.NotEmpty() || checkpoint.GetData() == nullptr) { + err.Errorf("could not retrieve port mappings: %s", err.GetCMessage()); + goto free_out; + } + std::copy(checkpoint.GetData()->GetPortMappings().begin(), + checkpoint.GetData()->GetPortMappings().end(), + std::back_inserter(portMappings)); + } + + if (portMappings.size() > 0) { + if (portMappings.size() > SIZE_MAX / sizeof(struct cni_port_mapping *)) { + err.SetError("Invalid cni port mapping size"); + goto free_out; + } + rt->p_mapping = (struct cni_port_mapping **)util_common_calloc_s(sizeof(struct cni_port_mapping *) * + portMappings.size()); + if (rt->p_mapping == nullptr) { + err.SetError("Out of memory"); + goto free_out; + } + for (auto iter = portMappings.cbegin(); iter != portMappings.cend(); iter++) { + if (iter->GetHostPort() && *(iter->GetHostPort()) <= 0) { + continue; + } + rt->p_mapping[rt->p_mapping_len] = + (struct cni_port_mapping *)util_common_calloc_s(sizeof(struct cni_port_mapping)); + if (rt->p_mapping[rt->p_mapping_len] == nullptr) { + err.SetError("Out of memory"); + goto free_out; + } + if (iter->GetHostPort()) { + rt->p_mapping[rt->p_mapping_len]->host_port = *(iter->GetHostPort()); + } + if (iter->GetContainerPort()) { + rt->p_mapping[rt->p_mapping_len]->container_port = *(iter->GetContainerPort()); + } + if (iter->GetProtocol()) { + rt->p_mapping[rt->p_mapping_len]->protocol = strings_to_lower(iter->GetProtocol()->c_str()); + } + // ignore hostip, because GetPodPortMappings() don't set + (rt->p_mapping_len)++; + } + } + + *cni_rc = rt; + return; +free_out: + free_runtime_conf(rt); +} + +void CniNetworkPlugin::RLockNetworkMap(Errors &error) +{ + int ret = pthread_rwlock_rdlock(&m_netsLock); + if (ret != 0) { + error.Errorf("Get read lock failed: %s", strerror(ret)); + } +} + +void CniNetworkPlugin::WLockNetworkMap(Errors &error) +{ + int ret = pthread_rwlock_wrlock(&m_netsLock); + if (ret != 0) { + error.Errorf("Get write lock failed: %s", strerror(ret)); + } +} + +void CniNetworkPlugin::UnlockNetworkMap(Errors &error) +{ + int ret = pthread_rwlock_unlock(&m_netsLock); + if (ret != 0) { + error.Errorf("Unlock failed: %s", strerror(ret)); + } +} + +} // namespace Network diff --git a/src/services/cri/cni_network_plugin.h b/src/services/cri/cni_network_plugin.h new file mode 100644 index 0000000..30ee81d --- /dev/null +++ b/src/services/cri/cni_network_plugin.h @@ -0,0 +1,157 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cni network plugin function definition + ********************************************************************************/ +#ifndef _CRI_CNI_NETWORK_PLUGIN_H_ +#define _CRI_CNI_NETWORK_PLUGIN_H_ + +#include +#include +#include +#include +#include +#include + +#include "network_plugin.h" +#include "utils.h" +#include "errors.h" +#include "cri_runtime_service.h" + +namespace Network { +#define UNUSED(x) ((void)(x)) +static const std::string CNI_PLUGIN_NAME { "cni" }; +static const std::string DEFAULT_NET_DIR { "/etc/cni/net.d" }; +static const std::string DEFAULT_CNI_DIR { "/opt/cni/bin" }; + +class CNINetwork { +public: + CNINetwork() = delete; + CNINetwork(const CNINetwork &) = delete; + CNINetwork &operator=(const CNINetwork &) = delete; + CNINetwork(const std::string &name, struct cni_network_list_conf *netList); + ~CNINetwork(); + const std::string &GetName() const + { + return m_name; + } + void SetName(const std::string &name) + { + m_name = name; + } + void InsertPath(const std::string &path) + { + m_path.push_back(path); + } + std::string GetNetworkConfigJsonStr() + { + return m_networkConfig->bytes ? m_networkConfig->bytes : ""; + } + std::string GetNetworkType() const + { + return m_networkConfig->first_plugin_type ? m_networkConfig->first_plugin_type : ""; + } + std::string GetNetworkName() const + { + return m_networkConfig->first_plugin_name ? m_networkConfig->first_plugin_name : ""; + } + char **GetPaths(Errors &err); + +private: + std::string m_name; + std::vector m_path; + struct cni_network_list_conf *m_networkConfig { + nullptr + }; +}; + +class CniNetworkPlugin : public NetworkPlugin { +public: + CniNetworkPlugin(const std::string &binDir, const std::string &pluginDir, + const std::string &vendorCNIDirPrefix = ""); + + virtual ~CniNetworkPlugin(); + + void Init(CRIRuntimeServiceImpl *criImpl, const std::string &hairpinMode, const std::string &nonMasqueradeCIDR, + int mtu, Errors &error) override; + + void Event(const std::string &name, std::map &details) override; + + const std::string &Name() const override; + + std::map *Capabilities() override; + + void SetUpPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + const std::map &annotations, Errors &error) override; + + void TearDownPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + const std::map &annotations, Errors &error) override; + + void GetPodNetworkStatus(const std::string &ns, const std::string &name, const std::string &interfaceName, + const std::string &podSandboxID, PodNetworkStatus &status, Errors &error) override; + + void Status(Errors &error) override; + + virtual void SetLoNetwork(std::unique_ptr lo); + +private: + virtual void PlatformInit(Errors &error); + virtual void SyncNetworkConfig(); + + virtual void GetCNINetwork(const std::string &pluginDir, const std::string &binDir, + const std::string &vendorCNIDirPrefix, Errors &error); + + virtual void CheckInitialized(Errors &error); + + virtual void AddToNetwork(CNINetwork *network, const std::string &jsonCheckpoint, const std::string &podName, + const std::string &podNamespace, const std::string &interfaceName, + const std::string &podSandboxID, const std::string &podNetnsPath, struct result **presult, + Errors &error); + + virtual void DeleteFromNetwork(CNINetwork *network, const std::string &jsonCheckpoint, const std::string &podName, + const std::string &podNamespace, const std::string &interfaceName, + const std::string &podSandboxID, const std::string &podNetnsPath, Errors &error); + + virtual void BuildCNIRuntimeConf(const std::string &podName, const std::string &jsonCheckpoint, + const std::string &podNs, const std::string &interfaceName, + const std::string &podSandboxID, const std::string &podNetnsPath, + struct runtime_conf **cni_rc, Errors &error); + +private: + void RLockNetworkMap(Errors &error); + void WLockNetworkMap(Errors &error); + void UnlockNetworkMap(Errors &error); + int GetCNIConfFiles(const std::string &pluginDir, std::vector &vect_files, Errors &err); + int LoadCNIConfigFileList(const std::string &elem, struct cni_network_list_conf **n_list); + int InsertConfNameToAllPanes(struct cni_network_list_conf *n_list, std::set &allPanes, Errors &err); + int InsertNewNetwork(struct cni_network_list_conf *n_list, + std::map> &newNets, const std::string &binDir, + const std::string &vendorCNIDirPrefix, Errors &err); + void ResetCNINetwork(std::map> &newNets, Errors &err); + + NoopNetworkPlugin m_noop; + std::unique_ptr m_loNetwork { nullptr }; + CRIRuntimeServiceImpl *m_criImpl { nullptr }; + std::string m_nsenterPath; + std::string m_pluginDir; + std::string m_vendorCNIDirPrefix; + std::string m_binDir; + + pthread_rwlock_t m_netsLock = PTHREAD_RWLOCK_INITIALIZER; + std::map> m_networks; +}; + +} // namespace Network + +#endif diff --git a/src/services/cri/cri_container.cc b/src/services/cri/cri_container.cc new file mode 100644 index 0000000..06f034e --- /dev/null +++ b/src/services/cri/cri_container.cc @@ -0,0 +1,1414 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri container definition + *********************************************************************************/ +#include "cri_container.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "container_custom_config.h" +#include "timestamp.h" +#include "cri_helpers.h" +#include "path.h" +#include "naming.h" +#include "parse_common.h" + +#include "cri_runtime_service.h" +#include "request_cache.h" +#include "url.h" +#include "ws_server.h" + +std::string CRIRuntimeServiceImpl::GetRealContainerOrSandboxID(const std::string &id, bool isSandbox, Errors &error) +{ + std::string realID; + + if (m_cb == nullptr || m_cb->container.get_id == nullptr) { + error.SetError("Unimplemented callback"); + return realID; + } + + container_get_id_request *request { nullptr }; + container_get_id_response *response { nullptr }; + request = (container_get_id_request *)util_common_calloc_s(sizeof(container_get_id_request)); + if (request == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + request->id_or_name = util_strdup_s(id.c_str()); + if (isSandbox) { + std::string label = CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY + "=" + + CRIHelpers::Constants::CONTAINER_TYPE_LABEL_SANDBOX; + request->label = util_strdup_s(label.c_str()); + } else { + std::string label = CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY + "=" + + CRIHelpers::Constants::CONTAINER_TYPE_LABEL_CONTAINER; + request->label = util_strdup_s(label.c_str()); + } + + if (m_cb->container.get_id(request, &response) != 0) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + goto cleanup; + } else { + error.SetError("Failed to call get id callback"); + goto cleanup; + } + } + + if (strncmp(response->id, id.c_str(), id.length()) != 0) { + error.Errorf("No such container with id: %s", id.c_str()); + goto cleanup; + } + + realID = response->id; + +cleanup: + free_container_get_id_request(request); + free_container_get_id_response(response); + return realID; +} + +void CRIRuntimeServiceImpl::GetContainerTimeStamps(container_inspect *inspect, int64_t *createdAt, int64_t *startedAt, + int64_t *finishedAt, Errors &err) +{ + if (inspect == nullptr) { + err.SetError("Invalid arguments"); + return; + } + if (createdAt != nullptr) { + if (to_unix_nanos_from_str(inspect->created, createdAt)) { + err.Errorf("Parse createdAt failed: %s", inspect->created); + return; + } + } + if (inspect->state != nullptr) { + if (startedAt != nullptr) { + if (to_unix_nanos_from_str(inspect->state->started_at, startedAt)) { + err.Errorf("Parse startedAt failed: %s", inspect->state->started_at); + return; + } + } + if (finishedAt != nullptr) { + if (to_unix_nanos_from_str(inspect->state->finished_at, finishedAt)) { + err.Errorf("Parse finishedAt failed: %s", inspect->state->finished_at); + return; + } + } + } +} + +container_custom_config *CRIRuntimeServiceImpl::GenerateCreateContainerCustomConfig( + const std::string &realPodSandboxID, const runtime::ContainerConfig &containerConfig, + const runtime::PodSandboxConfig &podSandboxConfig, Errors &error) +{ + container_custom_config *custom_config = + (container_custom_config *)util_common_calloc_s(sizeof(container_custom_config)); + if (custom_config == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + custom_config->labels = CRIHelpers::MakeLabels(containerConfig.labels(), error); + if (error.NotEmpty()) { + goto cleanup; + } + if (append_json_map_string_string(custom_config->labels, CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY.c_str(), + CRIHelpers::Constants::CONTAINER_TYPE_LABEL_CONTAINER.c_str())) { + error.SetError("Append map string string failed"); + goto cleanup; + } + + custom_config->annotations = CRIHelpers::MakeAnnotations(containerConfig.annotations(), error); + if (error.NotEmpty()) { + goto cleanup; + } + + if (!podSandboxConfig.log_directory().empty() || !containerConfig.log_path().empty()) { + std::string logpath = podSandboxConfig.log_directory() + "/" + containerConfig.log_path(); + char real_logpath[PATH_MAX] { 0 }; + if (cleanpath(logpath.c_str(), real_logpath, sizeof(real_logpath)) == nullptr) { + ERROR("Failed to clean path: %s", logpath.c_str()); + error.Errorf("Failed to clean path: %s", logpath.c_str()); + goto cleanup; + } + + if (append_json_map_string_string(custom_config->labels, + CRIHelpers::Constants::CONTAINER_LOGPATH_LABEL_KEY.c_str(), + real_logpath)) { + error.SetError("Append map string string failed"); + goto cleanup; + } + } + + if (append_json_map_string_string(custom_config->labels, CRIHelpers::Constants::SANDBOX_ID_LABEL_KEY.c_str(), + realPodSandboxID.c_str())) { + error.SetError("Append map string string failed"); + goto cleanup; + } + MakeContainerConfig(containerConfig, custom_config, error); + if (error.NotEmpty()) { + goto cleanup; + } + return custom_config; + +cleanup: + free_container_custom_config(custom_config); + return nullptr; +} + +int CRIRuntimeServiceImpl::PackCreateContainerHostConfigDevices(const runtime::ContainerConfig &containerConfig, + host_config *hostconfig, Errors &error) +{ + int ret { 0 }; + + if (containerConfig.devices_size() == 0) { + return 0; + } + if (static_cast(containerConfig.devices_size()) > SIZE_MAX / sizeof(host_config_devices_element *)) { + error.Errorf("Invalid device size"); + return -1; + } + hostconfig->devices = (host_config_devices_element **)util_common_calloc_s(containerConfig.devices_size() * + sizeof(host_config_devices_element *)); + if (hostconfig->devices == nullptr) { + error.Errorf("Out of memory"); + ret = -1; + goto out; + } + for (int i = 0; i < containerConfig.devices_size(); i++) { + hostconfig->devices[i] = + (host_config_devices_element *)util_common_calloc_s(sizeof(host_config_devices_element)); + if (hostconfig->devices[i] == nullptr) { + ret = -1; + goto out; + } + hostconfig->devices[i]->path_on_host = util_strdup_s(containerConfig.devices(i).host_path().c_str()); + hostconfig->devices[i]->path_in_container = util_strdup_s(containerConfig.devices(i).container_path().c_str()); + hostconfig->devices[i]->cgroup_permissions = util_strdup_s(containerConfig.devices(i).permissions().c_str()); + hostconfig->devices_len++; + } +out: + return ret; +} + +int CRIRuntimeServiceImpl::PackCreateContainerHostConfigSecurityContext(const runtime::ContainerConfig &containerConfig, + host_config *hostconfig, Errors &error) +{ + if (!containerConfig.linux().has_security_context()) { + return 0; + } + // security Opt Separator Change Version : k8s v1.23.0 (Corresponds to docker 1.11.x) + // New version '=' , old version ':', iSulad cri is based on v18.09, so iSulad cri use new version separator + const char securityOptSep { '=' }; + std::vector securityOpts = CRIHelpers::GetSecurityOpts( + containerConfig.linux().security_context().seccomp_profile_path(), + securityOptSep, error); + if (error.NotEmpty()) { + error.Errorf("failed to generate security options for container %s", containerConfig.metadata().name().c_str()); + return -1; + } + if (securityOpts.size() > 0) { + char **tmp_security_opt = nullptr; + if (securityOpts.size() > (SIZE_MAX / sizeof(char *)) - hostconfig->security_opt_len) { + error.Errorf("Out of memory"); + return -1; + } + size_t newSize = (hostconfig->security_opt_len + securityOpts.size()) * sizeof(char *); + size_t oldSize = hostconfig->security_opt_len * sizeof(char *); + int ret = mem_realloc((void **)(&tmp_security_opt), newSize, (void *)hostconfig->security_opt, oldSize); + if (ret != 0) { + error.Errorf("Out of memory"); + return -1; + } + hostconfig->security_opt = tmp_security_opt; + for (size_t i = 0; i < securityOpts.size(); i++) { + hostconfig->security_opt[hostconfig->security_opt_len] = util_strdup_s(securityOpts[i].c_str()); + hostconfig->security_opt_len++; + } + } + return 0; +} + +host_config *CRIRuntimeServiceImpl::GenerateCreateContainerHostConfig(const runtime::ContainerConfig &containerConfig, + Errors &error) +{ + host_config *hostconfig = (host_config *)util_common_calloc_s(sizeof(host_config)); + if (hostconfig == nullptr) { + error.SetError("Out of memory"); + return nullptr; + } + // iSulad: limit the number of threads in container + if (containerConfig.annotations().count("cgroup.pids.max") != 0) { + long long int converted = -1; + int ret = util_safe_llong(containerConfig.annotations().at("cgroup.pids.max").c_str(), &converted); + if (ret != 0) { + error.SetError("Cgroup.pids.max is not a valid numeric string"); + goto cleanup; + } + hostconfig->pids_limit = converted; + } + CRIHelpers::GenerateMountBindings(containerConfig.mounts(), hostconfig, error); + if (error.NotEmpty()) { + goto cleanup; + } + + if (PackCreateContainerHostConfigDevices(containerConfig, hostconfig, error) != 0) { + error.SetError("Failed to pack devices to host config"); + goto cleanup; + } + + if (PackCreateContainerHostConfigSecurityContext(containerConfig, hostconfig, error) != 0) { + error.SetError("Failed to security context to host config"); + goto cleanup; + } + + return hostconfig; +cleanup: + free_host_config(hostconfig); + return nullptr; +} + +container_create_request *CRIRuntimeServiceImpl::GenerateCreateContainerRequest( + const std::string &realPodSandboxID, + const runtime::ContainerConfig &containerConfig, + const runtime::PodSandboxConfig &podSandboxConfig, + Errors &error) +{ + struct parser_context ctx { + OPT_GEN_SIMPLIFY, 0 + }; + parser_error perror { nullptr }; + + container_create_request *request = (container_create_request *)util_common_calloc_s(sizeof(*request)); + if (request == nullptr) { + error.SetError("Out of memory"); + return nullptr; + } + + std::string cname = CRINaming::MakeContainerName(podSandboxConfig, containerConfig); + request->id = util_strdup_s(cname.c_str()); + + request->runtime = util_strdup_s(CRIHelpers::Constants::DEFAULT_RUNTIME_NAME.c_str()); + + if (!containerConfig.image().image().empty()) { + request->image = util_strdup_s(containerConfig.image().image().c_str()); + } + + container_custom_config *custom_config { nullptr }; + + host_config *hostconfig = GenerateCreateContainerHostConfig(containerConfig, error); + if (error.NotEmpty()) { + goto cleanup; + } + + if (podSandboxConfig.has_linux() && !podSandboxConfig.linux().cgroup_parent().empty()) { + hostconfig->cgroup_parent = util_strdup_s(podSandboxConfig.linux().cgroup_parent().c_str()); + } + + custom_config = CRIRuntimeServiceImpl::GenerateCreateContainerCustomConfig(realPodSandboxID, containerConfig, + podSandboxConfig, error); + if (error.NotEmpty()) { + goto cleanup; + } + + CRIHelpers::UpdateCreateConfig(custom_config, hostconfig, containerConfig, realPodSandboxID, error); + if (error.NotEmpty()) { + goto cleanup; + } + + request->hostconfig = host_config_generate_json(hostconfig, &ctx, &perror); + if (request->hostconfig == nullptr) { + error.Errorf("Failed to generate host config json: %s", perror); + free_container_create_request(request); + request = nullptr; + goto cleanup; + } + + request->customconfig = container_custom_config_generate_json(custom_config, &ctx, &perror); + if (request->customconfig == nullptr) { + error.Errorf("Failed to generate custom config json: %s", perror); + free_container_create_request(request); + request = nullptr; + goto cleanup; + } +cleanup: + free_host_config(hostconfig); + free_container_custom_config(custom_config); + free(perror); + return request; +} + +std::string CRIRuntimeServiceImpl::CreateContainer(const std::string &podSandboxID, + const runtime::ContainerConfig &containerConfig, + const runtime::PodSandboxConfig &podSandboxConfig, + Errors &error) +{ + std::string response_id { "" }; + + if (m_cb == nullptr || m_cb->container.create == nullptr) { + error.SetError("Unimplemented callback"); + return response_id; + } + container_create_request *request { nullptr }; + container_create_response *response { nullptr }; + + std::string realPodSandboxID = GetRealContainerOrSandboxID(podSandboxID, true, error); + if (error.NotEmpty()) { + ERROR("Failed to find sandbox id %s: %s", podSandboxID.c_str(), error.GetCMessage()); + error.Errorf("Failed to find sandbox id %s: %s", podSandboxID.c_str(), error.GetCMessage()); + goto cleanup; + } + + request = GenerateCreateContainerRequest(realPodSandboxID, containerConfig, podSandboxConfig, error); + if (error.NotEmpty()) { + error.SetError("Failed to generate create container request"); + goto cleanup; + } + + if (m_cb->container.create(request, &response)) { + if (response != nullptr && response->errmsg) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call create container callback"); + } + goto cleanup; + } + + response_id = response->id; +cleanup: + free_container_create_request(request); + free_container_create_response(response); + return response_id; +} + +void CRIRuntimeServiceImpl::MakeContainerConfig(const runtime::ContainerConfig &config, + container_custom_config *cConfig, Errors &error) +{ + if (config.command_size() > 0) { + if (static_cast(config.command_size()) > SIZE_MAX / sizeof(char *)) { + error.SetError("Invalid command size"); + return; + } + cConfig->entrypoint = (char **)util_common_calloc_s(config.command_size() * sizeof(char *)); + if (cConfig->entrypoint == nullptr) { + error.SetError("Out of memory"); + return; + } + for (int i = 0; i < config.command_size(); i++) { + cConfig->entrypoint[i] = util_strdup_s(config.command(i).c_str()); + cConfig->entrypoint_len++; + } + } + + if (config.args_size() > 0) { + if (static_cast(config.args_size()) > SIZE_MAX / sizeof(char *)) { + error.SetError("Invalid argument size"); + return; + } + cConfig->cmd = (char **)util_common_calloc_s(config.args_size() * sizeof(char *)); + if (cConfig->cmd == nullptr) { + error.SetError("Out of memory"); + return; + } + for (int i = 0; i < config.args_size(); i++) { + cConfig->cmd[i] = util_strdup_s(config.args(i).c_str()); + cConfig->cmd_len++; + } + } + + if (config.envs_size() > 0) { + if (static_cast(config.envs_size()) > SIZE_MAX / sizeof(char *)) { + error.SetError("Invalid env size"); + return; + } + cConfig->env = (char **)util_common_calloc_s(config.envs_size() * sizeof(char *)); + if (cConfig->env == nullptr) { + error.SetError("Out of memory"); + return; + } + auto envVect = CRIHelpers::GenerateEnvList(config.envs()); + for (size_t i = 0; i < envVect.size(); i++) { + cConfig->env[i] = util_strdup_s(envVect.at(i).c_str()); + cConfig->env_len++; + } + } + + if (!config.working_dir().empty()) { + cConfig->working_dir = util_strdup_s(config.working_dir().c_str()); + } +} + +void CRIRuntimeServiceImpl::GetContainerLogPath(const std::string &containerID, char **path, char **realPath, + Errors &error) +{ + container_inspect *info = InspectContainer(containerID, error); + if (info == nullptr || error.NotEmpty()) { + error.Errorf("failed to inspect container %s: %s", containerID.c_str(), error.GetCMessage()); + return; + } + + if (info->config != nullptr && info->config->labels) { + for (size_t i = 0; i < info->config->labels->len; i++) { + if (strcmp(info->config->labels->keys[i], + CRIHelpers::Constants::CONTAINER_LOGPATH_LABEL_KEY.c_str()) == 0 && + strcmp(info->config->labels->values[i], "") != 0) { + *path = util_strdup_s(info->config->labels->values[i]); + break; + } + } + } + + if (info->log_path != nullptr && strcmp(info->log_path, "") != 0) { + *realPath = util_strdup_s(info->log_path); + } + free_container_inspect(info); +} + +void CRIRuntimeServiceImpl::CreateContainerLogSymlink(const std::string &containerID, Errors &error) +{ + char *path { nullptr }; + char *realPath { nullptr }; + + GetContainerLogPath(containerID, &path, &realPath, error); + if (error.NotEmpty()) { + error.Errorf("failed to get container %s log path: %s", containerID.c_str(), error.GetCMessage()); + return; + } + if (path == nullptr) { + INFO("Container %s log path isn't specified, will not create the symlink", containerID.c_str()); + goto cleanup; + } + if (realPath != nullptr) { + if (util_path_remove(path) == 0) { + WARN("Deleted previously existing symlink file: %s", path); + } + if (symlink(realPath, path) != 0) { + error.Errorf("failed to create symbolic link %s to the container log file %s for container %s: %s", path, + realPath, containerID.c_str(), strerror(errno)); + goto cleanup; + } + } else { + WARN("Cannot create symbolic link because container log file doesn't exist!"); + } +cleanup: + free(path); + free(realPath); +} + +void CRIRuntimeServiceImpl::StartContainer(const std::string &containerID, Errors &error) +{ + if (containerID.empty()) { + error.SetError("Invalid empty container id."); + return; + } + std::string realContainerID = GetRealContainerOrSandboxID(containerID, false, error); + if (error.NotEmpty()) { + ERROR("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + error.Errorf("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + return; + } + + if (m_cb == nullptr || m_cb->container.start == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + container_start_response *response { nullptr }; + int ret {}; + container_start_request *request = + (container_start_request *)util_common_calloc_s(sizeof(container_start_request)); + if (request == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + request->id = util_strdup_s(realContainerID.c_str()); + + ret = m_cb->container.start(request, &response, -1, nullptr, nullptr); + + // Create container log symlink for all containers (including failed ones). + CreateContainerLogSymlink(realContainerID, error); + if (error.NotEmpty()) { + goto cleanup; + } + + if (ret != 0) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call start container callback"); + } + goto cleanup; + } +cleanup: + free_container_start_request(request); + free_container_start_response(response); +} + +void CRIRuntimeServiceImpl::StopContainer(const std::string &containerID, int64_t timeout, Errors &error) +{ + if (containerID.empty()) { + error.SetError("Invalid empty container id."); + return; + } + std::string realContainerID = GetRealContainerOrSandboxID(containerID, false, error); + if (error.NotEmpty()) { + ERROR("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + error.Errorf("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + return; + } + + if (m_cb == nullptr || m_cb->container.stop == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + container_stop_response *response { nullptr }; + container_stop_request *request = + (container_stop_request *)util_common_calloc_s(sizeof(container_stop_request)); + if (request == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + request->id = util_strdup_s(realContainerID.c_str()); + request->timeout = (int32_t)timeout; + + if (m_cb->container.stop(request, &response)) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call stop container callback"); + } + goto cleanup; + } +cleanup: + free_container_stop_request(request); + free_container_stop_response(response); +} + +// CreateContainerLogSymlink creates the symlink for container log. +void CRIRuntimeServiceImpl::RemoveContainerLogSymlink(const std::string &containerID, Errors &error) +{ + char *path { nullptr }; + char *realPath { nullptr }; + + GetContainerLogPath(containerID, &path, &realPath, error); + if (error.NotEmpty()) { + error.Errorf("Failed to get container %s log path: %s", containerID.c_str(), error.GetCMessage()); + return; + } + + if (path != nullptr) { + // Only remove the symlink when container log path is specified. + if (util_path_remove(path) != 0 && errno != ENOENT) { + error.Errorf("Failed to remove container %s log symlink %s: %s", containerID.c_str(), path, + strerror(errno)); + goto cleanup; + } + } +cleanup: + free(path); + free(realPath); +} + +void CRIRuntimeServiceImpl::RemoveContainer(const std::string &containerID, Errors &error) +{ + if (containerID.empty()) { + error.SetError("Invalid empty container id."); + return; + } + std::string realContainerID = GetRealContainerOrSandboxID(containerID, false, error); + if (error.NotEmpty()) { + ERROR("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + error.Errorf("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + return; + } + + if (m_cb == nullptr || m_cb->container.remove == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + container_delete_response *response { nullptr }; + container_delete_request *request = + (container_delete_request *)util_common_calloc_s(sizeof(container_delete_request)); + if (request == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + request->id = util_strdup_s(realContainerID.c_str()); + request->force = true; + + RemoveContainerLogSymlink(realContainerID, error); + if (error.NotEmpty()) { + goto cleanup; + } + + if (m_cb->container.remove(request, &response)) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call remove container callback"); + } + goto cleanup; + } +cleanup: + free_container_delete_request(request); + free_container_delete_response(response); +} + +void CRIRuntimeServiceImpl::ListContainersToGRPC(container_list_response *response, + std::vector> *pods, Errors &error) +{ + for (size_t i = 0; i < response->containers_len; i++) { + std::unique_ptr container(new runtime::Container); + + if (response->containers[i]->id != nullptr) { + container->set_id(response->containers[i]->id); + } + + container->set_created_at(response->containers[i]->created); + + CRINaming::ParseContainerName(response->containers[i]->name, container->mutable_metadata(), error); + if (error.NotEmpty()) { + return; + } + + CRIHelpers::ExtractLabels(response->containers[i]->labels, *container->mutable_labels()); + + CRIHelpers::ExtractAnnotations(response->containers[i]->annotations, *container->mutable_annotations()); + + for (size_t j = 0; j < response->containers[i]->labels->len; j++) { + if (strcmp(response->containers[i]->labels->keys[j], + CRIHelpers::Constants::SANDBOX_ID_LABEL_KEY.c_str()) == 0) { + container->set_pod_sandbox_id(response->containers[i]->labels->values[j]); + break; + } + } + + if (response->containers[i]->image != nullptr) { + runtime::ImageSpec *image = container->mutable_image(); + image->set_image(response->containers[i]->image); + imagetool_image *ir = CRIHelpers::InspectImageByID(response->containers[i]->image, error); + if (error.NotEmpty() || ir == nullptr) { + error.Errorf("unable to inspect image %s while inspecting container %s: %s", + response->containers[i]->image, response->containers[i]->id, + error.Empty() ? "Unknown" : error.GetMessage().c_str()); + free_imagetool_image(ir); + return; + } + std::string imageID = CRIHelpers::ToPullableImageID(response->containers[i]->image, ir); + container->set_image_ref(imageID); + free_imagetool_image(ir); + } + + runtime::ContainerState state = + CRIHelpers::ContainerStatusToRuntime(Container_Status(response->containers[i]->status)); + container->set_state(state); + + pods->push_back(move(container)); + } +} + +void CRIRuntimeServiceImpl::ListContainersFromGRPC(const runtime::ContainerFilter *filter, + container_list_request **request, Errors &error) +{ + *request = (container_list_request *)util_common_calloc_s(sizeof(container_list_request)); + if (*request == nullptr) { + error.SetError("Out of memory"); + return; + } + (*request)->all = true; + + (*request)->filters = (defs_filters *)util_common_calloc_s(sizeof(defs_filters)); + if ((*request)->filters == nullptr) { + error.SetError("Out of memory"); + return; + } + + if (filter != nullptr) { + if (!filter->id().empty()) { + if (CRIHelpers::FiltersAdd((*request)->filters, "id", filter->id()) != 0) { + error.SetError("Failed to add filter"); + return; + } + } + if (filter->has_state()) { + if (CRIHelpers::FiltersAdd((*request)->filters, "status", + CRIHelpers::ToIsuladContainerStatus(filter->state())) != 0) { + error.SetError("Failed to add filter"); + return; + } + } + if (!filter->pod_sandbox_id().empty()) { + if (CRIHelpers::FiltersAddLabel((*request)->filters, CRIHelpers::Constants::SANDBOX_ID_LABEL_KEY, + filter->pod_sandbox_id()) != 0) { + error.SetError("Failed to add filter"); + return; + } + } + + // Add some label + if (CRIHelpers::FiltersAddLabel((*request)->filters, CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY, + CRIHelpers::Constants::CONTAINER_TYPE_LABEL_CONTAINER) != 0) { + error.SetError("Failed to add filter"); + return; + } + for (auto &iter : filter->label_selector()) { + if (CRIHelpers::FiltersAddLabel((*request)->filters, iter.first, iter.second) != 0) { + error.SetError("Failed to add filter"); + return; + } + } + } +} + +void CRIRuntimeServiceImpl::ListContainers(const runtime::ContainerFilter *filter, + std::vector> *containers, Errors &error) +{ + if (m_cb == nullptr || m_cb->container.list == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + container_list_response *response { nullptr }; + container_list_request *request { nullptr }; + ListContainersFromGRPC(filter, &request, error); + if (error.NotEmpty()) { + goto cleanup; + } + + if (m_cb->container.list(request, &response)) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call list container callback"); + } + goto cleanup; + } + + ListContainersToGRPC(response, containers, error); + +cleanup: + free_container_list_request(request); + free_container_list_response(response); +} + +void CRIRuntimeServiceImpl::ContainerStatsToGRPC(container_stats_response *response, + std::vector> *containerstats, + Errors &error) +{ + int ret {}; + + for (size_t i {}; i < response->container_stats_len; i++) { + std::unique_ptr container(new runtime::ContainerStats); + imagetool_fs_info *fs_usage { nullptr }; + + if (response->container_stats[i]->id != nullptr && response->container_stats[i]->image_type != nullptr) { + container->mutable_attributes()->set_id(response->container_stats[i]->id); + ret = im_get_container_filesystem_usage(response->container_stats[i]->image_type, + response->container_stats[i]->id, &fs_usage); + if (ret != 0) { + ERROR("Failed to get container filesystem usage"); + } + } + + if (response->container_stats[i]->mem_used) { + container->mutable_memory()->mutable_working_set_bytes()->set_value(response->container_stats[i]->mem_used); + } + + if (response->container_stats[i]->cpu_use_nanos) { + container->mutable_cpu()->mutable_usage_core_nano_seconds()->set_value( + response->container_stats[i]->cpu_use_nanos); + container->mutable_cpu()->set_timestamp((int64_t)(response->container_stats[i]->cpu_system_use)); + } + + if (fs_usage == nullptr) { + container->mutable_writable_layer()->mutable_used_bytes()->set_value(0); + container->mutable_writable_layer()->mutable_inodes_used()->set_value(0); + } + if (fs_usage != nullptr) { + if (fs_usage->image_filesystems[0]->used_bytes->value) { + container->mutable_writable_layer()->mutable_used_bytes()->set_value( + fs_usage->image_filesystems[0]->used_bytes->value); + } + + if (fs_usage->image_filesystems[0]->inodes_used->value) { + container->mutable_writable_layer()->mutable_inodes_used()->set_value( + fs_usage->image_filesystems[0]->inodes_used->value); + } + } + containerstats->push_back(move(container)); + free_imagetool_fs_info(fs_usage); + } +} + +void CRIRuntimeServiceImpl::ListContainerStats(const runtime::ContainerStatsFilter *filter, + std::vector> *containerstats, + Errors &error) +{ + if (m_cb == nullptr || m_cb->container.stats == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + if (containerstats == nullptr) { + error.SetError("Invalid arguments"); + return; + } + + container_stats_response *response { nullptr }; + container_stats_request *request = (container_stats_request *)util_common_calloc_s(sizeof(container_stats_request)); + if (request == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + request->all = true; + request->runtime = util_strdup_s(CRIHelpers::Constants::DEFAULT_RUNTIME_NAME.c_str()); + + request->filters = (defs_filters *)util_common_calloc_s(sizeof(defs_filters)); + if (request->filters == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + + if (filter != nullptr) { + if (!filter->id().empty()) { + if (CRIHelpers::FiltersAdd(request->filters, "id", filter->id()) != 0) { + error.SetError("Failed to add filter"); + goto cleanup; + } + } + if (!filter->pod_sandbox_id().empty()) { + if (CRIHelpers::FiltersAddLabel(request->filters, CRIHelpers::Constants::SANDBOX_ID_LABEL_KEY, + filter->pod_sandbox_id()) != 0) { + error.SetError("Failed to add filter"); + goto cleanup; + } + } + + // Add some label + if (CRIHelpers::FiltersAddLabel(request->filters, CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY, + CRIHelpers::Constants::CONTAINER_TYPE_LABEL_CONTAINER) != 0) { + error.SetError("Failed to add filter"); + goto cleanup; + } + for (auto &iter : filter->label_selector()) { + if (CRIHelpers::FiltersAddLabel(request->filters, iter.first, iter.second) != 0) { + error.SetError("Failed to add filter"); + goto cleanup; + } + } + } + + if (m_cb->container.stats(request, &response)) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call stats container callback"); + } + goto cleanup; + } + ContainerStatsToGRPC(response, containerstats, error); + +cleanup: + free_container_stats_request(request); + free_container_stats_response(response); +} + +int CRIRuntimeServiceImpl::PackContainerImageToStatus(container_inspect *inspect, + std::unique_ptr &contStatus, + Errors &error) +{ + if (inspect->image == nullptr) { + return 0; + } + contStatus->mutable_image()->set_image(inspect->image); + imagetool_image *ir = CRIHelpers::InspectImageByID(inspect->image, error); + if (error.NotEmpty() || ir == nullptr) { + error.Errorf("unable to inspect image %s while inspecting container %s: %s", inspect->image, inspect->name, + error.Empty() ? "Unknown" : error.GetMessage().c_str()); + free_imagetool_image(ir); + return -1; + } + contStatus->set_image_ref(CRIHelpers::ToPullableImageID(inspect->image, ir)); + free_imagetool_image(ir); + return 0; +} + +void CRIRuntimeServiceImpl::UpdateBaseStatusFromInspect(container_inspect *inspect, int64_t &createdAt, + int64_t &startedAt, int64_t &finishedAt, + std::unique_ptr &contStatus) +{ + runtime::ContainerState state { runtime::CONTAINER_UNKNOWN }; + std::string reason, message; + int32_t exitCode { 0 }; + + if (inspect->state == nullptr) { + goto pack_status; + } + + if (inspect->state->running) { + // Container is running + state = runtime::CONTAINER_RUNNING; + } else { + // Container is not running. + if (finishedAt) { // Case 1 + state = runtime::CONTAINER_EXITED; + if (inspect->state->exit_code == 0) { + reason = "Completed"; + } else { + reason = "Error"; + } + } else if (inspect->state->exit_code) { // Case 2 + state = runtime::CONTAINER_EXITED; + finishedAt = createdAt; + startedAt = createdAt; + reason = "ContainerCannotRun"; + } else { // Case 3 + state = runtime::CONTAINER_CREATED; + } + if (inspect->state->error != nullptr) { + message = inspect->state->error; + } + exitCode = (int32_t)inspect->state->exit_code; + } + +pack_status: + contStatus->set_exit_code(exitCode); + contStatus->set_state(state); + contStatus->set_created_at(createdAt); + contStatus->set_started_at(startedAt); + contStatus->set_finished_at(finishedAt); + contStatus->set_reason(reason); + contStatus->set_message(message); +} + +void CRIRuntimeServiceImpl::PackLabelsToStatus(container_inspect *inspect, + std::unique_ptr &contStatus) +{ + if (inspect->config == nullptr || inspect->config->labels == nullptr) { + return; + } + CRIHelpers::ExtractLabels(inspect->config->labels, *contStatus->mutable_labels()); + CRIHelpers::ExtractAnnotations(inspect->config->annotations, *contStatus->mutable_annotations()); + for (size_t i = 0; i < inspect->config->labels->len; i++) { + if (strcmp(inspect->config->labels->keys[i], CRIHelpers::Constants::CONTAINER_LOGPATH_LABEL_KEY.c_str()) == 0) { + contStatus->set_log_path(inspect->config->labels->values[i]); + break; + } + } +} + +void CRIRuntimeServiceImpl::ConvertMountsToStatus(container_inspect *inspect, + std::unique_ptr &contStatus) +{ + for (size_t i = 0; i < inspect->mounts_len; i++) { + runtime::Mount *mount = contStatus->add_mounts(); + mount->set_host_path(inspect->mounts[i]->source); + mount->set_container_path(inspect->mounts[i]->destination); + mount->set_readonly(!inspect->mounts[i]->rw); + if (inspect->mounts[i]->propagation == nullptr || strcmp(inspect->mounts[i]->propagation, "rprivate") == 0) { + mount->set_propagation(runtime::PROPAGATION_PRIVATE); + } else if (strcmp(inspect->mounts[i]->propagation, "rslave") == 0) { + mount->set_propagation(runtime::PROPAGATION_HOST_TO_CONTAINER); + } else if (strcmp(inspect->mounts[i]->propagation, "rshared") == 0) { + mount->set_propagation(runtime::PROPAGATION_BIDIRECTIONAL); + } + // Note: Can't set SeLinuxRelabel + } +} +void CRIRuntimeServiceImpl::ContainerStatusToGRPC(container_inspect *inspect, + std::unique_ptr &contStatus, Errors &error) +{ + if (inspect->id != nullptr) { + contStatus->set_id(inspect->id); + } + + int64_t createdAt {}; + int64_t startedAt {}; + int64_t finishedAt {}; + GetContainerTimeStamps(inspect, &createdAt, &startedAt, &finishedAt, error); + if (error.NotEmpty()) { + return; + } + contStatus->set_created_at(createdAt); + contStatus->set_started_at(startedAt); + contStatus->set_finished_at(finishedAt); + + if (inspect->name != nullptr) { + CRINaming::ParseContainerName(inspect->name, contStatus->mutable_metadata(), error); + if (error.NotEmpty()) { + return; + } + } + if (PackContainerImageToStatus(inspect, contStatus, error) != 0) { + return; + } + UpdateBaseStatusFromInspect(inspect, createdAt, startedAt, finishedAt, contStatus); + PackLabelsToStatus(inspect, contStatus); + ConvertMountsToStatus(inspect, contStatus); +} + +std::unique_ptr CRIRuntimeServiceImpl::ContainerStatus(const std::string &containerID, + Errors &error) +{ + if (containerID.empty()) { + error.SetError("Empty pod sandbox id"); + return nullptr; + } + + std::string realContainerID = GetRealContainerOrSandboxID(containerID, false, error); + if (error.NotEmpty()) { + ERROR("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + error.Errorf("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + return nullptr; + } + + container_inspect *inspect = InspectContainer(realContainerID, error); + if (error.NotEmpty()) { + return nullptr; + } + if (inspect == nullptr) { + error.SetError("Get null inspect"); + return nullptr; + } + + std::unique_ptr contStatus(new runtime::ContainerStatus); + ContainerStatusToGRPC(inspect, contStatus, error); + + free_container_inspect(inspect); + return contStatus; +} + +void CRIRuntimeServiceImpl::UpdateContainerResources(const std::string &containerID, + const runtime::LinuxContainerResources &resources, Errors &error) +{ + if (containerID.empty()) { + error.SetError("Invalid empty container id."); + return; + } + std::string realContainerID = GetRealContainerOrSandboxID(containerID, false, error); + if (error.NotEmpty()) { + ERROR("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + error.Errorf("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + return; + } + + if (m_cb == nullptr || m_cb->container.update == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + container_update_request *request { nullptr }; + container_update_response *response { nullptr }; + host_config *hostconfig { nullptr }; + parser_error perror { nullptr }; + struct parser_context ctx { + OPT_GEN_SIMPLIFY, 0 + }; + request = (container_update_request *)util_common_calloc_s(sizeof(container_update_request)); + if (request == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + request->name = util_strdup_s(realContainerID.c_str()); + + hostconfig = (host_config *)util_common_calloc_s(sizeof(host_config)); + if (hostconfig == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + + hostconfig->cpu_period = resources.cpu_period(); + hostconfig->cpu_quota = resources.cpu_quota(); + hostconfig->cpu_shares = resources.cpu_shares(); + hostconfig->memory = resources.memory_limit_in_bytes(); + if (!resources.cpuset_cpus().empty()) { + hostconfig->cpuset_cpus = util_strdup_s(resources.cpuset_cpus().c_str()); + } + if (!resources.cpuset_mems().empty()) { + hostconfig->cpuset_mems = util_strdup_s(resources.cpuset_mems().c_str()); + } + + request->host_config = host_config_generate_json(hostconfig, &ctx, &perror); + if (request->host_config == nullptr) { + error.Errorf("Failed to generate host config json: %s", perror); + goto cleanup; + } + INFO("hostconfig: %s", request->host_config); + + if (m_cb->container.update(request, &response)) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call update container callback"); + } + } +cleanup: + free_container_update_request(request); + free_container_update_response(response); + free_host_config(hostconfig); + free(perror); +} + +static ssize_t WriteToString(void *context, const void *data, size_t len) +{ + if (len == 0) { + return 0; + } + + std::string *str = reinterpret_cast(context); + + str->append(reinterpret_cast(data), len); + return (ssize_t)len; +} + +void CRIRuntimeServiceImpl::ExecSyncFromGRPC(const std::string &containerID, + const google::protobuf::RepeatedPtrField &cmd, + int64_t timeout, container_exec_request **request, Errors &error) +{ + if (timeout < 0) { + error.SetError("Exec timeout cannot be negative."); + return; + } + + *request = (container_exec_request *)util_common_calloc_s(sizeof(container_exec_request)); + if (*request == nullptr) { + error.SetError("Out of memory"); + return; + } + (*request)->tty = true; + (*request)->attach_stdin = true; + (*request)->attach_stdout = true; + (*request)->attach_stderr = true; + (*request)->timeout = timeout; + (*request)->container_id = util_strdup_s(containerID.c_str()); + if (cmd.size() > 0) { + if ((size_t)cmd.size() > INT_MAX / sizeof(char *)) { + error.SetError("Too many cmd args"); + return; + } + (*request)->argv = (char **)util_common_calloc_s(cmd.size() * sizeof(char *)); + if ((*request)->argv == nullptr) { + error.SetError("Out of memory"); + return; + } + for (int i = 0; i < cmd.size(); i++) { + (*request)->argv[i] = util_strdup_s(cmd[i].c_str()); + (*request)->argv_len++; + } + } +} + +void CRIRuntimeServiceImpl::ExecSync(const std::string &containerID, + const google::protobuf::RepeatedPtrField &cmd, int64_t timeout, + runtime::ExecSyncResponse *reply, Errors &error) +{ + struct io_write_wrapper stringWriter = { 0 }; + + if (m_cb == nullptr || m_cb->container.exec == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + if (containerID.empty()) { + error.SetError("Invalid empty container id."); + return; + } + + std::string realContainerID = GetRealContainerOrSandboxID(containerID, false, error); + if (error.NotEmpty()) { + ERROR("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + error.Errorf("Failed to find container id %s: %s", containerID.c_str(), error.GetCMessage()); + return; + } + + container_exec_response *response { nullptr }; + container_exec_request *request { nullptr }; + ExecSyncFromGRPC(realContainerID, cmd, timeout, &request, error); + if (error.NotEmpty()) { + goto cleanup; + } + + stringWriter.context = (void *)reply->mutable_stdout(); + stringWriter.write_func = WriteToString; + if (m_cb->container.exec(request, &response, -1, &stringWriter)) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call exec container callback"); + } + goto cleanup; + } + reply->set_exit_code((::google::protobuf::uint32)(response->exit_code)); + +cleanup: + free_container_exec_request(request); + free_container_exec_response(response); +} + +int CRIRuntimeServiceImpl::ValidateExecRequest(const runtime::ExecRequest &req, Errors &error) +{ + if (req.container_id().empty()) { + error.SetError("missing required container id!"); + return -1; + } + std::string realContainerID = GetRealContainerOrSandboxID(req.container_id(), false, error); + if (error.NotEmpty()) { + ERROR("Failed to find container id %s: %s", req.container_id().c_str(), error.GetCMessage()); + error.Errorf("Failed to find container id %s: %s", req.container_id().c_str(), error.GetCMessage()); + return -1; + } + + container_inspect *status = CRIRuntimeServiceImpl::InspectContainer(realContainerID, error); + if (error.NotEmpty()) { + ERROR("Failed to inspect container id %s: %s", req.container_id().c_str(), error.GetCMessage()); + error.Errorf("Failed to inspect container id %s: %s", req.container_id().c_str(), error.GetCMessage()); + return -1; + } + bool running = status->state != nullptr && status->state->running; + free_container_inspect(status); + if (!running) { + ERROR("Container is not running: %s", req.container_id().c_str()); + error.Errorf("Container is not running: %s", req.container_id().c_str()); + return -1; + } + + if (req.tty() && req.stderr()) { + error.SetError("tty and stderr cannot both be true!"); + return -1; + } + if (!req.stdin() && !req.stdout() && !req.stderr()) { + error.SetError("one of stdin, stdout, or stderr must be set!"); + return -1; + } + return 0; +} + +std::string CRIRuntimeServiceImpl::BuildURL(const std::string &method, const std::string &token) +{ + url::URLDatum url; + url.SetPathWithoutEscape("/cri/" + method + "/" + token); + WebsocketServer *server = WebsocketServer::GetInstance(); + url::URLDatum wsurl = server->GetWebsocketUrl(); + return wsurl.ResolveReference(&url)->String(); +} + +void CRIRuntimeServiceImpl::Exec(const runtime::ExecRequest &req, runtime::ExecResponse *resp, Errors &error) +{ + if (ValidateExecRequest(req, error)) { + return; + } + RequestCache *cache = RequestCache::GetInstance(); + runtime::ExecRequest *execReq = new runtime::ExecRequest(req); + std::string token = cache->Insert(const_cast(execReq)); + if (token.empty()) { + error.SetError("failed to get a unique token!"); + return; + } + std::string url = BuildURL("exec", token); + resp->set_url(url); +} + +int CRIRuntimeServiceImpl::ValidateAttachRequest(const runtime::AttachRequest &req, Errors &error) +{ + if (req.container_id().empty()) { + error.SetError("missing required container id!"); + return -1; + } + (void)GetRealContainerOrSandboxID(req.container_id(), false, error); + if (error.NotEmpty()) { + ERROR("Failed to find container id %s: %s", req.container_id().c_str(), error.GetCMessage()); + error.Errorf("Failed to find container id %s: %s", req.container_id().c_str(), error.GetCMessage()); + return -1; + } + + if (req.tty() && req.stderr()) { + error.SetError("tty and stderr cannot both be true!"); + return -1; + } + if (!req.stdin() && !req.stdout() && !req.stderr()) { + error.SetError("one of stdin, stdout, and stderr must be set"); + return -1; + } + return 0; +} + +void CRIRuntimeServiceImpl::Attach(const runtime::AttachRequest &req, runtime::AttachResponse *resp, Errors &error) +{ + if (ValidateAttachRequest(req, error)) { + return; + } + if (resp == nullptr) { + error.SetError("Empty attach response arguments"); + return; + } + RequestCache *cache = RequestCache::GetInstance(); + runtime::AttachRequest *attachReq = new runtime::AttachRequest(req); + std::string token = cache->Insert(const_cast(attachReq)); + if (token.empty()) { + error.SetError("failed to get a unique token!"); + return; + } + std::string url = BuildURL("attach", token); + resp->set_url(url); +} + +container_inspect *CRIRuntimeServiceImpl::InspectContainer(const std::string &containerID, Errors &err) +{ + container_inspect *inspect_data { nullptr }; + container_inspect_response *resp { nullptr }; + parser_error perr { nullptr }; + + if (m_cb == nullptr || m_cb->container.inspect == nullptr) { + err.SetError("Umimplements inspect"); + return inspect_data; + } + + container_inspect_request *req = + (container_inspect_request *)util_common_calloc_s(sizeof(container_inspect_request)); + if (req == nullptr) { + err.SetError("Out of memory"); + goto cleanup; + } + req->id = util_strdup_s(containerID.c_str()); + if (m_cb->container.inspect(req, &resp)) { + if (resp != nullptr && resp->errmsg != nullptr) { + err.SetError(resp->errmsg); + } else { + err.Errorf("Failed to call inspect callback"); + } + goto cleanup; + } + /* parse oci container json */ + if (resp != nullptr && resp->container_json) { + inspect_data = container_inspect_parse_data(resp->container_json, nullptr, &perr); + if (inspect_data == nullptr) { + err.Errorf("Parse container json failed: %s", perr); + goto cleanup; + } + } +cleanup: + free_container_inspect_request(req); + free_container_inspect_response(resp); + free(perr); + return inspect_data; +} + diff --git a/src/services/cri/cri_container.h b/src/services/cri/cri_container.h new file mode 100644 index 0000000..0e71ee2 --- /dev/null +++ b/src/services/cri/cri_container.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri container manager function definition + ********************************************************************************/ +#ifndef _CRI_CONTAINER_MANAGER_IMPL_H_ +#define _CRI_CONTAINER_MANAGER_IMPL_H_ + +#include "cri_services.h" +#include "callback.h" + +class CRIContainerManagerImpl : public cri::ContainerManager { +public: + CRIContainerManagerImpl() = default; + CRIContainerManagerImpl(const CRIContainerManagerImpl &) = delete; + CRIContainerManagerImpl &operator=(const CRIContainerManagerImpl &) = delete; + + virtual ~CRIContainerManagerImpl() = default; +}; + +#endif /* _CRI_CONTAINER_MANAGER_IMPL_H_ */ diff --git a/src/services/cri/cri_helpers.cc b/src/services/cri/cri_helpers.cc new file mode 100644 index 0000000..e8e2cc3 --- /dev/null +++ b/src/services/cri/cri_helpers.cc @@ -0,0 +1,712 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cni network plugin + *********************************************************************************/ + +#include "cri_helpers.h" +#include +#include +#include +#include +#include +#include + +#include "cri_runtime_service.h" +#include "api.pb.h" +#include "cri_security_context.h" +#include "utils.h" +#include "log.h" +#include "path.h" +#include "parse_common.h" +#include "cxxutils.h" + +namespace CRIHelpers { +const std::string Constants::DEFAULT_RUNTIME_NAME { "lcr" }; +const std::string Constants::POD_NETWORK_ANNOTATION_KEY { "network.alpha.kubernetes.io/network" }; +const std::string Constants::CONTAINER_TYPE_LABEL_KEY { "cri.isulad.type" }; +const std::string Constants::CONTAINER_TYPE_LABEL_SANDBOX { "podsandbox" }; +const std::string Constants::CONTAINER_TYPE_LABEL_CONTAINER { "container" }; +const std::string Constants::CONTAINER_LOGPATH_LABEL_KEY { "cri.container.logpath" }; +const std::string Constants::CONTAINER_HUGETLB_ANNOTATION_KEY { "cri.container.hugetlblimit" }; +const std::string Constants::SANDBOX_ID_LABEL_KEY { "cri.sandbox.id" }; +const std::string Constants::KUBERNETES_CONTAINER_NAME_LABEL { "io.kubernetes.container.name" }; +const std::string Constants::DOCKER_IMAGEID_PREFIX { "docker://" }; +const std::string Constants::DOCKER_PULLABLE_IMAGEID_PREFIX { "docker-pullable://" }; +const std::string Constants::RUNTIME_READY { "RuntimeReady" }; +const std::string Constants::NETWORK_READY { "NetworkReady" }; +const std::string Constants::POD_CHECKPOINT_KEY { "cri.sandbox.isulad.checkpoint" }; + +const char *InternalLabelKeys[] = { + CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY.c_str(), + CRIHelpers::Constants::CONTAINER_LOGPATH_LABEL_KEY.c_str(), + CRIHelpers::Constants::SANDBOX_ID_LABEL_KEY.c_str(), + nullptr +}; + +std::string GetDefaultSandboxImage(Errors &err) +{ + const std::string defaultPodSandboxImageName { "rnd-dockerhub.huawei.com/library/pause" }; + const std::string defaultPodSandboxImageVersion { "3.0" }; + std::string machine; + struct utsname uts; + + if (uname(&uts) < 0) { + err.SetError("Failed to read host arch."); + return ""; + } + + if (strcasecmp("i386", uts.machine) == 0) { + machine = "386"; + } else if ((strcasecmp("x86_64", uts.machine) == 0) || (strcasecmp("x86-64", uts.machine) == 0)) { + machine = "amd64"; + } else if (strcasecmp("aarch64", uts.machine) == 0) { + machine = "aarch64"; + } else if ((strcasecmp("armhf", uts.machine) == 0) || (strcasecmp("armel", uts.machine) == 0) || + (strcasecmp("arm", uts.machine) == 0)) { + machine = "aarch"; + } else { + machine = uts.machine; + } + return defaultPodSandboxImageName + "-" + machine + ":" + defaultPodSandboxImageVersion; +} + +json_map_string_string *MakeLabels(const google::protobuf::Map &mapLabels, Errors &error) +{ + json_map_string_string *labels = (json_map_string_string *)util_common_calloc_s(sizeof(json_map_string_string)); + if (labels == nullptr) { + ERROR("Out of memory"); + return nullptr; + } + + if (mapLabels.size() > 0) { + if (mapLabels.size() > LIST_SIZE_MAX) { + error.Errorf("Labels list is too long, the limit is %d", LIST_SIZE_MAX); + goto cleanup; + } + for (auto &iter : mapLabels) { + if (append_json_map_string_string(labels, iter.first.c_str(), iter.second.c_str()) != 0) { + ERROR("Failed to append string"); + goto cleanup; + } + } + } + return labels; +cleanup: + free_json_map_string_string(labels); + return nullptr; +} + +json_map_string_string *MakeAnnotations(const google::protobuf::Map &mapAnnotations, + Errors &error) +{ + json_map_string_string *annotations = + (json_map_string_string *)util_common_calloc_s(sizeof(json_map_string_string)); + if (annotations == nullptr) { + ERROR("Out of memory"); + return nullptr; + } + + if (mapAnnotations.size() > 0) { + if (mapAnnotations.size() > LIST_SIZE_MAX) { + error.Errorf("Annotations list is too long, the limit is %d", LIST_SIZE_MAX); + goto cleanup; + } + for (auto &iter : mapAnnotations) { + if (append_json_map_string_string(annotations, iter.first.c_str(), iter.second.c_str()) != 0) { + ERROR("Failed to append string"); + goto cleanup; + } + } + } + return annotations; +cleanup: + free_json_map_string_string(annotations); + return nullptr; +} + +void ProtobufAnnoMapToStd(const google::protobuf::Map &annotations, + std::map &newAnnos) +{ + for (auto &iter : annotations) { + newAnnos.insert(std::pair(iter.first, iter.second)); + } +} + +static bool IsSandboxLabel(json_map_string_string *input) +{ + bool is_sandbox_label { false }; + + for (size_t j = 0; j < input->len; j++) { + if (strcmp(input->keys[j], CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY.c_str()) == 0 && + strcmp(input->values[j], CRIHelpers::Constants::CONTAINER_TYPE_LABEL_SANDBOX.c_str()) == 0) { + is_sandbox_label = true; + break; + } + } + + return is_sandbox_label; +} + +void ExtractLabels(json_map_string_string *input, google::protobuf::Map &labels) +{ + if (input == nullptr) { + return; + } + + for (size_t i = 0; i < input->len; i++) { + bool internal = false; + const char **internal_key = InternalLabelKeys; + // Check if the key is used internally by the shim. + while (*internal_key != nullptr) { + if (strcmp(input->keys[i], *internal_key) == 0) { + internal = true; + break; + } + internal_key++; + } + if (internal) { + continue; + } + + // Delete the container name label for the sandbox. It is added + // in the shim, should not be exposed via CRI. + if (strcmp(input->keys[i], Constants::KUBERNETES_CONTAINER_NAME_LABEL.c_str()) == 0) { + bool is_sandbox_label = IsSandboxLabel(input); + if (is_sandbox_label) { + continue; + } + } + + labels[input->keys[i]] = input->values[i]; + } +} + +void ExtractAnnotations(json_map_string_string *input, google::protobuf::Map &annotations) +{ + if (input == nullptr) { + return; + } + + for (size_t i = 0; i < input->len; i++) { + annotations[input->keys[i]] = input->values[i]; + } +} + +int FiltersAdd(defs_filters *filters, const std::string &key, const std::string &value) +{ + if (filters == nullptr) { + return -1; + } + + size_t len = filters->len + 1; + if (len > SIZE_MAX / sizeof(char *)) { + ERROR("Invalid filter size"); + return -1; + } + char **keys = (char **)util_common_calloc_s(len * sizeof(char *)); + if (keys == nullptr) { + ERROR("Out of memory"); + return -1; + } + json_map_string_bool **vals = (json_map_string_bool **)util_common_calloc_s(len * sizeof(json_map_string_bool *)); + if (vals == nullptr) { + free(keys); + ERROR("Out of memory"); + return -1; + } + + if (filters->len) { + if (memcpy_s(keys, len * sizeof(char *), filters->keys, filters->len * sizeof(char *)) != EOK) { + free(keys); + free(vals); + return -1; + } + if (memcpy_s(vals, len * sizeof(json_map_string_bool *), filters->values, + filters->len * sizeof(json_map_string_bool *)) != EOK) { + free(keys); + free(vals); + return -1; + } + } + free(filters->keys); + filters->keys = keys; + free(filters->values); + filters->values = vals; + + filters->values[filters->len] = (json_map_string_bool *)util_common_calloc_s(sizeof(json_map_string_bool)); + if (filters->values[filters->len] == nullptr) { + ERROR("Out of memory"); + return -1; + } + if (append_json_map_string_bool(filters->values[filters->len], value.c_str(), true)) { + ERROR("Append failed"); + return -1; + } + + filters->keys[filters->len] = util_strdup_s(key.c_str()); + filters->len++; + return 0; +} + +int FiltersAddLabel(defs_filters *filters, const std::string &key, const std::string &value) +{ + if (filters == nullptr) { + return -1; + } + return FiltersAdd(filters, "label", key + "=" + value); +} + +runtime::ContainerState ContainerStatusToRuntime(Container_Status status) +{ + switch (status) { + case CONTAINER_STATUS_CREATED: + case CONTAINER_STATUS_STARTING: + return runtime::CONTAINER_CREATED; + case CONTAINER_STATUS_PAUSED: + case CONTAINER_STATUS_RESTARTING: + case CONTAINER_STATUS_RUNNING: + return runtime::CONTAINER_RUNNING; + case CONTAINER_STATUS_STOPPED: + return runtime::CONTAINER_EXITED; + default: + return runtime::CONTAINER_UNKNOWN; + } +} + +char **StringVectorToCharArray(std::vector &path) +{ + size_t len = path.size(); + if (len == 0 || len > (SIZE_MAX / sizeof(char *)) - 1) { + return nullptr; + } + char **result = (char **)util_common_calloc_s((len + 1) * sizeof(char *)); + if (result == nullptr) { + return nullptr; + } + size_t i {}; + for (auto it = path.cbegin(); it != path.cend(); it++) { + result[i++] = util_strdup_s(it->c_str()); + } + + return result; +} + +imagetool_image *InspectImageByID(const std::string &imageID, Errors &err) +{ + oci_image_status_request *request { nullptr }; + oci_image_status_response *response { nullptr }; + imagetool_image *image { nullptr }; + + if (imageID.empty()) { + err.SetError("Empty image ID"); + return nullptr; + } + + request = (oci_image_status_request *)util_common_calloc_s(sizeof(oci_image_status_request)); + if (request == nullptr) { + ERROR("Out of memory"); + err.SetError("Out of memory"); + return nullptr; + } + request->image.image = util_strdup_s(imageID.c_str()); + + if (oci_status_image(request, &response)) { + if (response != nullptr && response->errmsg != nullptr) { + err.SetError(response->errmsg); + } else { + err.SetError("Failed to call status image"); + } + goto cleanup; + } + + if (response->image_info != nullptr) { + image = response->image_info->image; + response->image_info->image = nullptr; + } + +cleanup: + free_oci_image_status_request(request); + free_oci_image_status_response(response); + return image; +} + +std::string ToPullableImageID(const std::string &id, imagetool_image *image) +{ + // Default to the image ID, but if RepoDigests is not empty, use + // the first digest instead. + std::string imageID = Constants::DOCKER_IMAGEID_PREFIX + id; + if (image != nullptr && image->repo_digests != nullptr && image->repo_digests_len > 0) { + imageID = Constants::DOCKER_PULLABLE_IMAGEID_PREFIX + std::string(image->repo_digests[0]); + } + return imageID; +} + +// IsContainerNotFoundError checks whether the error is container not found error. +bool IsContainerNotFoundError(const std::string &err) +{ + return err.find("No such container:") != std::string::npos || + err.find("No such image or container") != std::string::npos; +} + +// IsImageNotFoundError checks whether the error is Image not found error. +bool IsImageNotFoundError(const std::string &err) +{ + return err.find("No such image:") != std::string::npos; +} + +std::string sha256(const char *val) +{ + if (val == nullptr) { + return ""; + } + + SHA256_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, val, strlen(val)); + unsigned char hash[SHA256_DIGEST_LENGTH] = { 0 }; + SHA256_Final(hash, &ctx); + + char outputBuffer[(SHA256_DIGEST_LENGTH * 2) + 1] { 0 }; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + if (sprintf_s(outputBuffer + (i * 2), 3, "%02x", (unsigned int)hash[i]) < 0) { + return ""; + } + } + outputBuffer[SHA256_DIGEST_LENGTH * 2] = 0; + + return outputBuffer; +} + +cri_pod_network_element **GetNetworkPlaneFromPodAnno(const google::protobuf::Map &annotations, + size_t *len, Errors &error) +{ + auto iter = annotations.find(CRIHelpers::Constants::POD_NETWORK_ANNOTATION_KEY); + + cri_pod_network_element **result { nullptr }; + if (iter != annotations.end()) { + parser_error err = nullptr; + result = cri_pod_network_parse_data(iter->second.c_str(), nullptr, &err, len); + if (result == nullptr) { + error.Errorf("parse pod network json failed: %s", err); + } + free(err); + } + + return result; +} + +std::unique_ptr CheckpointToSandbox(const std::string &id, + const cri::PodSandboxCheckpoint &checkpoint) +{ + std::unique_ptr result(new runtime::PodSandbox); + runtime::PodSandboxMetadata *metadata = new (std::nothrow) runtime::PodSandboxMetadata; + if (metadata == nullptr) { + return nullptr; + } + + metadata->set_name(checkpoint.GetName()); + metadata->set_namespace_(checkpoint.GetNamespace()); + result->set_allocated_metadata(metadata); + result->set_id(id); + result->set_state(runtime::SANDBOX_NOTREADY); + + return result; +} + +void UpdateCreateConfig(container_custom_config *createConfig, host_config *hc, const runtime::ContainerConfig &config, + const std::string &podSandboxID, Errors &error) +{ + if (createConfig == nullptr || hc == nullptr) { + return; + } + DEBUG("Apply security context"); + CRISecurity::ApplyContainerSecurityContext(config.linux(), podSandboxID, createConfig, hc, error); + if (error.NotEmpty()) { + error.SetError("failed to apply container security context for container " + config.metadata().name() + ": " + + error.GetCMessage()); + return; + } + if (config.linux().has_resources()) { + runtime::LinuxContainerResources rOpts = config.linux().resources(); + hc->memory = rOpts.memory_limit_in_bytes(); + hc->memory_swap = CRIRuntimeService::Constants::DefaultMemorySwap; + hc->cpu_shares = rOpts.cpu_shares(); + hc->cpu_quota = rOpts.cpu_quota(); + hc->cpu_period = rOpts.cpu_period(); + if (!rOpts.cpuset_cpus().empty()) { + hc->cpuset_cpus = util_strdup_s(rOpts.cpuset_cpus().c_str()); + } + if (!rOpts.cpuset_mems().empty()) { + hc->cpuset_mems = util_strdup_s(rOpts.cpuset_mems().c_str()); + } + hc->oom_score_adj = rOpts.oom_score_adj(); + } + + createConfig->open_stdin = config.stdin(); + createConfig->tty = config.tty(); +} + +void GenerateMountBindings(const google::protobuf::RepeatedPtrField &mounts, host_config *hostconfig, + Errors &err) +{ + if (mounts.size() <= 0 || hostconfig == nullptr) { + return; + } + if ((size_t)mounts.size() > INT_MAX / sizeof(char *)) { + err.SetError("Too many mounts"); + return; + } + + hostconfig->binds = (char **)util_common_calloc_s(mounts.size() * sizeof(char *)); + if (hostconfig->binds == nullptr) { + err.SetError("Out of memory"); + return; + } + for (int i = 0; i < mounts.size(); i++) { + std::string bind = mounts[i].host_path() + ":" + mounts[i].container_path(); + std::vector attrs; + if (mounts[i].readonly()) { + attrs.push_back("ro"); + } + // Only request relabeling if the pod provides an SELinux context. If the pod + // does not provide an SELinux context relabeling will label the volume with + // the container's randomly allocated MCS label. This would restrict access + // to the volume to the container which mounts it first. + if (mounts[i].selinux_relabel()) { + attrs.push_back("Z"); + } + if (mounts[i].propagation() == runtime::PROPAGATION_PRIVATE) { + ; // noop, private is default + } else if (mounts[i].propagation() == runtime::PROPAGATION_BIDIRECTIONAL) { + attrs.push_back("rshared"); + } else if (mounts[i].propagation() == runtime::PROPAGATION_HOST_TO_CONTAINER) { + attrs.push_back("rslave"); + } else { + WARN("unknown propagation mode for hostPath %s", mounts[i].host_path().c_str()); + // Falls back to "private" + } + + if (attrs.size() > 0) { + bind += ":" + CXXUtils::StringsJoin(attrs, ","); + } + hostconfig->binds[i] = util_strdup_s(bind.c_str()); + hostconfig->binds_len++; + } +} + +std::vector GenerateEnvList(const ::google::protobuf::RepeatedPtrField<::runtime::KeyValue> &envs) +{ + std::vector vect; + std::for_each(envs.begin(), envs.end(), [&vect](const ::runtime::KeyValue & elem) { + vect.push_back(elem.key() + "=" + elem.value()); + }); + return vect; +} + +bool ValidateCheckpointKey(const std::string &key, Errors &error) +{ + const std::string PATTERN { "^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$" }; + + if (key.empty()) { + goto err_out; + } + + if (key.size() <= CRIHelpers::Constants::MAX_CHECKPOINT_KEY_LEN && + util_reg_match(PATTERN.c_str(), key.c_str()) == 0) { + return true; + } + +err_out: + error.Errorf("invalid key: %s", key.c_str()); + return false; +} + +std::string ToIsuladContainerStatus(const runtime::ContainerStateValue &state) +{ + if (state.state() == runtime::CONTAINER_CREATED) { + return "created"; + } else if (state.state() == runtime::CONTAINER_RUNNING) { + return "running"; + } else if (state.state() == runtime::CONTAINER_EXITED) { + return "exited"; + } else { + return "unknown"; + } +} + +struct iSuladOpt { + std::string key; + std::string value; + std::string msg; +}; + +std::vector fmtiSuladOpts(const std::vector &opts, const char &sep) +{ + std::vector fmtOpts(opts.size()); + for (size_t i {}; i < opts.size(); i++) { + fmtOpts[i] = opts.at(i).key + sep + opts.at(i).value; + } + return fmtOpts; +} + +std::vector GetSeccompiSuladOpts(const std::string &seccompProfile, Errors &error) +{ + if (seccompProfile.empty() || seccompProfile == "unconfined") { + return std::vector { { "seccomp", "unconfined", "" } }; + } + if (seccompProfile == "iSulad/default" || seccompProfile == "docker/default") { + // return nil so docker will load the default seccomp profile + return std::vector {}; + } + if (seccompProfile.compare(0, strlen("localhost/"), "localhost/") != 0) { + error.Errorf("unknown seccomp profile option: %s", seccompProfile.c_str()); + return std::vector {}; + } + std::string fname = seccompProfile.substr(std::string("localhost/").length(), seccompProfile.length()); + char dstpath[PATH_MAX] { 0 }; + if (!cleanpath(fname.c_str(), dstpath, sizeof(dstpath))) { + error.Errorf("failed to get clean path"); + return std::vector {}; + } + if (dstpath[0] != '/') { + error.Errorf("seccomp profile path must be absolute, but got relative path %s", fname.c_str()); + return std::vector {}; + } + docker_seccomp *seccomp_spec = get_seccomp_security_opt_spec(dstpath); + if (seccomp_spec == nullptr) { + error.Errorf("failed to parse seccomp profile"); + return std::vector {}; + } + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error err = nullptr; + char *seccomp_json = docker_seccomp_generate_json(seccomp_spec, &ctx, &err); + if (seccomp_json == nullptr) { + free(err); + free_docker_seccomp(seccomp_spec); + error.Errorf("failed to generate seccomp json!"); + return std::vector {}; + } + + // msg does not need + std::vector ret { { "seccomp", seccomp_json, "" } }; + free(err); + free(seccomp_json); + free_docker_seccomp(seccomp_spec); + return ret; +} + +std::vector GetSeccompSecurityOpts(const std::string &seccompProfile, const char &separator, Errors &error) +{ + std::vector seccompOpts = GetSeccompiSuladOpts(seccompProfile, error); + if (error.NotEmpty()) { + return std::vector(); + } + + return fmtiSuladOpts(seccompOpts, separator); +} + +std::vector GetSecurityOpts(const std::string &seccompProfile, const char &separator, Errors &error) +{ + std::vector seccompSecurityOpts = GetSeccompSecurityOpts(seccompProfile, separator, error); + if (error.NotEmpty()) { + error.Errorf("failed to generate seccomp security options for container"); + } + return seccompSecurityOpts; +} + +std::string CreateCheckpoint(cri::PodSandboxCheckpoint &checkpoint, Errors &error) +{ + cri_checkpoint *criCheckpoint { nullptr }; + struct parser_context ctx { + OPT_GEN_SIMPLIFY, 0 + }; + parser_error err { nullptr }; + char *jsonStr { nullptr }; + std::string result { "" }; + + checkpoint.CheckpointToCStruct(&criCheckpoint, error); + if (error.NotEmpty()) { + goto out; + } + free(criCheckpoint->checksum); + criCheckpoint->checksum = nullptr; + jsonStr = cri_checkpoint_generate_json(criCheckpoint, &ctx, &err); + if (jsonStr == nullptr) { + error.Errorf("Generate cri checkpoint json failed: %s", err); + goto out; + } + checkpoint.SetCheckSum(CRIHelpers::sha256(jsonStr)); + if (checkpoint.GetCheckSum().empty()) { + error.SetError("checksum is empty"); + goto out; + } + criCheckpoint->checksum = util_strdup_s(checkpoint.GetCheckSum().c_str()); + + free(jsonStr); + jsonStr = cri_checkpoint_generate_json(criCheckpoint, &ctx, &err); + if (jsonStr == nullptr) { + error.Errorf("Generate cri checkpoint json failed: %s", err); + goto out; + } + + result = jsonStr; +out: + free(err); + free(jsonStr); + free_cri_checkpoint(criCheckpoint); + return result; +} + +void GetCheckpoint(const std::string &jsonCheckPoint, cri::PodSandboxCheckpoint &checkpoint, Errors &error) +{ + cri_checkpoint *criCheckpoint { nullptr }; + struct parser_context ctx { + OPT_GEN_SIMPLIFY, 0 + }; + parser_error err { nullptr }; + std::string tmpChecksum; + char *jsonStr { nullptr }; + char *storeChecksum { nullptr }; + + criCheckpoint = cri_checkpoint_parse_data(jsonCheckPoint.c_str(), &ctx, &err); + if (criCheckpoint == nullptr) { + ERROR("Failed to unmarshal checkpoint, removing checkpoint. ErrMsg: %s", err); + error.SetError("Failed to unmarshal checkpoint"); + goto out; + } + + tmpChecksum = criCheckpoint->checksum; + storeChecksum = criCheckpoint->checksum; + criCheckpoint->checksum = nullptr; + jsonStr = cri_checkpoint_generate_json(criCheckpoint, &ctx, &err); + criCheckpoint->checksum = storeChecksum; + if (jsonStr == nullptr) { + error.Errorf("Generate cri json str failed: %s", err); + goto out; + } + + if (tmpChecksum != CRIHelpers::sha256(jsonStr)) { + ERROR("Checksum of checkpoint is not valid"); + error.SetError("checkpoint is corrupted"); + goto out; + } + + checkpoint.CStructToCheckpoint(criCheckpoint, error); +out: + free(jsonStr); + free(err); + free_cri_checkpoint(criCheckpoint); +} + +} // namespace CRIHelpers + diff --git a/src/services/cri/cri_helpers.h b/src/services/cri/cri_helpers.h new file mode 100644 index 0000000..8ec0349 --- /dev/null +++ b/src/services/cri/cri_helpers.h @@ -0,0 +1,112 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri helpers functions + *********************************************************************************/ +#ifndef _CRI_HELPERS_H_ +#define _CRI_HELPERS_H_ +#include +#include +#include +#include + +#include "api.pb.h" +#include "errors.h" +#include "container_def.h" +#include "host_config.h" +#include "callback.h" +#include "oci_image_status.h" +#include "cri_pod_network.h" +#include "checkpoint_handler.h" + +namespace CRIHelpers { + +class Constants { +public: + static const std::string DEFAULT_RUNTIME_NAME; + static const std::string POD_NETWORK_ANNOTATION_KEY; + static const std::string CONTAINER_TYPE_LABEL_KEY; + static const std::string CONTAINER_TYPE_LABEL_SANDBOX; + static const std::string CONTAINER_TYPE_LABEL_CONTAINER; + static const std::string CONTAINER_LOGPATH_LABEL_KEY; + static const std::string CONTAINER_HUGETLB_ANNOTATION_KEY; + static const std::string SANDBOX_ID_LABEL_KEY; + static const std::string KUBERNETES_CONTAINER_NAME_LABEL; + // DOCKER_IMAGEID_PREFIX is the prefix of image id in container status. + static const std::string DOCKER_IMAGEID_PREFIX; + // DOCKER_PULLABLE_IMAGEID_PREFIX is the prefix of pullable image id in container status. + static const std::string DOCKER_PULLABLE_IMAGEID_PREFIX; + static const std::string RUNTIME_READY; + static const std::string NETWORK_READY; + static const std::string POD_CHECKPOINT_KEY; + static const size_t MAX_CHECKPOINT_KEY_LEN { 250 }; +}; +std::string GetDefaultSandboxImage(Errors &err); + +json_map_string_string *MakeLabels(const google::protobuf::Map &mapLabels, Errors &error); + +json_map_string_string *MakeAnnotations(const google::protobuf::Map &mapAnnotations, + Errors &error); + +void ExtractLabels(json_map_string_string *input, google::protobuf::Map &labels); + +void ExtractAnnotations(json_map_string_string *input, google::protobuf::Map &annotations); + +int FiltersAdd(defs_filters *filters, const std::string &key, const std::string &value); + +int FiltersAddLabel(defs_filters *filters, const std::string &key, const std::string &value); + +void ProtobufAnnoMapToStd(const google::protobuf::Map &annotations, + std::map &newAnnos); + +runtime::ContainerState ContainerStatusToRuntime(Container_Status status); + +char **StringVectorToCharArray(std::vector &path); + +imagetool_image *InspectImageByID(const std::string &imageID, Errors &err); + +std::string ToPullableImageID(const std::string &id, imagetool_image *image); + +bool IsContainerNotFoundError(const std::string &err); + +bool IsImageNotFoundError(const std::string &err); + +std::string sha256(const char *val); + +cri_pod_network_element **GetNetworkPlaneFromPodAnno(const google::protobuf::Map &annotations, + size_t *len, Errors &error); + +std::unique_ptr CheckpointToSandbox(const std::string &id, + const cri::PodSandboxCheckpoint &checkpoint); + +std::string StringsJoin(const std::vector &vec, const std::string &sep); + +void UpdateCreateConfig(container_custom_config *createConfig, host_config *hc, const runtime::ContainerConfig &config, + const std::string &podSandboxID, Errors &error); + +void GenerateMountBindings(const google::protobuf::RepeatedPtrField &mounts, host_config *hostconfig, + Errors &err); + +std::vector GenerateEnvList(const ::google::protobuf::RepeatedPtrField<::runtime::KeyValue> &envs); + +bool ValidateCheckpointKey(const std::string &key, Errors &error); + +std::string ToIsuladContainerStatus(const runtime::ContainerStateValue &state); + +std::vector GetSecurityOpts(const std::string &seccompProfile, const char &separator, Errors &error); + +std::string CreateCheckpoint(cri::PodSandboxCheckpoint &checkpoint, Errors &error); + +void GetCheckpoint(const std::string &jsonCheckPoint, cri::PodSandboxCheckpoint &checkpoint, Errors &error); +}; // namespace CRIHelpers + +#endif /* _CRI_HELPERS_H_ */ diff --git a/src/services/cri/cri_image_service.cc b/src/services/cri/cri_image_service.cc new file mode 100644 index 0000000..a06f2b7 --- /dev/null +++ b/src/services/cri/cri_image_service.cc @@ -0,0 +1,391 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri image functions + *********************************************************************************/ +#include "cri_image_service.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "log.h" +#include "utils.h" +#include "cri_helpers.h" + +static void conv_image_to_grpc(const imagetool_image *element, std::unique_ptr &image) +{ + if (element == nullptr) { + return; + } + + if (element->id != nullptr) { + image->set_id(element->id); + } + + for (size_t j = 0; j < element->repo_tags_len; j++) { + if (element->repo_tags[j] != nullptr) { + image->add_repo_tags(element->repo_tags[j]); + } + } + + for (size_t j = 0; j < element->repo_digests_len; j++) { + if (element->repo_digests[j] != nullptr) { + image->add_repo_digests(element->repo_digests[j]); + } + } + + image->set_size(element->size); + + if (element->uid != nullptr) { + runtime::Int64Value *uid_value = new (std::nothrow) runtime::Int64Value; + if (uid_value == nullptr) { + return; + } + uid_value->set_value(element->uid->value); + image->set_allocated_uid(uid_value); + } + + if (element->username != nullptr) { + image->set_username(element->username); + } + + return; +} + +int CRIImageServiceImpl::pull_request_from_grpc(const runtime::ImageSpec *image, const runtime::AuthConfig *auth, + im_pull_request **request, Errors &error) +{ + im_pull_request *tmpreq = (im_pull_request *)util_common_calloc_s(sizeof(im_pull_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + error.SetError("Out of memory"); + return -1; + } + + if (!image->image().empty()) { + tmpreq->image = util_strdup_s(image->image().c_str()); + } + + if (!auth->username().empty()) { + tmpreq->username = util_strdup_s(auth->username().c_str()); + } + + if (!auth->password().empty()) { + tmpreq->password = util_strdup_s(auth->password().c_str()); + } + + if (!auth->auth().empty()) { + tmpreq->auth = util_strdup_s(auth->auth().c_str()); + } + + if (!auth->server_address().empty()) { + tmpreq->server_address = util_strdup_s(auth->server_address().c_str()); + } + + if (!auth->identity_token().empty()) { + tmpreq->identity_token = util_strdup_s(auth->identity_token().c_str()); + } + + if (!auth->registry_token().empty()) { + tmpreq->registry_token = util_strdup_s(auth->registry_token().c_str()); + } + + *request = tmpreq; + + return 0; +} + +int CRIImageServiceImpl::list_request_from_grpc(const runtime::ImageFilter *filter, im_list_request **request, + Errors &error) +{ + im_list_request *tmpreq = (im_list_request *)util_common_calloc_s(sizeof(im_list_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + error.SetError("Out of memory"); + return -1; + } + + if (!filter->image().image().empty()) { + tmpreq->filter.image.image = util_strdup_s(filter->image().image().c_str()); + } + + *request = tmpreq; + + return 0; +} + +void CRIImageServiceImpl::list_images_to_grpc(im_list_response *response, + std::vector> *images, Errors &error) +{ + imagetool_images_list *list_images = response->images; + if (list_images == nullptr) { + return; + } + + for (size_t i = 0; i < list_images->images_len; i++) { + std::unique_ptr image(new runtime::Image); + + imagetool_image *element = list_images->images[i]; + conv_image_to_grpc(element, image); + images->push_back(move(image)); + } +} + +void CRIImageServiceImpl::ListImages(const runtime::ImageFilter &filter, + std::vector> *images, Errors &error) +{ + im_list_request *request { nullptr }; + im_list_response *response { nullptr }; + + int ret = list_request_from_grpc(&filter, &request, error); + if (ret) { + goto cleanup; + } + + ret = im_list_images(request, &response); + if (ret) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call pull image"); + } + goto cleanup; + } + + list_images_to_grpc(response, images, error); + +cleanup: + DAEMON_CLEAR_ERRMSG(); + free_im_list_request(request); + free_im_list_response(response); + return; +} + +int CRIImageServiceImpl::status_request_from_grpc(const runtime::ImageSpec *image, oci_image_status_request **request, + Errors &error) +{ + oci_image_status_request *tmpreq = + (oci_image_status_request *)util_common_calloc_s(sizeof(oci_image_status_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + error.SetError("Out of memory"); + return -1; + } + + if (!image->image().empty()) { + tmpreq->image.image = util_strdup_s(image->image().c_str()); + } + + *request = tmpreq; + + return 0; +} + +std::unique_ptr CRIImageServiceImpl::status_image_to_grpc(oci_image_status_response *response, + Errors &error) +{ + imagetool_image_status *image_info = response->image_info; + if (image_info == nullptr) { + return nullptr; + } + + imagetool_image *element = image_info->image; + if (element == nullptr) { + return nullptr; + } + + std::unique_ptr image(new runtime::Image); + conv_image_to_grpc(element, image); + + return image; +} + +std::unique_ptr CRIImageServiceImpl::ImageStatus(const runtime::ImageSpec &image, Errors &error) +{ + oci_image_status_request *request { nullptr }; + oci_image_status_response *response { nullptr }; + std::unique_ptr out { nullptr }; + + int ret = status_request_from_grpc(&image, &request, error); + if (ret != 0) { + goto cleanup; + } + + ret = oci_status_image(request, &response); + if (ret != 0) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call status image"); + } + goto cleanup; + } + + out = status_image_to_grpc(response, error); + +cleanup: + DAEMON_CLEAR_ERRMSG(); + free_oci_image_status_request(request); + free_oci_image_status_response(response); + return out; +} + +std::string CRIImageServiceImpl::PullImage(const runtime::ImageSpec &image, const runtime::AuthConfig &auth, + Errors &error) +{ + std::string out_str { "" }; + im_pull_request *request { nullptr }; + im_pull_response *response { nullptr }; + + int ret = pull_request_from_grpc(&image, &auth, &request, error); + if (ret != 0) { + goto cleanup; + } +#ifdef ENABLE_OCI_IMAGE + request->type = util_strdup_s(IMAGE_TYPE_OCI); +#endif + + ret = im_pull_image(request, &response); + if (ret != 0) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call pull image"); + } + goto cleanup; + } + if (response->image_ref != nullptr) { + out_str = response->image_ref; + } + +cleanup: + DAEMON_CLEAR_ERRMSG(); + free_im_pull_request(request); + free_im_pull_response(response); + return out_str; +} + +int CRIImageServiceImpl::remove_request_from_grpc(const runtime::ImageSpec *image, im_remove_request **request, + Errors &error) +{ + im_remove_request *tmpreq = (im_remove_request *)util_common_calloc_s(sizeof(im_remove_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + error.SetError("Out of memory"); + return -1; + } + + if (!image->image().empty()) { + tmpreq->image.image = util_strdup_s(image->image().c_str()); + } + + *request = tmpreq; + + return 0; +} + +void CRIImageServiceImpl::RemoveImage(const runtime::ImageSpec &image, Errors &error) +{ + std::string out_str { "" }; + im_remove_request *request { nullptr }; + im_remove_response *response { nullptr }; + + if (remove_request_from_grpc(&image, &request, error)) { + goto cleanup; + } + + if (im_rm_image(request, &response)) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call remove image"); + } + } + +cleanup: + DAEMON_CLEAR_ERRMSG(); + free_im_remove_request(request); + free_im_remove_response(response); + return; +} + +void CRIImageServiceImpl::fs_info_to_grpc(image_fs_info_response *response, + std::vector> *fs_infos, + Errors &error) +{ + imagetool_fs_info *got_fs_info = response->fs_info; + if (got_fs_info == nullptr) { + return; + } + + for (size_t i {}; i < got_fs_info->image_filesystems_len; i++) { + std::unique_ptr fs_info(new runtime::FilesystemUsage); + + imagetool_fs_info_image_filesystems_element *element = got_fs_info->image_filesystems[i]; + + fs_info->set_timestamp(element->timestamp); + + if (element->fs_id != nullptr && element->fs_id->mountpoint != nullptr) { + runtime::StorageIdentifier *fs_id = new runtime::StorageIdentifier; + fs_id->set_uuid(element->fs_id->mountpoint); + fs_info->set_allocated_storage_id(fs_id); + } + + if (element->used_bytes != nullptr) { + runtime::UInt64Value *used_bytes = new (std::nothrow) runtime::UInt64Value; + if (used_bytes == nullptr) { + return; + } + used_bytes->set_value(element->used_bytes->value); + fs_info->set_allocated_used_bytes(used_bytes); + } + + if (element->inodes_used != nullptr) { + runtime::UInt64Value *inodes_used = new (std::nothrow) runtime::UInt64Value; + if (inodes_used == nullptr) { + return; + } + inodes_used->set_value(element->inodes_used->value); + fs_info->set_allocated_inodes_used(inodes_used); + } + + fs_infos->push_back(std::move(fs_info)); + } +} + +void CRIImageServiceImpl::ImageFsInfo(std::vector> *usages, Errors &error) +{ + image_fs_info_response *response { nullptr }; + + if (get_fs_info(&response)) { + if (response != nullptr && response->errmsg != nullptr) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call inspect image filesystem info"); + } + goto out; + } + + fs_info_to_grpc(response, usages, error); + +out: + DAEMON_CLEAR_ERRMSG(); + free_image_fs_info_response(response); + return; +} + diff --git a/src/services/cri/cri_image_service.h b/src/services/cri/cri_image_service.h new file mode 100644 index 0000000..8305937 --- /dev/null +++ b/src/services/cri/cri_image_service.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri image service function definition + *********************************************************************************/ + +#ifndef _CRI_IMAGE_SERVICES_IMPL_H_ +#define _CRI_IMAGE_SERVICES_IMPL_H_ + +#include +#include +#include +#include "cri_services.h" +#include "oci_image_pull.h" +#include "oci_image.h" +#include "oci_fs_info.h" +#include "image.h" + +class CRIImageServiceImpl : public cri::ImageManagerService { +public: + CRIImageServiceImpl() = default; + CRIImageServiceImpl(const CRIImageServiceImpl &) = delete; + CRIImageServiceImpl &operator=(const CRIImageServiceImpl &) = delete; + virtual ~CRIImageServiceImpl() = default; + + void ListImages(const runtime::ImageFilter &filter, std::vector> *images, + Errors &error) override; + + std::unique_ptr ImageStatus(const runtime::ImageSpec &image, Errors &error) override; + + std::string PullImage(const runtime::ImageSpec &image, const runtime::AuthConfig &auth, + Errors &error) override; + + void RemoveImage(const runtime::ImageSpec &image, Errors &error) override; + + void ImageFsInfo(std::vector> *usages, Errors &error) override; + +private: + int pull_request_from_grpc(const runtime::ImageSpec *image, const runtime::AuthConfig *auth, + im_pull_request **request, Errors &error); + + int list_request_from_grpc(const runtime::ImageFilter *filter, im_list_request **request, Errors &error); + + void list_images_to_grpc(im_list_response *response, std::vector> *images, + Errors &error); + + int status_request_from_grpc(const runtime::ImageSpec *image, oci_image_status_request **request, Errors &error); + + std::unique_ptr status_image_to_grpc(oci_image_status_response *response, Errors &error); + + void fs_info_to_grpc(image_fs_info_response *response, + std::vector> *fs_infos, Errors &error); + + int remove_request_from_grpc(const runtime::ImageSpec *image, im_remove_request **request, Errors &error); +}; + +#endif /* _CRI_IMAGE_SERVICES_IMPL_H_ */ diff --git a/src/services/cri/cri_runtime_service.cc b/src/services/cri/cri_runtime_service.cc new file mode 100644 index 0000000..2babaa8 --- /dev/null +++ b/src/services/cri/cri_runtime_service.cc @@ -0,0 +1,184 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri runtime service function + *********************************************************************************/ +#include "cri_runtime_service.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "config.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "cri_helpers.h" +#include "network_plugin.h" +#include "container_inspect.h" + +namespace CRIRuntimeService { +std::string Constants::namespaceModeHost { "host" }; +std::string Constants::nameDelimiter { "_" }; +char Constants::nameDelimiterChar { '_' }; +std::string Constants::kubePrefix { "k8s" }; +std::string Constants::sandboxContainerName { "POD" }; +std::string Constants::kubeAPIVersion { "0.1.0" }; +std::string Constants::iSulaRuntimeName { "iSulad" }; +std::string Constants::RESOLV_CONF_PATH { "/etc/resolv.conf" }; +} +CRIRuntimeServiceImpl::CRIRuntimeServiceImpl() +{ + m_cb = get_service_callback(); + if (m_cb == nullptr) { + ERROR("Get callback failed"); + } +} + +void CRIRuntimeServiceImpl::VersionResponseToGRPC(container_version_response *response, + runtime::VersionResponse *gResponse, Errors &error) +{ + gResponse->set_version(CRIRuntimeService::Constants::kubeAPIVersion); + gResponse->set_runtime_name(CRIRuntimeService::Constants::iSulaRuntimeName); + gResponse->set_runtime_version(response->version ? response->version : ""); + gResponse->set_runtime_api_version(VERSION); +} + +void CRIRuntimeServiceImpl::Init(Network::NetworkPluginConf mConf, const std::string &podSandboxImage, Errors &err) +{ + if (!podSandboxImage.empty()) { + m_podSandboxImage = podSandboxImage; + } else { + m_podSandboxImage = CRIHelpers::GetDefaultSandboxImage(err); + if (err.NotEmpty()) { + return; + } + } + + std::vector> plugins; + Network::ProbeNetworkPlugins(mConf.GetPluginConfDir(), mConf.GetPluginBinDir(), &plugins); + + std::shared_ptr chosen { nullptr }; + Network::InitNetworkPlugin(&plugins, mConf.GetPluginName(), this, mConf.GetHairpinMode(), + mConf.GetNonMasqueradeCIDR(), mConf.GetMTU(), &chosen, err); + if (err.NotEmpty()) { + ERROR("Init network plugin failed: %s", err.GetCMessage()); + return; + } + + m_pluginManager = std::make_shared(chosen); +} + +void CRIRuntimeServiceImpl::Version(const std::string &apiVersion, runtime::VersionResponse *versionResponse, + Errors &error) +{ + (void)apiVersion; + + if (m_cb == nullptr || m_cb->container.version == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + container_version_response *response { nullptr }; + if (m_cb->container.version(nullptr, &response) != 0) { + if (response != nullptr && response->errmsg) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call version callback"); + } + goto cleanup; + } + VersionResponseToGRPC(response, versionResponse, error); + +cleanup: + free_container_version_response(response); +} + +void CRIRuntimeServiceImpl::UpdateRuntimeConfig(const runtime::RuntimeConfig &config, Errors &error) +{ + const std::string NET_PLUGIN_EVENT_POD_CIDR_CHANGE { "pod-cidr-change" }; + const std::string NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR { "pod-cidr" }; + + INFO("iSulad cri received runtime config: %s", config.network_config().pod_cidr().c_str()); + if (m_pluginManager != nullptr && config.has_network_config() && !(config.network_config().pod_cidr().empty())) { + std::map events; + events[NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR] = config.network_config().pod_cidr(); + m_pluginManager->Event(NET_PLUGIN_EVENT_POD_CIDR_CHANGE, events); + } + return; +} + +std::unique_ptr CRIRuntimeServiceImpl::Status(Errors &error) +{ + std::unique_ptr status(new runtime::RuntimeStatus); + + runtime::RuntimeCondition *runtimeReady = status->add_conditions(); + runtimeReady->set_type(CRIHelpers::Constants::RUNTIME_READY); + runtimeReady->set_status(true); + runtime::RuntimeCondition *networkReady = status->add_conditions(); + networkReady->set_type(CRIHelpers::Constants::NETWORK_READY); + networkReady->set_status(true); + + container_version_response *response { nullptr }; + if (m_cb == nullptr || m_cb->container.version == nullptr || m_cb->container.version(nullptr, &response) != 0) { + runtimeReady->set_status(false); + runtimeReady->set_reason("iSuladDaemonNotReady"); + std::string msg = "iSulad: failed to get iSulad version: "; + if (response != nullptr && response->errmsg != nullptr) { + msg += response->errmsg; + } else { + msg += "Get version callback failed"; + } + runtimeReady->set_message(msg); + } + free_container_version_response(response); + + // Get status of network + m_pluginManager->Status(error); + if (error.NotEmpty()) { + networkReady->set_status(false); + networkReady->set_reason("NetworkPluginNotReady"); + networkReady->set_message("iSulad: network plugin is not ready: " + error.GetMessage()); + error.Clear(); + } + return status; +} + +std::string CRIRuntimeServiceImpl::GetNetNS(const std::string &podSandboxID, Errors &err) +{ + char fullpath[PATH_MAX] { 0 }; + std::string result { "" }; + const std::string NetNSFmt { "/proc/%d/ns/net" }; + + container_inspect *inspect_data = InspectContainer(podSandboxID, err); + if (inspect_data == nullptr) { + goto cleanup; + } + if (inspect_data->state->pid == 0) { + err.Errorf("cannot find network namespace for the terminated container %s", podSandboxID.c_str()); + goto cleanup; + } + if (sprintf_s(fullpath, sizeof(fullpath), NetNSFmt.c_str(), inspect_data->state->pid) < 0) { + err.SetError("Sprint nspath failed"); + goto cleanup; + } + result = fullpath; + +cleanup: + free_container_inspect(inspect_data); + return result; +} diff --git a/src/services/cri/cri_runtime_service.h b/src/services/cri/cri_runtime_service.h new file mode 100644 index 0000000..ba7268d --- /dev/null +++ b/src/services/cri/cri_runtime_service.h @@ -0,0 +1,265 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri runtime service function definition + **********************************************************************************/ +#ifndef _CRI_RUNTIME_SERVICES_IMPL_H_ +#define _CRI_RUNTIME_SERVICES_IMPL_H_ + +#include +#include +#include +#include +#include + +#include "checkpoint_handler.h" +#include "network_plugin.h" +#include "cri_services.h" +#include "callback.h" +#include "container_inspect.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "errors.h" +#include "cri_image_service.h" +#include "cri_pod_network.h" + +namespace CRIRuntimeService { +class Constants { +public: + static std::string namespaceModeHost; + + // sandboxname default values + static std::string nameDelimiter; + static char nameDelimiterChar; + static std::string kubePrefix; + static std::string sandboxContainerName; + static std::string kubeAPIVersion; + static std::string iSulaRuntimeName; + static constexpr int64_t DefaultMemorySwap { 0 }; + static constexpr int64_t DefaultSandboxCPUshares { 2 }; + static constexpr int64_t PodInfraOOMAdj { -998 }; + + // container mounts files + static std::string RESOLV_CONF_PATH; + static constexpr int MAX_DNS_SEARCHES { 6 }; +}; +} // namespace CRIRuntimeService + +class CRIRuntimeServiceImpl : public cri::RuntimeManager, + public cri::RuntimeVersioner, + public cri::PodSandboxManager, + public cri::ContainerManager { +public: + CRIRuntimeServiceImpl(); + CRIRuntimeServiceImpl(const CRIRuntimeServiceImpl &) = delete; + CRIRuntimeServiceImpl &operator=(const CRIRuntimeServiceImpl &) = delete; + + virtual ~CRIRuntimeServiceImpl() = default; + + void Init(Network::NetworkPluginConf mConf, const std::string &podSandboxImage, Errors &err); + + std::string GetRealContainerOrSandboxID(const std::string &id, bool isSandbox, Errors &error); + + container_inspect *InspectContainer(const std::string &containerID, Errors &err); + + std::string GetNetNS(const std::string &podSandboxID, Errors &err); + + void Version(const std::string &apiVersion, runtime::VersionResponse *versionResponse, + Errors &error) override; + + void UpdateRuntimeConfig(const runtime::RuntimeConfig &config, Errors &error) override; + + std::unique_ptr Status(Errors &error) override; + + std::string RunPodSandbox(const runtime::PodSandboxConfig &config, Errors &error) override; + + void StopPodSandbox(const std::string &podSandboxID, Errors &error) override; + + void RemovePodSandbox(const std::string &podSandboxID, Errors &error) override; + + std::unique_ptr PodSandboxStatus(const std::string &podSandboxID, + Errors &error) override; + + void ListPodSandbox(const runtime::PodSandboxFilter *filter, + std::vector> *pods, Errors &error) override; + + void PortForward(const runtime::PortForwardRequest &req, runtime::PortForwardResponse *resp, + Errors &error) override; + + std::string CreateContainer(const std::string &podSandboxID, + const runtime::ContainerConfig &containerConfig, + const runtime::PodSandboxConfig &podSandboxConfig, Errors &error) override; + + void StartContainer(const std::string &containerID, Errors &error) override; + + void StopContainer(const std::string &containerID, int64_t timeout, Errors &error) override; + + void RemoveContainer(const std::string &containerID, Errors &error) override; + + void ListContainers(const runtime::ContainerFilter *filter, + std::vector> *containers, Errors &error) override; + + void ListContainerStats(const runtime::ContainerStatsFilter *filter, + std::vector> *containerstats, + Errors &error) override; + + std::unique_ptr ContainerStatus(const std::string &containerID, + Errors &error) override; + + void UpdateContainerResources(const std::string &containerID, + const runtime::LinuxContainerResources &resources, Errors &error) override; + + void ExecSync(const std::string &containerID, const google::protobuf::RepeatedPtrField &cmd, + int64_t timeout, runtime::ExecSyncResponse *reply, Errors &error) override; + + void Exec(const runtime::ExecRequest &req, runtime::ExecResponse *resp, Errors &error) override; + + void Attach(const runtime::AttachRequest &req, runtime::AttachResponse *resp, Errors &error) override; + +private: + void VersionResponseToGRPC(container_version_response *response, runtime::VersionResponse *gResponse, + Errors &error); + bool IsDefaultNetworkPlane(cri_pod_network_element *network); + void SetSandboxStatusNetwork(container_inspect *inspect, const std::string &podSandboxID, + std::unique_ptr &podStatus, Errors &error); + + void PodSandboxStatusToGRPC(container_inspect *inspect, const std::string &podSandboxID, + std::unique_ptr &podStatus, Errors &error); + + void ListPodSandboxToGRPC(container_list_response *response, + std::vector> *pods, bool filterOutReadySandboxes, + Errors &error); + + void ListContainersToGRPC(container_list_response *response, std::vector> *pods, + Errors &error); + + void ContainerStatsToGRPC(container_stats_response *response, + std::vector> *pods, Errors &error); + + void ContainerStatusToGRPC(container_inspect *inspect, std::unique_ptr &contStatus, + Errors &error); + + void ExecSyncFromGRPC(const std::string &containerID, const google::protobuf::RepeatedPtrField &cmd, + int64_t timeout, container_exec_request **request, Errors &error); + + void ListContainersFromGRPC(const runtime::ContainerFilter *filter, container_list_request **request, + Errors &error); + + void ListPodSandboxFromGRPC(const runtime::PodSandboxFilter *filter, container_list_request **request, + bool *filterOutReadySandboxes, Errors &error); + + void ApplySandboxResources(const runtime::LinuxPodSandboxConfig *lc, host_config *hc, Errors &error); + + void ApplySandboxLinuxOptions(const runtime::LinuxPodSandboxConfig &lc, host_config *hc, + container_custom_config *custom_config, Errors &error); + + void MakeSandboxIsuladConfig(const runtime::PodSandboxConfig &c, host_config *hc, + container_custom_config *custom_config, Errors &error); + + void MakeContainerConfig(const runtime::ContainerConfig &config, container_custom_config *cConfig, Errors &error); + + void GetContainerLogPath(const std::string &containerID, char **path, char **realPath, Errors &error); + + void CreateContainerLogSymlink(const std::string &containerID, Errors &error); + + void RemoveContainerLogSymlink(const std::string &containerID, Errors &error); + + std::string MakeSandboxName(const runtime::PodSandboxMetadata &metadata); + + std::string MakeContainerName(const runtime::PodSandboxConfig &s, const runtime::ContainerConfig &c); + + void modifyContainerNamespaceOptions(bool hasOpts, const runtime::NamespaceOption &nsOpts, const char *ID, + host_config *hconf, Errors &err); + + bool SharesHostNetwork(container_inspect *inspect); + bool SharesHostPid(container_inspect *inspect); + bool SharesHostIpc(container_inspect *inspect); + + void GetContainerTimeStamps(container_inspect *inspect, int64_t *createdAt, int64_t *startedAt, int64_t *finishedAt, + Errors &err); + int ValidateExecRequest(const runtime::ExecRequest &req, Errors &error); + + std::string BuildURL(const std::string &method, const std::string &token); + + int ValidateAttachRequest(const runtime::AttachRequest &req, Errors &error); + + std::string ParseCheckpointProtocol(runtime::Protocol protocol); + + void ConstructPodSandboxCheckpoint(const runtime::PodSandboxConfig &config, cri::PodSandboxCheckpoint &checkpoint); + + std::string GetIP(const std::string &podSandboxID, container_inspect *inspect, const std::string &networkInterface, + Errors &error); + std::string GetIPFromPlugin(container_inspect *inspect, std::string networkInterface, Errors &error); + bool GetNetworkReady(const std::string &podSandboxID, Errors &error); + void SetNetworkReady(const std::string &podSandboxID, bool ready, Errors &error); + void ClearNetworkReady(const std::string &podSandboxID); + bool EnsureSandboxImageExists(const std::string &image, Errors &error); + void StopContainerHelper(const std::string &containerID, Errors &error); + void SetupSandboxFiles(const std::string &podID, const runtime::PodSandboxConfig &config, Errors &error); + container_create_request *GenerateCreateContainerRequest(const std::string &realPodSandboxID, + const runtime::ContainerConfig &containerConfig, + const runtime::PodSandboxConfig &podSandboxConfig, + Errors &error); + host_config *GenerateCreateContainerHostConfig(const runtime::ContainerConfig &containerConfig, Errors &error); + int PackCreateContainerHostConfigSecurityContext(const runtime::ContainerConfig &containerConfig, + host_config *hostconfig, Errors &error); + int PackCreateContainerHostConfigDevices(const runtime::ContainerConfig &containerConfig, host_config *hostconfig, + Errors &error); + container_custom_config *GenerateCreateContainerCustomConfig(const std::string &realPodSandboxID, + const runtime::ContainerConfig &containerConfig, + const runtime::PodSandboxConfig &podSandboxConfig, + Errors &error); + int PackContainerImageToStatus(container_inspect *inspect, std::unique_ptr &contStatus, + Errors &error); + void UpdateBaseStatusFromInspect(container_inspect *inspect, int64_t &createdAt, int64_t &startedAt, + int64_t &finishedAt, std::unique_ptr &contStatus); + void PackLabelsToStatus(container_inspect *inspect, std::unique_ptr &contStatus); + void ConvertMountsToStatus(container_inspect *inspect, std::unique_ptr &contStatus); + + void SetupSandboxNetwork(const runtime::PodSandboxConfig &config, const std::string &response_id, + const std::string &jsonCheckpoint, Errors &error); + void SetupUserDefinedNetworkPlane(const runtime::PodSandboxConfig &config, const std::string &response_id, + container_inspect *inspect_data, std::map &stdAnnos, + Errors &error); + void StartSandboxContainer(const std::string &response_id, Errors &error); + std::string CreateSandboxContainer(const runtime::PodSandboxConfig &config, const std::string &image, + std::string &jsonCheckpoint, Errors &error); + container_create_request *GenerateSandboxCreateContainerRequest(const runtime::PodSandboxConfig &config, + const std::string &image, + std::string &jsonCheckpoint, Errors &error); + container_create_request *PackCreateContainerRequest(const runtime::PodSandboxConfig &config, + const std::string &image, host_config *hostconfig, + container_custom_config *custom_config, Errors &error); + int GetRealSandboxIDToStop(const std::string &podSandboxID, bool &hostNetwork, std::string &name, std::string &ns, + std::string &realSandboxID, std::map &stdAnnos, Errors &error); + int StopAllContainersInSandbox(const std::string &realSandboxID, Errors &error); + int TearDownPodCniNetwork(const std::string &realSandboxID, std::vector &errlist, + std::map &stdAnnos, const std::string &ns, + const std::string &name, Errors &error); + int ClearCniNetwork(const std::string &realSandboxID, bool hostNetwork, const std::string &ns, + const std::string &name, std::vector &errlist, + std::map &stdAnnos, Errors &error); + int RemoveAllContainersInSandbox(const std::string &realSandboxID, std::vector &errors); + int DoRemovePodSandbox(const std::string &realSandboxID, std::vector &errors); + +private: + service_callback_t *m_cb { nullptr }; + + std::shared_ptr m_pluginManager { nullptr }; + + std::map m_networkReady; + pthread_mutex_t m_networkReadyLock = PTHREAD_MUTEX_INITIALIZER; + CRIImageServiceImpl rImageService; + std::string m_podSandboxImage; +}; + +#endif /* _CRI_RUNTIME_SERVICES_IMPL_H_ */ diff --git a/src/services/cri/cri_sandbox.cc b/src/services/cri/cri_sandbox.cc new file mode 100644 index 0000000..e7c2fae --- /dev/null +++ b/src/services/cri/cri_sandbox.cc @@ -0,0 +1,1246 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri sandbox functions + *********************************************************************************/ +#include "cri_sandbox.h" +#include "cri_runtime_service.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "log.h" +#include "utils.h" +#include "errors.h" +#include "naming.h" +#include "host_config.h" +#include "cri_helpers.h" +#include "container_custom_config.h" +#include "checkpoint_handler.h" +#include "cri_security_context.h" + +bool CRIRuntimeServiceImpl::SharesHostNetwork(container_inspect *inspect) +{ + if (inspect != nullptr && inspect->host_config != nullptr && inspect->host_config->network_mode) { + return strcmp(inspect->host_config->network_mode, CRIRuntimeService::Constants::namespaceModeHost.c_str()) == 0; + } + return false; +} + +bool CRIRuntimeServiceImpl::SharesHostPid(container_inspect *inspect) +{ + if (inspect != nullptr && inspect->host_config != nullptr && inspect->host_config->pid_mode) { + return strcmp(inspect->host_config->pid_mode, CRIRuntimeService::Constants::namespaceModeHost.c_str()) == 0; + } + return false; +} + +bool CRIRuntimeServiceImpl::SharesHostIpc(container_inspect *inspect) +{ + if (inspect != nullptr && inspect->host_config != nullptr && inspect->host_config->ipc_mode) { + return strcmp(inspect->host_config->ipc_mode, CRIRuntimeService::Constants::namespaceModeHost.c_str()) == 0; + } + return false; +} + +bool CRIRuntimeServiceImpl::EnsureSandboxImageExists(const std::string &image, Errors &error) +{ + runtime::ImageSpec imageRef; + runtime::AuthConfig auth; + + imageRef.set_image(image); + + std::string outRef = rImageService.PullImage(imageRef, auth, error); + if (!error.Empty() || outRef.empty()) { + return false; + } + + return true; +} + +std::string CRIRuntimeServiceImpl::ParseCheckpointProtocol(runtime::Protocol protocol) +{ + switch (protocol) { + case runtime::UDP: + return "udp"; + case runtime::TCP: + default: + return "tcp"; + } +} + +void CRIRuntimeServiceImpl::ConstructPodSandboxCheckpoint(const runtime::PodSandboxConfig &config, + cri::PodSandboxCheckpoint &checkpoint) +{ + checkpoint.SetName(config.metadata().name()); + checkpoint.SetNamespace(config.metadata().namespace_()); + checkpoint.SetData(new cri::CheckpointData); + + int len = config.port_mappings_size(); + for (int i = 0; i < len; i++) { + cri::PortMapping item; + + runtime::PortMapping iter = config.port_mappings(i); + item.SetProtocol(ParseCheckpointProtocol(iter.protocol())); + item.SetContainerPort(iter.container_port()); + item.SetHostPort(iter.host_port()); + (checkpoint.GetData())->InsertPortMapping(item); + } + + (checkpoint.GetData())->SetHostNetwork(config.linux().security_context().namespace_options().host_network()); +} + +void CRIRuntimeServiceImpl::ApplySandboxResources(const runtime::LinuxPodSandboxConfig *lc, host_config *hc, + Errors &error) +{ + hc->memory_swap = CRIRuntimeService::Constants::DefaultMemorySwap; + hc->cpu_shares = CRIRuntimeService::Constants::DefaultSandboxCPUshares; +} + +void CRIRuntimeServiceImpl::ApplySandboxLinuxOptions(const runtime::LinuxPodSandboxConfig &lc, host_config *hc, + container_custom_config *custom_config, Errors &error) +{ + CRISecurity::ApplySandboxSecurityContext(lc, custom_config, hc, error); + if (error.NotEmpty()) { + return; + } + + if (!lc.cgroup_parent().empty()) { + hc->cgroup_parent = util_strdup_s(lc.cgroup_parent().c_str()); + } + int len = lc.sysctls_size(); + if (len <= 0) { + return; + } + + if (len > LIST_SIZE_MAX) { + error.Errorf("Too many sysctls, the limit is %d", LIST_SIZE_MAX); + return; + } + hc->sysctls = (json_map_string_string *)util_common_calloc_s(sizeof(json_map_string_string)); + if (hc->sysctls == nullptr) { + error.SetError("Out of memory"); + return; + } + + auto iter = lc.sysctls().begin(); + while (iter != lc.sysctls().end()) { + if (append_json_map_string_string(hc->sysctls, iter->first.c_str(), iter->second.c_str()) != 0) { + error.SetError("Failed to append sysctl"); + return; + } + ++iter; + } +} + +void CRIRuntimeServiceImpl::MakeSandboxIsuladConfig(const runtime::PodSandboxConfig &c, host_config *hc, + container_custom_config *custom_config, Errors &error) +{ + custom_config->labels = CRIHelpers::MakeLabels(c.labels(), error); + if (error.NotEmpty()) { + return; + } + if (append_json_map_string_string(custom_config->labels, CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY.c_str(), + CRIHelpers::Constants::CONTAINER_TYPE_LABEL_SANDBOX.c_str()) != 0) { + error.SetError("Append container type into labels failed"); + return; + } + + custom_config->annotations = CRIHelpers::MakeAnnotations(c.annotations(), error); + if (error.NotEmpty()) { + return; + } + + if (!c.hostname().empty()) { + custom_config->hostname = util_strdup_s(c.hostname().c_str()); + } + + if (c.has_linux()) { + ApplySandboxLinuxOptions(c.linux(), hc, custom_config, error); + if (error.NotEmpty()) { + return; + } + } + + hc->oom_score_adj = CRIRuntimeService::Constants::PodInfraOOMAdj; + + ApplySandboxResources(c.has_linux() ? &c.linux() : nullptr, hc, error); + if (error.NotEmpty()) { + return; + } + + const char securityOptSep = '='; + + // Security Opts + if (c.linux().has_security_context()) { + std::vector securityOpts = + CRIHelpers::GetSecurityOpts(c.linux().security_context().seccomp_profile_path(), securityOptSep, error); + if (error.NotEmpty()) { + error.Errorf("failed to generate security options for sandbox %s", c.metadata().name().c_str()); + return; + } + if (securityOpts.size() > 0) { + char **tmp_security_opt = nullptr; + + if (securityOpts.size() > (SIZE_MAX / sizeof(char *)) - hc->security_opt_len) { + error.Errorf("Out of memory"); + return; + } + size_t newSize = (hc->security_opt_len + securityOpts.size()) * sizeof(char *); + size_t oldSize = hc->security_opt_len * sizeof(char *); + int ret = mem_realloc((void **)(&tmp_security_opt), newSize, (void *)hc->security_opt, oldSize); + if (ret != 0) { + error.Errorf("Out of memory"); + return; + } + hc->security_opt = tmp_security_opt; + for (size_t i = 0; i < securityOpts.size(); i++) { + hc->security_opt[hc->security_opt_len] = util_strdup_s(securityOpts[i].c_str()); + hc->security_opt_len++; + } + } + } +} + +void CRIRuntimeServiceImpl::SetupSandboxFiles(const std::string &resolvPath, const runtime::PodSandboxConfig &config, + Errors &error) +{ + if (resolvPath.empty()) { + return; + } + std::string resolvContent; + + /* set DNS options */ + int len = config.dns_config().searches_size(); + if (len > CRIRuntimeService::Constants::MAX_DNS_SEARCHES) { + error.SetError("DNSOption.Searches has more than 6 domains"); + return; + } + resolvContent = "search "; + int i; + for (i = 0; i < len - 1; i++) { + resolvContent += (config.dns_config().searches(i) + " "); + } + resolvContent += (config.dns_config().searches(i) + "\n"); + len = config.dns_config().servers_size(); + resolvContent += "nameserver "; + for (i = 0; i < len - 1; i++) { + resolvContent += (config.dns_config().servers(i) + "\nnameserver "); + } + resolvContent += config.dns_config().servers(i) + "\n"; + len = config.dns_config().options_size(); + resolvContent += "options "; + for (i = 0; i < len - 1; i++) { + resolvContent += (config.dns_config().options(i) + " "); + } + resolvContent += (config.dns_config().options(i) + "\n"); + + if (!resolvContent.empty()) { + if (util_write_file(resolvPath.c_str(), resolvContent.c_str(), resolvContent.size()) != 0) { + error.SetError("Failed to write resolv content"); + } + } +} + +container_create_request *CRIRuntimeServiceImpl::PackCreateContainerRequest(const runtime::PodSandboxConfig &config, + const std::string &image, + host_config *hostconfig, + container_custom_config *custom_config, + Errors &error) +{ + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + parser_error perror = nullptr; + container_create_request *create_request = + (container_create_request *)util_common_calloc_s(sizeof(*create_request)); + if (create_request == nullptr) { + error.Errorf("Out of memory"); + return nullptr; + } + + std::string sandboxName = CRINaming::MakeSandboxName(config.metadata()); + create_request->id = util_strdup_s(sandboxName.c_str()); + create_request->runtime = util_strdup_s(CRIHelpers::Constants::DEFAULT_RUNTIME_NAME.c_str()); + create_request->image = util_strdup_s(image.c_str()); + + create_request->hostconfig = host_config_generate_json(hostconfig, &ctx, &perror); + if (create_request->hostconfig == nullptr) { + error.Errorf("Failed to generate host config json: %s", perror); + goto error_out; + } + create_request->customconfig = container_custom_config_generate_json(custom_config, &ctx, &perror); + if (create_request->customconfig == nullptr) { + error.Errorf("Failed to generate custom config json: %s", perror); + goto error_out; + } + + free(perror); + return create_request; +error_out: + free_container_create_request(create_request); + free(perror); + return nullptr; +} + +container_create_request *CRIRuntimeServiceImpl::GenerateSandboxCreateContainerRequest( + const runtime::PodSandboxConfig &config, const std::string &image, std::string &jsonCheckpoint, Errors &error) +{ + container_create_request *create_request = nullptr; + host_config *hostconfig = nullptr; + container_custom_config *custom_config = nullptr; + cri::PodSandboxCheckpoint checkpoint; + + hostconfig = (host_config *)util_common_calloc_s(sizeof(host_config)); + if (hostconfig == nullptr) { + error.SetError("Out of memory"); + goto error_out; + } + + custom_config = (container_custom_config *)util_common_calloc_s(sizeof(container_custom_config)); + if (custom_config == nullptr) { + error.SetError("Out of memory"); + goto error_out; + } + + MakeSandboxIsuladConfig(config, hostconfig, custom_config, error); + if (error.NotEmpty()) { + ERROR("Failed to make sandbox config for pod %s: %s", config.metadata().name().c_str(), error.GetCMessage()); + error.Errorf("Failed to make sandbox config for pod %s: %s", config.metadata().name().c_str(), + error.GetCMessage()); + goto error_out; + } + + // add checkpoint into annotations + ConstructPodSandboxCheckpoint(config, checkpoint); + jsonCheckpoint = CRIHelpers::CreateCheckpoint(checkpoint, error); + if (error.NotEmpty()) { + goto error_out; + } + + if (append_json_map_string_string(custom_config->annotations, CRIHelpers::Constants::POD_CHECKPOINT_KEY.c_str(), + jsonCheckpoint.c_str()) != 0) { + error.SetError("Append checkpoint into annotations failed"); + goto error_out; + } + + create_request = PackCreateContainerRequest(config, image, hostconfig, custom_config, error); + if (create_request == nullptr) { + error.SetError("Failed to pack create container request"); + goto error_out; + } + + goto cleanup; +error_out: + free_container_create_request(create_request); + create_request = nullptr; +cleanup: + free_host_config(hostconfig); + free_container_custom_config(custom_config); + return create_request; +} + +std::string CRIRuntimeServiceImpl::CreateSandboxContainer(const runtime::PodSandboxConfig &config, + const std::string &image, std::string &jsonCheckpoint, + Errors &error) +{ + std::string response_id { "" }; + container_create_request *create_request = + GenerateSandboxCreateContainerRequest(config, image, jsonCheckpoint, error); + if (error.NotEmpty()) { + return response_id; + } + + container_create_response *create_response = nullptr; + if (m_cb->container.create(create_request, &create_response) != 0) { + if (create_response != nullptr && create_response->errmsg) { + error.SetError(create_response->errmsg); + } else { + error.SetError("Failed to call create container callback"); + } + goto cleanup; + } + response_id = create_response->id; +cleanup: + free_container_create_request(create_request); + free_container_create_response(create_response); + return response_id; +} + +void CRIRuntimeServiceImpl::StartSandboxContainer(const std::string &response_id, Errors &error) +{ + container_start_request *start_request = + (container_start_request *)util_common_calloc_s(sizeof(container_start_request)); + if (start_request == nullptr) { + error.SetError("Out of memory"); + return; + } + start_request->id = util_strdup_s(response_id.c_str()); + container_start_response *start_response = nullptr; + int ret = m_cb->container.start(start_request, &start_response, -1, nullptr, nullptr); + if (ret != 0) { + if (start_response != nullptr && start_response->errmsg) { + error.SetError(start_response->errmsg); + } else { + error.SetError("Failed to call start container callback"); + } + } + free_container_start_request(start_request); + free_container_start_response(start_response); +} + +void CRIRuntimeServiceImpl::SetupUserDefinedNetworkPlane(const runtime::PodSandboxConfig &config, + const std::string &response_id, + container_inspect *inspect_data, + std::map &stdAnnos, Errors &error) +{ + google::protobuf::Map annotations; + CRIHelpers::ExtractAnnotations(inspect_data->config->annotations, annotations); + + size_t len = 0; + cri_pod_network_element **networks = CRIHelpers::GetNetworkPlaneFromPodAnno(annotations, &len, error); + if (error.NotEmpty()) { + ERROR("Couldn't get network plane from pod annotations: %s", error.GetCMessage()); + error.Errorf("Couldn't get network plane from pod annotations: %s", error.GetCMessage()); + goto cleanup; + } + for (size_t i = 0; i < len; i++) { + if (networks[i] && networks[i]->name && networks[i]->interface && + strcmp(networks[i]->name, Network::DEFAULT_NETWORK_PLANE_NAME.c_str()) != 0) { + INFO("SetupPod net: %s", networks[i]->name); + m_pluginManager->SetUpPod(config.metadata().namespace_(), config.metadata().name(), networks[i]->name, + networks[i]->interface, response_id, stdAnnos, error); + if (error.Empty()) { + continue; + } + Errors tmpErr; + StopContainerHelper(response_id, tmpErr); + if (tmpErr.NotEmpty()) { + WARN("Failed to stop sandbox container %s for pod %s: %s", response_id.c_str(), networks[i]->name, + tmpErr.GetCMessage()); + } + goto cleanup; + } + } +cleanup: + free_cri_pod_network(networks, len); +} + +void CRIRuntimeServiceImpl::SetupSandboxNetwork(const runtime::PodSandboxConfig &config, const std::string &response_id, + const std::string &jsonCheckpoint, Errors &error) +{ + std::map stdAnnos; + container_inspect *inspect_data = InspectContainer(response_id, error); + if (error.NotEmpty()) { + return; + } + + // Setup sandbox files + if (config.has_dns_config() && inspect_data->resolv_conf_path != nullptr) { + INFO("Over write resolv.conf: %s", inspect_data->resolv_conf_path); + SetupSandboxFiles(inspect_data->resolv_conf_path, config, error); + if (error.NotEmpty()) { + ERROR("failed to setup sandbox files"); + goto cleanup; + } + } + // Do not invoke network plugins if in hostNetwork mode. + if (config.linux().security_context().namespace_options().host_network()) { + goto cleanup; + } + + // Setup networking for the sandbox. + CRIHelpers::ProtobufAnnoMapToStd(config.annotations(), stdAnnos); + stdAnnos[CRIHelpers::Constants::POD_CHECKPOINT_KEY] = jsonCheckpoint; + m_pluginManager->SetUpPod(config.metadata().namespace_(), config.metadata().name(), + Network::DEFAULT_NETWORK_PLANE_NAME, Network::DEFAULT_NETWORK_INTERFACE_NAME, + response_id, stdAnnos, error); + if (error.NotEmpty()) { + ERROR("SetupPod failed: %s", error.GetCMessage()); + StopContainerHelper(response_id, error); + goto cleanup; + } + + // Multi network plane featrue + // Set up user defined network plane + SetupUserDefinedNetworkPlane(config, response_id, inspect_data, stdAnnos, error); + if (error.NotEmpty()) { + ERROR("failed to user defined network plane"); + goto cleanup; + } +cleanup: + free_container_inspect(inspect_data); +} + +std::string CRIRuntimeServiceImpl::RunPodSandbox(const runtime::PodSandboxConfig &config, Errors &error) +{ + std::string response_id; + std::string jsonCheckpoint; + if (m_cb == nullptr || m_cb->container.create == nullptr || m_cb->container.start == nullptr) { + error.SetError("Unimplemented callback"); + return response_id; + } + + // Step 1: Pull the image for the sandbox. + const std::string &image = m_podSandboxImage; + if (!EnsureSandboxImageExists(image, error)) { + ERROR("Failed to pull sandbox image %s: %s", image.c_str(), error.NotEmpty() ? error.GetCMessage() : ""); + error.Errorf("Failed to pull sandbox image %s: %s", image.c_str(), error.NotEmpty() ? error.GetCMessage() : ""); + goto cleanup; + } + + // Step 2: Create the sandbox container. + response_id = CreateSandboxContainer(config, image, jsonCheckpoint, error); + if (error.NotEmpty()) { + goto cleanup; + } + + // Step 3: Enable network + SetNetworkReady(response_id, false, error); + if (error.NotEmpty()) { + WARN("disable network: %s", error.GetCMessage()); + error.Clear(); + } + + // Step 4: Start the sandbox container. + StartSandboxContainer(response_id, error); + if (error.NotEmpty()) { + goto cleanup; + } + // Step 5: Setup networking for the sandbox. + SetupSandboxNetwork(config, response_id, jsonCheckpoint, error); + if (error.NotEmpty()) { + goto cleanup; + } + +cleanup: + if (error.Empty()) { + SetNetworkReady(response_id, true, error); + DEBUG("set %s ready", response_id.c_str()); + error.Clear(); + } + return response_id; +} + +int CRIRuntimeServiceImpl::GetRealSandboxIDToStop(const std::string &podSandboxID, bool &hostNetwork, std::string &name, + std::string &ns, std::string &realSandboxID, + std::map &stdAnnos, Errors &error) +{ + Errors statusErr; + + auto status = PodSandboxStatus(podSandboxID, statusErr); + if (statusErr.Empty()) { + if (status->linux().namespaces().has_options()) { + hostNetwork = status->linux().namespaces().options().host_network(); + } + if (status->has_metadata()) { + name = status->metadata().name(); + ns = status->metadata().namespace_(); + } + realSandboxID = status->id(); + CRIHelpers::ProtobufAnnoMapToStd(status->annotations(), stdAnnos); + } else { + if (CRIHelpers::IsContainerNotFoundError(statusErr.GetMessage())) { + WARN("Both sandbox container and checkpoint for id %s could not be found. " + "Proceed without further sandbox information.", + podSandboxID.c_str()); + } else { + error.Errorf("failed to get sandbox status: %s", statusErr.GetCMessage()); + return -1; + } + } + if (realSandboxID.empty()) { + realSandboxID = podSandboxID; + } + return 0; +} + +int CRIRuntimeServiceImpl::StopAllContainersInSandbox(const std::string &realSandboxID, Errors &error) +{ + int ret = 0; + container_list_request *list_request = nullptr; + container_list_response *list_response = nullptr; + + if (m_cb == nullptr || m_cb->container.list == nullptr) { + error.SetError("Unimplemented callback"); + return -1; + } + + // list all containers to stop + list_request = (container_list_request *)util_common_calloc_s(sizeof(container_list_request)); + if (list_request == nullptr) { + error.SetError("Out of memory"); + return -1; + } + list_request->all = true; + + list_request->filters = (defs_filters *)util_common_calloc_s(sizeof(defs_filters)); + if (list_request->filters == nullptr) { + error.SetError("Out of memory"); + ret = -1; + goto cleanup; + } + + // Add sandbox label + if (CRIHelpers::FiltersAddLabel(list_request->filters, + CRIHelpers::Constants::SANDBOX_ID_LABEL_KEY, realSandboxID) != 0) { + error.SetError("Failed to add label"); + ret = -1; + goto cleanup; + } + + ret = m_cb->container.list(list_request, &list_response); + if (ret != 0) { + if (list_response != nullptr && list_response->errmsg != nullptr) { + error.SetError(list_response->errmsg); + } else { + error.SetError("Failed to call list container callback"); + } + ret = -1; + goto cleanup; + } + + // Remove all containers in the sandbox. + for (size_t i = 0; i < list_response->containers_len; i++) { + Errors stopError; + StopContainer(list_response->containers[i]->id, 0, stopError); + if (stopError.NotEmpty() && !CRIHelpers::IsContainerNotFoundError(stopError.GetMessage())) { + ERROR("Error stop container: %s: %s", list_response->containers[i]->id, stopError.GetCMessage()); + error.SetError(stopError.GetMessage()); + ret = -1; + goto cleanup; + } + } +cleanup: + free_container_list_request(list_request); + free_container_list_response(list_response); + return ret; +} + +int CRIRuntimeServiceImpl::TearDownPodCniNetwork(const std::string &realSandboxID, std::vector &errlist, + std::map &stdAnnos, const std::string &ns, + const std::string &name, Errors &error) +{ + int ret = 0; + cri_pod_network_element **networks = nullptr; + container_inspect *inspect_data = InspectContainer(realSandboxID, error); + if (inspect_data == nullptr) { + return -1; + } + + google::protobuf::Map annotations; + CRIHelpers::ExtractAnnotations(inspect_data->config->annotations, annotations); + size_t len = 0; + + networks = CRIHelpers::GetNetworkPlaneFromPodAnno(annotations, &len, error); + if (error.NotEmpty()) { + ERROR("Couldn't get network plane from pod annotations: %s", error.GetCMessage()); + error.Errorf("Couldn't get network plane from pod annotations: %s", error.GetCMessage()); + ret = -1; + goto cleanup; + } + for (size_t i = 0; i < len; i++) { + if (networks[i] && networks[i]->name && networks[i]->interface && + strcmp(networks[i]->name, Network::DEFAULT_NETWORK_PLANE_NAME.c_str()) != 0) { + Errors tmpErr; + m_pluginManager->TearDownPod(ns, name, networks[i]->name, networks[i]->interface, inspect_data->id, + stdAnnos, tmpErr); + if (tmpErr.NotEmpty()) { + WARN("TearDownPod cni network failed: %s", tmpErr.GetCMessage()); + errlist.push_back(tmpErr.GetMessage()); + } + } + } +cleanup: + free_cri_pod_network(networks, len); + free_container_inspect(inspect_data); + return ret; +} + +int CRIRuntimeServiceImpl::ClearCniNetwork(const std::string &realSandboxID, bool hostNetwork, const std::string &ns, + const std::string &name, std::vector &errlist, + std::map &stdAnnos, Errors &error) +{ + Errors networkErr; + + bool ready = GetNetworkReady(realSandboxID, networkErr); + if (!hostNetwork && (ready || networkErr.NotEmpty())) { + Errors pluginErr; + m_pluginManager->TearDownPod(ns, name, Network::DEFAULT_NETWORK_PLANE_NAME, + Network::DEFAULT_NETWORK_INTERFACE_NAME, realSandboxID, stdAnnos, pluginErr); + if (pluginErr.NotEmpty()) { + WARN("TearDownPod cni network: %s failed: %s", Network::DEFAULT_NETWORK_PLANE_NAME.c_str(), + pluginErr.GetCMessage()); + errlist.push_back(pluginErr.GetMessage()); + } else { + INFO("TearDownPod cni network: %s success", Network::DEFAULT_NETWORK_PLANE_NAME.c_str()); + SetNetworkReady(realSandboxID, false, pluginErr); + if (pluginErr.NotEmpty()) { + WARN("set network ready: %s", pluginErr.GetCMessage()); + } + } + if (TearDownPodCniNetwork(realSandboxID, errlist, stdAnnos, ns, name, error)) { + return -1; + } + } + return 0; +} + +void CRIRuntimeServiceImpl::StopPodSandbox(const std::string &podSandboxID, Errors &error) +{ + std::string name, ns, realSandboxID; + bool hostNetwork = false; + Errors statusErr, networkErr; + std::map stdAnnos; + std::vector errlist; + + if (m_cb == nullptr || m_cb->container.list == nullptr || m_cb->container.stop == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + INFO("TearDownPod begin"); + if (podSandboxID.empty()) { + error.SetError("Invalid empty sandbox id."); + return; + } + if (GetRealSandboxIDToStop(podSandboxID, hostNetwork, name, ns, realSandboxID, stdAnnos, error)) { + return; + } + + if (StopAllContainersInSandbox(realSandboxID, error)) { + return; + } + + if (ClearCniNetwork(realSandboxID, hostNetwork, ns, name, errlist, stdAnnos, error)) { + return; + } + + StopContainerHelper(realSandboxID, error); + if (error.NotEmpty()) { + errlist.push_back(error.GetMessage()); + } + error.SetAggregate(errlist); +} + +void CRIRuntimeServiceImpl::StopContainerHelper(const std::string &containerID, Errors &error) +{ + int ret; + container_stop_request *request { nullptr }; + container_stop_response *response { nullptr }; + // Termination grace period + constexpr int32_t DefaultSandboxGracePeriod { 10 }; + + if (m_cb == nullptr || m_cb->container.stop == nullptr) { + error.SetError("Unimplemented callback"); + goto cleanup; + } + + request = (container_stop_request *)util_common_calloc_s(sizeof(container_stop_request)); + if (request == nullptr) { + error.SetError("Out of memory"); + goto cleanup; + } + request->id = util_strdup_s(containerID.c_str()); + request->timeout = DefaultSandboxGracePeriod; + + ret = m_cb->container.stop(request, &response); + if (ret != 0) { + std::string msg = (response != nullptr && response->errmsg != nullptr) ? response->errmsg : "internal"; + ERROR("Failed to stop sandbox %s: %s", containerID.c_str(), msg.c_str()); + error.SetError(msg); + } +cleanup: + free_container_stop_request(request); + free_container_stop_response(response); +} + +int CRIRuntimeServiceImpl::DoRemovePodSandbox(const std::string &realSandboxID, std::vector &errors) +{ + int ret = 0; + container_delete_request *remove_request { nullptr }; + container_delete_response *remove_response { nullptr }; + + if (m_cb == nullptr || m_cb->container.remove == nullptr) { + errors.push_back("Unimplemented callback"); + return -1; + } + + remove_request = (container_delete_request *)util_common_calloc_s(sizeof(container_delete_request)); + if (remove_request == nullptr) { + errors.push_back("Out of memory"); + return -1; + } + remove_request->id = util_strdup_s(realSandboxID.c_str()); + remove_request->force = true; + + ret = m_cb->container.remove(remove_request, &remove_response); + if (ret == 0 || (remove_response != nullptr && remove_response->errmsg != nullptr && + CRIHelpers::IsContainerNotFoundError(remove_response->errmsg))) { + // Only clear network ready when the sandbox has actually been + // removed from docker or doesn't exist + ClearNetworkReady(realSandboxID); + } else { + if (remove_response != nullptr && remove_response->errmsg) { + errors.push_back(remove_response->errmsg); + } else { + errors.push_back("Failed to call remove container callback"); + } + } + free_container_delete_request(remove_request); + free_container_delete_response(remove_response); + return ret; +} +int CRIRuntimeServiceImpl::RemoveAllContainersInSandbox(const std::string &realSandboxID, + std::vector &errors) +{ + int ret = 0; + container_list_request *list_request { nullptr }; + container_list_response *list_response { nullptr }; + + if (m_cb == nullptr || m_cb->container.list == nullptr) { + errors.push_back("Unimplemented callback"); + return -1; + } + + // list all containers to stop + list_request = (container_list_request *)util_common_calloc_s(sizeof(container_list_request)); + if (list_request == nullptr) { + errors.push_back("Out of memory"); + return -1; + } + list_request->all = true; + + list_request->filters = (defs_filters *)util_common_calloc_s(sizeof(defs_filters)); + if (list_request->filters == nullptr) { + errors.push_back("Out of memory"); + ret = -1; + goto cleanup; + } + + // Add sandbox label + if (CRIHelpers::FiltersAddLabel(list_request->filters, CRIHelpers::Constants::SANDBOX_ID_LABEL_KEY, + realSandboxID) != 0) { + errors.push_back("Faild to add label"); + ret = -1; + goto cleanup; + } + + ret = m_cb->container.list(list_request, &list_response); + if (ret != 0) { + if (list_response != nullptr && list_response->errmsg != nullptr) { + errors.push_back(list_response->errmsg); + } else { + errors.push_back("Failed to call list container callback"); + } + } + + // Remove all containers in the sandbox. + for (size_t i = 0; i < list_response->containers_len; i++) { + Errors rmError; + RemoveContainer(list_response->containers[i]->id, rmError); + if (rmError.NotEmpty() && !CRIHelpers::IsContainerNotFoundError(rmError.GetMessage())) { + ERROR("Error remove container: %s: %s", list_response->containers[i]->id, rmError.GetCMessage()); + errors.push_back(rmError.GetMessage()); + } + } +cleanup: + free_container_list_request(list_request); + free_container_list_response(list_response); + return ret; +} + +void CRIRuntimeServiceImpl::RemovePodSandbox(const std::string &podSandboxID, Errors &error) +{ + std::vector errors; + Errors localErr; + std::string realSandboxID; + + if (podSandboxID.empty()) { + errors.push_back("Invalid empty sandbox id."); + goto cleanup; + } + realSandboxID = GetRealContainerOrSandboxID(podSandboxID, true, error); + if (error.NotEmpty()) { + if (CRIHelpers::IsContainerNotFoundError(error.GetMessage())) { + error.Clear(); + realSandboxID = podSandboxID; + } else { + ERROR("Failed to find sandbox id %s: %s", podSandboxID.c_str(), error.GetCMessage()); + errors.push_back("Failed to find sandbox id " + podSandboxID + ": " + error.GetMessage()); + goto cleanup; + } + } + + if (RemoveAllContainersInSandbox(realSandboxID, errors)) { + goto cleanup; + } + + if (DoRemovePodSandbox(realSandboxID, errors)) { + goto cleanup; + } + +cleanup: + error.SetAggregate(errors); +} + +bool CRIRuntimeServiceImpl::IsDefaultNetworkPlane(cri_pod_network_element *network) +{ + if (network && network->name && network->interface && + strcmp(network->name, Network::DEFAULT_NETWORK_PLANE_NAME.c_str()) != 0) { + return true; + } + + return false; +} + +void CRIRuntimeServiceImpl::SetSandboxStatusNetwork(container_inspect *inspect, const std::string &podSandboxID, + std::unique_ptr &podStatus, + Errors &error) +{ + runtime::PodSandboxNetworkStatus *network = podStatus->add_networks(); + std::string ipAddress = GetIP(podSandboxID, inspect, "", error); + if (error.NotEmpty()) { + WARN("get default ip failed: %s", error.GetCMessage()); + error.Clear(); + } + INFO("get default net ip: %s", ipAddress.c_str()); + network->set_name(Network::DEFAULT_NETWORK_INTERFACE_NAME); + network->set_network(Network::DEFAULT_NETWORK_PLANE_NAME); + network->set_ip(ipAddress); + if (podStatus->annotations_size() > 0) { + size_t len = 0; + auto networks = CRIHelpers::GetNetworkPlaneFromPodAnno(*podStatus->mutable_annotations(), &len, error); + if (error.NotEmpty()) { + ERROR("Couldn't get network plane from pod annotations: %s", error.GetCMessage()); + return; + } + for (size_t i = 0; i < len; i++) { + if (IsDefaultNetworkPlane(networks[i])) { + network = podStatus->add_networks(); + std::string ip = GetIP(podSandboxID, inspect, networks[i]->interface, error); + network->set_name(networks[i]->interface); + network->set_network(networks[i]->name); + network->set_ip(ip); + INFO("get %s net ip: %s", networks[i]->name, ip.c_str()); + } + free_cri_pod_network_element(networks[i]); + networks[i] = nullptr; + } + free(networks); + } +} + +void CRIRuntimeServiceImpl::PodSandboxStatusToGRPC(container_inspect *inspect, const std::string &podSandboxID, + std::unique_ptr &podStatus, Errors &error) +{ + int64_t createdAt {}; + runtime::NamespaceOption *options { nullptr }; + + if (inspect->id) { + podStatus->set_id(inspect->id); + } + + GetContainerTimeStamps(inspect, &createdAt, nullptr, nullptr, error); + if (error.NotEmpty()) { + return; + } + podStatus->set_created_at(createdAt); + + if (inspect->state && inspect->state->running) { + podStatus->set_state(runtime::SANDBOX_READY); + } else { + podStatus->set_state(runtime::SANDBOX_NOTREADY); + } + + if (inspect->config) { + CRIHelpers::ExtractLabels(inspect->config->labels, *podStatus->mutable_labels()); + CRIHelpers::ExtractAnnotations(inspect->config->annotations, *podStatus->mutable_annotations()); + } + + options = podStatus->mutable_linux()->mutable_namespaces()->mutable_options(); + options->set_host_network(SharesHostNetwork(inspect)); + options->set_host_pid(SharesHostPid(inspect)); + options->set_host_ipc(SharesHostIpc(inspect)); + + // add networks + // get default network status + SetSandboxStatusNetwork(inspect, podSandboxID, podStatus, error); + if (error.NotEmpty()) { + return; + } + + if (inspect->name) { + CRINaming::ParseSandboxName(inspect->name, *podStatus->mutable_metadata(), error); + if (error.NotEmpty()) { + return; + } + } +} + +std::string CRIRuntimeServiceImpl::GetIPFromPlugin(container_inspect *inspect, std::string networkInterface, + Errors &error) +{ + if (inspect == nullptr || inspect->id == nullptr || inspect->name == nullptr) { + error.SetError("Empty arguments"); + return ""; + } + + runtime::PodSandboxMetadata metadata; + CRINaming::ParseSandboxName(inspect->name, metadata, error); + if (error.NotEmpty()) { + return ""; + } + std::string cid = inspect->id; + Network::PodNetworkStatus status; + if (networkInterface == "") { + m_pluginManager->GetPodNetworkStatus(metadata.namespace_(), metadata.name(), + Network::DEFAULT_NETWORK_INTERFACE_NAME, cid, status, error); + } else { + m_pluginManager->GetPodNetworkStatus(metadata.namespace_(), metadata.name(), networkInterface, cid, status, + error); + } + if (error.NotEmpty()) { + return ""; + } + + return status.GetIP(); +} + +std::string CRIRuntimeServiceImpl::GetIP(const std::string &podSandboxID, container_inspect *inspect, + const std::string &networkInterface, Errors &error) +{ + if (inspect == nullptr || inspect->network_settings == nullptr) { + return ""; + } + if (SharesHostNetwork(inspect)) { + // For sandboxes using host network, the shim is not responsible for reporting the IP. + return ""; + } + + bool ready = GetNetworkReady(podSandboxID, error); + if (error.Empty() && !ready) { + WARN("Network %s do not ready", podSandboxID.c_str()); + return ""; + } + + error.Clear(); + auto ip = GetIPFromPlugin(inspect, networkInterface, error); + if (error.Empty()) { + return ip; + } + if (inspect->network_settings->ip_address) { + WARN("Use container inspect ip info: %s", error.GetCMessage()); + error.Clear(); + return inspect->network_settings->ip_address; + } + + WARN("Failed to read pod IP from plugin/docker: %s", error.GetCMessage()); + return ""; +} + +std::unique_ptr CRIRuntimeServiceImpl::PodSandboxStatus(const std::string &podSandboxID, + Errors &error) +{ + container_inspect *inspect { nullptr }; + std::unique_ptr podStatus(new runtime::PodSandboxStatus); + + if (podSandboxID.empty()) { + error.SetError("Empty pod sandbox id"); + return nullptr; + } + std::string realSandboxID = GetRealContainerOrSandboxID(podSandboxID, true, error); + if (error.NotEmpty()) { + ERROR("Failed to find sandbox id %s: %s", podSandboxID.c_str(), error.GetCMessage()); + error.Errorf("Failed to find sandbox id %s: %s", podSandboxID.c_str(), error.GetCMessage()); + return nullptr; + } + + inspect = InspectContainer(realSandboxID, error); + if (error.NotEmpty()) { + return nullptr; + } + + PodSandboxStatusToGRPC(inspect, realSandboxID, podStatus, error); + + free_container_inspect(inspect); + return podStatus; +} + +void CRIRuntimeServiceImpl::ListPodSandboxToGRPC(container_list_response *response, + std::vector> *pods, + bool filterOutReadySandboxes, Errors &error) +{ + for (size_t i = 0; i < response->containers_len; i++) { + std::unique_ptr pod(new runtime::PodSandbox); + + if (response->containers[i]->id) { + pod->set_id(response->containers[i]->id); + } + if (response->containers[i]->status == CONTAINER_STATUS_RUNNING) { + pod->set_state(runtime::SANDBOX_READY); + } else { + pod->set_state(runtime::SANDBOX_NOTREADY); + } + pod->set_created_at(response->containers[i]->created); + + CRINaming::ParseSandboxName(response->containers[i]->name, *pod->mutable_metadata(), error); + + CRIHelpers::ExtractLabels(response->containers[i]->labels, *pod->mutable_labels()); + + CRIHelpers::ExtractAnnotations(response->containers[i]->annotations, *pod->mutable_annotations()); + + if (filterOutReadySandboxes && pod->state() == runtime::SANDBOX_READY) { + continue; + } + + pods->push_back(std::move(pod)); + } +} + +void CRIRuntimeServiceImpl::ListPodSandboxFromGRPC(const runtime::PodSandboxFilter *filter, + container_list_request **request, bool *filterOutReadySandboxes, + Errors &error) +{ + *request = (container_list_request *)util_common_calloc_s(sizeof(container_list_request)); + if (*request == nullptr) { + error.SetError("Out of memory"); + return; + } + (*request)->filters = (defs_filters *)util_common_calloc_s(sizeof(defs_filters)); + if ((*request)->filters == nullptr) { + error.SetError("Out of memory"); + return; + } + (*request)->all = true; + + if (CRIHelpers::FiltersAddLabel((*request)->filters, CRIHelpers::Constants::CONTAINER_TYPE_LABEL_KEY, + CRIHelpers::Constants::CONTAINER_TYPE_LABEL_SANDBOX) != 0) { + error.SetError("Failed to add label"); + return; + } + + if (filter != nullptr) { + if (!filter->id().empty()) { + if (CRIHelpers::FiltersAdd((*request)->filters, "id", filter->id()) != 0) { + error.SetError("Failed to add label"); + return; + } + } + if (filter->has_state()) { + if (filter->state().state() == runtime::SANDBOX_READY) { + (*request)->all = false; + } else { + *filterOutReadySandboxes = true; + } + } + + // Add some label + for (auto &iter : filter->label_selector()) { + if (CRIHelpers::FiltersAddLabel((*request)->filters, iter.first, iter.second) != 0) { + error.SetError("Failed to add label"); + return; + } + } + } +} + +void CRIRuntimeServiceImpl::ListPodSandbox(const runtime::PodSandboxFilter *filter, + std::vector> *pods, Errors &error) +{ + int ret; + container_list_request *request { nullptr }; + container_list_response *response { nullptr }; + bool filterOutReadySandboxes { false }; + + if (m_cb == nullptr || m_cb->container.list == nullptr) { + error.SetError("Unimplemented callback"); + return; + } + + ListPodSandboxFromGRPC(filter, &request, &filterOutReadySandboxes, error); + if (error.NotEmpty()) { + goto cleanup; + } + + ret = m_cb->container.list(request, &response); + if (ret != 0) { + if (response != nullptr && response->errmsg) { + error.SetError(response->errmsg); + } else { + error.SetError("Failed to call start container callback"); + } + goto cleanup; + } + ListPodSandboxToGRPC(response, pods, filterOutReadySandboxes, error); + +cleanup: + free_container_list_request(request); + free_container_list_response(response); +} + +void CRIRuntimeServiceImpl::PortForward(const runtime::PortForwardRequest &req, runtime::PortForwardResponse *resp, + Errors &error) +{ +} + +bool CRIRuntimeServiceImpl::GetNetworkReady(const std::string &podSandboxID, Errors &error) +{ + bool ready { false }; + + if (pthread_mutex_lock(&m_networkReadyLock) != 0) { + error.SetError("lock failed"); + return ready; + } + auto iter = m_networkReady.find(podSandboxID); + if (iter != m_networkReady.end()) { + ready = iter->second; + } else { + error.Errorf("Do not find network: %s", podSandboxID.c_str()); + } + + pthread_mutex_unlock(&m_networkReadyLock); + return ready; +} + +void CRIRuntimeServiceImpl::SetNetworkReady(const std::string &podSandboxID, bool ready, Errors &error) +{ + if (pthread_mutex_lock(&m_networkReadyLock) != 0) { + error.SetError("lock failed"); + return; + } + + m_networkReady[podSandboxID] = ready; + + pthread_mutex_unlock(&m_networkReadyLock); +} + +void CRIRuntimeServiceImpl::ClearNetworkReady(const std::string &podSandboxID) +{ + if (pthread_mutex_lock(&m_networkReadyLock) != 0) { + return; + } + + auto iter = m_networkReady.find(podSandboxID); + if (iter != m_networkReady.end()) { + m_networkReady.erase(iter); + } + + pthread_mutex_unlock(&m_networkReadyLock); +} + diff --git a/src/services/cri/cri_sandbox.h b/src/services/cri/cri_sandbox.h new file mode 100644 index 0000000..8f6f73c --- /dev/null +++ b/src/services/cri/cri_sandbox.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide sandbox manager function definition + *********************************************************************************/ +#ifndef _CRI_SANDBOX_MANAGER_IMPL_H_ +#define _CRI_SANDBOX_MANAGER_IMPL_H_ + +#include "cri_services.h" +#include "callback.h" + +class CRISandboxManagerImpl : public cri::PodSandboxManager { +public: + CRISandboxManagerImpl() = default; + CRISandboxManagerImpl(const CRISandboxManagerImpl &) = delete; + CRISandboxManagerImpl &operator=(const CRISandboxManagerImpl &) = delete; + + virtual ~CRISandboxManagerImpl() = default; +}; + +#endif /* _CRI_SANDBOX_MANAGER_IMPL_H_ */ diff --git a/src/services/cri/cri_security_context.cc b/src/services/cri/cri_security_context.cc new file mode 100644 index 0000000..d742778 --- /dev/null +++ b/src/services/cri/cri_security_context.cc @@ -0,0 +1,213 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri security context functions + *********************************************************************************/ +#include "cri_security_context.h" +#include +#include "cri_runtime_service.h" +#include "utils.h" +#include "log.h" + +namespace CRISecurity { +static void ModifyContainerConfig(const runtime::LinuxContainerSecurityContext &sc, container_custom_config *config) +{ + if (sc.has_run_as_user()) { + free(config->user); + config->user = util_strdup_s(std::to_string(sc.run_as_user().value()).c_str()); + } + if (!sc.run_as_username().empty()) { + free(config->user); + config->user = util_strdup_s(sc.run_as_username().c_str()); + } +} + +static void ModifyHostConfig(const runtime::LinuxContainerSecurityContext &sc, host_config *hostConfig, Errors &error) +{ + hostConfig->privileged = sc.privileged(); + hostConfig->readonly_rootfs = sc.readonly_rootfs(); + if (sc.has_capabilities()) { + const google::protobuf::RepeatedPtrField &capAdd = sc.capabilities().add_capabilities(); + if (capAdd.size() > 0) { + if (static_cast(capAdd.size()) > SIZE_MAX / sizeof(char *)) { + error.SetError("Invalid capability add size"); + return; + } + hostConfig->cap_add = (char **)util_common_calloc_s(sizeof(char *) * capAdd.size()); + if (hostConfig->cap_add == nullptr) { + error.SetError("Out of memory"); + return; + } + for (int i {}; i < capAdd.size(); i++) { + hostConfig->cap_add[i] = util_strdup_s(capAdd[i].c_str()); + hostConfig->cap_add_len++; + } + } + const google::protobuf::RepeatedPtrField &capDrop = sc.capabilities().drop_capabilities(); + if (capDrop.size() > 0) { + if (static_cast(capDrop.size()) > SIZE_MAX / sizeof(char *)) { + error.SetError("Invalid capability drop size"); + return; + } + hostConfig->cap_drop = (char **)util_common_calloc_s(sizeof(char *) * capDrop.size()); + if (hostConfig->cap_drop == nullptr) { + error.SetError("Out of memory"); + return; + } + for (int i = 0; i < capDrop.size(); i++) { + hostConfig->cap_drop[i] = util_strdup_s(capDrop[i].c_str()); + hostConfig->cap_drop_len++; + } + } + } + + // note: Apply apparmor options, selinux options, noNewPrivilege + if (sc.no_new_privs()) { + char **tmp_security_opt { nullptr }; + + if (hostConfig->security_opt_len > (SIZE_MAX / sizeof(char *)) - 1) { + error.Errorf("Out of memory"); + return; + } + + size_t oldSize = hostConfig->security_opt_len * sizeof(char *); + size_t newSize = oldSize + sizeof(char *); + int ret = mem_realloc((void **)(&tmp_security_opt), newSize, (void *)hostConfig->security_opt, oldSize); + if (ret != 0) { + error.Errorf("Out of memory"); + return; + } + hostConfig->security_opt = tmp_security_opt; + hostConfig->security_opt[hostConfig->security_opt_len++] = util_strdup_s("no-new-privileges"); + } + + if (sc.supplemental_groups().size() > 0) { + const google::protobuf::RepeatedField &groups = sc.supplemental_groups(); + if (groups.size() > 0) { + if (static_cast(groups.size()) > SIZE_MAX / sizeof(char *)) { + error.SetError("Invalid group size"); + return; + } + hostConfig->group_add = (char **)util_common_calloc_s(sizeof(char *) * groups.size()); + if (hostConfig->group_add == nullptr) { + error.SetError("Out of memory"); + return; + } + for (int i = 0; i < groups.size(); i++) { + hostConfig->group_add[i] = util_strdup_s(std::to_string(groups[i]).c_str()); + hostConfig->group_add_len++; + } + } + } +} + +static void ModifyCommonNamespaceOptions(const runtime::NamespaceOption &nsOpts, host_config *hostConfig) +{ + if (nsOpts.host_pid()) { + free(hostConfig->pid_mode); + hostConfig->pid_mode = util_strdup_s(CRIRuntimeService::Constants::namespaceModeHost.c_str()); + } + if (nsOpts.host_ipc()) { + free(hostConfig->ipc_mode); + hostConfig->ipc_mode = util_strdup_s(CRIRuntimeService::Constants::namespaceModeHost.c_str()); + } +} + +static void ModifyHostNetworkOptionForContainer(bool hostNetwork, const std::string &podSandboxID, + host_config *hostConfig) +{ + std::string sandboxNSMode = "container:" + podSandboxID; + + free(hostConfig->network_mode); + hostConfig->network_mode = util_strdup_s(sandboxNSMode.c_str()); + free(hostConfig->ipc_mode); + hostConfig->ipc_mode = util_strdup_s(sandboxNSMode.c_str()); + if (hostNetwork) { + free(hostConfig->uts_mode); + hostConfig->uts_mode = util_strdup_s(CRIRuntimeService::Constants::namespaceModeHost.c_str()); + } +} + +static void ModifyHostNetworkOptionForSandbox(bool hostNetwork, host_config *hostConfig) +{ + if (hostNetwork) { + hostConfig->network_mode = util_strdup_s(CRIRuntimeService::Constants::namespaceModeHost.c_str()); + } + // Note: default networkMode is not supported +} + +static void ModifyContainerNamespaceOptions(const runtime::NamespaceOption &nsOpts, const std::string &podSandboxID, + host_config *hostConfig, Errors &error) +{ + std::string pidMode = "container:" + podSandboxID; + hostConfig->pid_mode = util_strdup_s(pidMode.c_str()); + + /* set common Namespace options */ + ModifyCommonNamespaceOptions(nsOpts, hostConfig); + /* modify host network option for container */ + ModifyHostNetworkOptionForContainer(nsOpts.host_network(), podSandboxID, hostConfig); +} + +static void ModifySandboxNamespaceOptions(const runtime::NamespaceOption &nsOpts, host_config *hostConfig, + Errors &error) +{ + /* set common Namespace options */ + ModifyCommonNamespaceOptions(nsOpts, hostConfig); + /* modify host network option for container */ + ModifyHostNetworkOptionForSandbox(nsOpts.host_network(), hostConfig); +} + +void ApplySandboxSecurityContext(const runtime::LinuxPodSandboxConfig &lc, container_custom_config *config, + host_config *hc, Errors &error) +{ + std::unique_ptr sc(new runtime::LinuxContainerSecurityContext); + if (lc.has_security_context()) { + const runtime::LinuxSandboxSecurityContext &old = lc.security_context(); + if (old.has_run_as_user()) { + *sc->mutable_run_as_user() = old.run_as_user(); + } + if (old.has_namespace_options()) { + *sc->mutable_namespace_options() = old.namespace_options(); + } + if (old.has_selinux_options()) { + *sc->mutable_selinux_options() = old.selinux_options(); + } + *sc->mutable_supplemental_groups() = old.supplemental_groups(); + sc->set_readonly_rootfs(old.readonly_rootfs()); + } + ModifyContainerConfig(*sc, config); + ModifyHostConfig(*sc, hc, error); + if (error.NotEmpty()) { + return; + } + ModifySandboxNamespaceOptions(sc->namespace_options(), hc, error); +} + +void ApplyContainerSecurityContext(const runtime::LinuxContainerConfig &lc, const std::string &podSandboxID, + container_custom_config *config, host_config *hc, Errors &error) +{ + if (lc.has_security_context()) { + const runtime::LinuxContainerSecurityContext &sc = lc.security_context(); + ModifyContainerConfig(sc, config); + ModifyHostConfig(sc, hc, error); + if (error.NotEmpty()) { + return; + } + } + ModifyContainerNamespaceOptions(lc.security_context().namespace_options(), podSandboxID, hc, error); + if (error.NotEmpty()) { + ERROR("Modify namespace options failed: %s", error.GetCMessage()); + return; + } +} + +} // namespace CRISecurity diff --git a/src/services/cri/cri_security_context.h b/src/services/cri/cri_security_context.h new file mode 100644 index 0000000..1d44a18 --- /dev/null +++ b/src/services/cri/cri_security_context.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri security context function definition + *********************************************************************************/ +#ifndef _CRI_SECURITY_CONTEXT_H_ +#define _CRI_SECURITY_CONTEXT_H_ + +#include +#include "api.pb.h" +#include "errors.h" +#include "container_custom_config.h" +#include "host_config.h" + +namespace CRISecurity { +void ApplySandboxSecurityContext(const runtime::LinuxPodSandboxConfig &lc, container_custom_config *config, + host_config *hc, Errors &error); + +void ApplyContainerSecurityContext(const runtime::LinuxContainerConfig &lc, const std::string &podSandboxID, + container_custom_config *config, host_config *hc, Errors &errorr); + +} // namespace CRISecurity + +#endif /* _CRI_SECURITY_CONTEXT_H_ */ diff --git a/src/services/cri/cri_services.h b/src/services/cri/cri_services.h new file mode 100644 index 0000000..e783668 --- /dev/null +++ b/src/services/cri/cri_services.h @@ -0,0 +1,105 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide cri service function definition + *********************************************************************************/ +#ifndef _CRI_SERVICES_H_ +#define _CRI_SERVICES_H_ + +#include +#include +#include +#include + +#include "api.pb.h" +#include "errors.h" + +namespace cri { +class RuntimeVersioner { +public: + virtual void Version(const std::string &apiVersion, runtime::VersionResponse *versionResponse, Errors &error) = 0; +}; + +class ContainerManager { +public: + virtual std::string CreateContainer(const std::string &podSandboxID, + const runtime::ContainerConfig &containerConfig, + const runtime::PodSandboxConfig &podSandboxConfig, Errors &error) = 0; + + virtual void StartContainer(const std::string &containerID, Errors &error) = 0; + + virtual void StopContainer(const std::string &containerID, int64_t timeout, Errors &error) = 0; + + virtual void RemoveContainer(const std::string &containerID, Errors &error) = 0; + + virtual void ListContainers(const runtime::ContainerFilter *filter, + std::vector> *containers, Errors &error) = 0; + + virtual void ListContainerStats(const runtime::ContainerStatsFilter *filter, + std::vector> *containerstats, + Errors &error) = 0; + + virtual std::unique_ptr ContainerStatus(const std::string &containerID, + Errors &error) = 0; + + virtual void UpdateContainerResources(const std::string &containerID, + const runtime::LinuxContainerResources &resources, Errors &error) = 0; + + virtual void ExecSync(const std::string &containerID, const google::protobuf::RepeatedPtrField &cmd, + int64_t timeout, runtime::ExecSyncResponse *reply, Errors &error) = 0; + + virtual void Exec(const runtime::ExecRequest &req, runtime::ExecResponse *resp, Errors &error) = 0; + + virtual void Attach(const runtime::AttachRequest &req, runtime::AttachResponse *resp, Errors &error) = 0; +}; + +class PodSandboxManager { +public: + virtual std::string RunPodSandbox(const runtime::PodSandboxConfig &config, Errors &error) = 0; + + virtual void StopPodSandbox(const std::string &podSandboxID, Errors &error) = 0; + + virtual void RemovePodSandbox(const std::string &podSandboxID, Errors &error) = 0; + + virtual std::unique_ptr PodSandboxStatus(const std::string &podSandboxID, + Errors &error) = 0; + + virtual void ListPodSandbox(const runtime::PodSandboxFilter *filter, + std::vector> *pods, Errors &error) = 0; + + virtual void PortForward(const runtime::PortForwardRequest &req, runtime::PortForwardResponse *resp, + Errors &error) = 0; +}; + +class RuntimeManager { +public: + virtual void UpdateRuntimeConfig(const runtime::RuntimeConfig &config, Errors &error) = 0; + + virtual std::unique_ptr Status(Errors &error) = 0; +}; + +class ImageManagerService { +public: + virtual void ListImages(const runtime::ImageFilter &filter, std::vector> *images, + Errors &error) = 0; + + virtual std::unique_ptr ImageStatus(const runtime::ImageSpec &image, Errors &error) = 0; + + virtual std::string PullImage(const runtime::ImageSpec &image, const runtime::AuthConfig &auth, Errors &error) = 0; + + virtual void RemoveImage(const runtime::ImageSpec &image, Errors &error) = 0; + + virtual void ImageFsInfo(std::vector> *usages, Errors &error) = 0; +}; + +} // namespace cri +#endif /* _CRI_SERVICES_H_ */ diff --git a/src/services/cri/errors.cc b/src/services/cri/errors.cc new file mode 100644 index 0000000..8373565 --- /dev/null +++ b/src/services/cri/errors.cc @@ -0,0 +1,128 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide err functions + ********************************************************************************/ + +#include "errors.h" + +#include + +#include "securec.h" + +Errors::Errors() +{ + m_message.clear(); + m_code = 0; +} + +Errors &Errors::operator=(const Errors &other) +{ + if (&other == this) { + return *this; + } + + m_message = other.m_message; + m_code = other.m_code; + return *this; +} + +Errors::~Errors() +{ + Clear(); +} + +void Errors::Clear() +{ + m_message.clear(); + m_code = 0; +} + +std::string &Errors::GetMessage() +{ + return m_message; +} + +const char *Errors::GetCMessage() const +{ + return m_message.empty() ? "" : m_message.c_str(); +} + +int Errors::GetCode() const +{ + return m_code; +} + +bool Errors::Empty() const +{ + return (m_message.empty() && (m_code == 0)); +} + +bool Errors::NotEmpty() const +{ + return !Empty(); +} + +void Errors::SetError(const char *msg) +{ + m_message = msg ? msg : ""; +} + +void Errors::SetError(const std::string &msg) +{ + m_message = msg; +} + +void Errors::AppendError(const std::string &msg) +{ + m_message.append(msg); +} + +void Errors::SetAggregate(const std::vector &msgs) +{ + std::string result; + size_t size = msgs.size(); + + if (!size) { + return; + } + + if (size == 1) { + m_message = msgs[0]; + return; + } + + result = "[" + msgs[0]; + for (size_t i = 1; i < size; i++) { + result += " " + msgs[i]; + } + result += "]"; + m_message = result; +} + +void Errors::Errorf(const char *fmt, ...) +{ + int ret { 0 }; + char errbuf[BUFSIZ + 1] { 0 }; + va_list argp; + + va_start(argp, fmt); + + ret = vsprintf_s(errbuf, BUFSIZ, fmt, argp); + va_end(argp); + if (ret < 0) { + m_message = "Error message is too long"; + return; + } + + m_message = errbuf; +} diff --git a/src/services/cri/errors.h b/src/services/cri/errors.h new file mode 100644 index 0000000..3946586 --- /dev/null +++ b/src/services/cri/errors.h @@ -0,0 +1,47 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide err function definition + *********************************************************************************/ +#ifndef _ERRORS_H_ +#define _ERRORS_H_ + +#include +#include + +class Errors { +public: + Errors(); + Errors(const Errors ©) : m_message(copy.m_message), m_code(copy.m_code) {} + Errors &operator=(const Errors &); + virtual ~Errors(); + + void Clear(); + std::string &GetMessage(); + const char *GetCMessage() const; + int GetCode() const; + bool Empty() const; + bool NotEmpty() const; + + void AppendError(const std::string &msg); + void SetError(const std::string &msg); + void SetError(const char *msg); + void Errorf(const char *fmt, ...); + + void SetAggregate(const std::vector &msgs); + +private: + std::string m_message; + int m_code; +}; + +#endif /* _ERRORS_H_ */ diff --git a/src/services/cri/naming.cc b/src/services/cri/naming.cc new file mode 100644 index 0000000..8db5a63 --- /dev/null +++ b/src/services/cri/naming.cc @@ -0,0 +1,124 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide naming functions + *********************************************************************************/ +#include "naming.h" + +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" + +namespace CRINaming { +static int parseName(const std::string &name, std::vector &items, unsigned int &attempt, Errors &err) +{ + std::istringstream f(name); + std::string part; + + while (getline(f, part, CRIRuntimeService::Constants::nameDelimiterChar)) { + items.push_back(part); + } + + if (items.size() != 6) { + err.Errorf("failed to parse the sandbox name: %s", name.c_str()); + return -1; + } + + if (items[0] != CRIRuntimeService::Constants::kubePrefix) { + err.Errorf("container is not managed by kubernetes: %s", name.c_str()); + return -1; + } + + if (util_safe_uint(items[5].c_str(), &attempt)) { + err.Errorf("failed to parse the sandbox name %s: %s", name.c_str(), strerror(errno)); + return -1; + } + + return 0; +} + +std::string MakeSandboxName(const runtime::PodSandboxMetadata &metadata) +{ + std::string sname; + + sname.append(CRIRuntimeService::Constants::kubePrefix); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(CRIRuntimeService::Constants::sandboxContainerName); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(metadata.name()); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(metadata.namespace_()); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(metadata.uid()); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(std::to_string(metadata.attempt())); + + return sname; +} + +void ParseSandboxName(const std::string &name, runtime::PodSandboxMetadata &metadata, Errors &err) +{ + int ret {}; + std::vector items; + unsigned int attempt; + + ret = parseName(name, items, attempt, err); + if (ret != 0) { + return; + } + + metadata.set_name(items[2]); + metadata.set_namespace_(items[3]); + metadata.set_uid(items[4]); + metadata.set_attempt(attempt); +} + +std::string MakeContainerName(const runtime::PodSandboxConfig &s, const runtime::ContainerConfig &c) +{ + std::string sname; + + sname.append(CRIRuntimeService::Constants::kubePrefix); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(c.metadata().name()); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(s.metadata().name()); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(s.metadata().namespace_()); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(s.metadata().uid()); + sname.append(CRIRuntimeService::Constants::nameDelimiter); + sname.append(std::to_string(c.metadata().attempt())); + + return sname; +} + +void ParseContainerName(const std::string &name, runtime::ContainerMetadata *metadata, Errors &err) +{ + int ret {}; + std::vector items; + unsigned int attempt; + + ret = parseName(name, items, attempt, err); + if (ret != 0) { + return; + } + + metadata->set_name(items[1]); + metadata->set_attempt(attempt); +} + +} // namespace CRINaming diff --git a/src/services/cri/naming.h b/src/services/cri/naming.h new file mode 100644 index 0000000..5a596f0 --- /dev/null +++ b/src/services/cri/naming.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide naming function definition + *********************************************************************************/ + +#ifndef _CRI_NAMING_H_ +#define _CRI_NAMING_H_ + +#include "cri_runtime_service.h" +#include + +namespace CRINaming { +std::string MakeSandboxName(const runtime::PodSandboxMetadata &metadata); + +std::string MakeContainerName(const runtime::PodSandboxConfig &s, const runtime::ContainerConfig &c); + +void ParseSandboxName(const std::string &name, runtime::PodSandboxMetadata &metadata, Errors &err); + +void ParseContainerName(const std::string &name, runtime::ContainerMetadata *metadata, Errors &err); +} // namespace CRINaming + +#endif /* _CRI_RUNTIME_SERVICES_IMPL_H_ */ diff --git a/src/services/cri/network_plugin.cc b/src/services/cri/network_plugin.cc new file mode 100644 index 0000000..f3c373d --- /dev/null +++ b/src/services/cri/network_plugin.cc @@ -0,0 +1,521 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide net plugin functions + *********************************************************************************/ + +#include "network_plugin.h" +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "sysctl_tools.h" +#include "cri_runtime_service.h" + +namespace Network { +static void run_modprobe(void *args) +{ + execlp("modprobe", "br-netfilter", nullptr); +} + +static void runGetIP(void *cmdArgs) +{ + constexpr size_t ARGS_NUM { 14 }; + constexpr size_t CMD_ARGS_NUM { 4 }; + char *args[ARGS_NUM]; + char **tmpArgs = reinterpret_cast(cmdArgs); + + if (util_array_len(tmpArgs) != CMD_ARGS_NUM) { + COMMAND_ERROR("need four args"); + exit(1); + } + + if (asprintf(&(args[1]), "--net=%s", tmpArgs[1]) < 0) { + COMMAND_ERROR("Out of memory"); + exit(1); + } + + args[0] = util_strdup_s(tmpArgs[0]); + args[2] = util_strdup_s("-F"); + args[3] = util_strdup_s("--"); + args[4] = util_strdup_s("ip"); + args[5] = util_strdup_s("-o"); + args[6] = util_strdup_s(tmpArgs[3]); + args[7] = util_strdup_s("addr"); + args[8] = util_strdup_s("show"); + args[9] = util_strdup_s("dev"); + args[10] = util_strdup_s(tmpArgs[2]); + args[11] = util_strdup_s("scope"); + args[12] = util_strdup_s("global"); + args[13] = nullptr; + execvp(tmpArgs[0], args); +} + +static std::string GetOnePodIP(std::string nsenterPath, std::string netnsPath, std::string interfaceName, + std::string addrType, Errors &error) +{ + char *stderr_str { nullptr }; + char *stdout_str { nullptr }; + char *strErr { nullptr }; + char **lines { nullptr }; + char **fields { nullptr }; + struct ipnet *ipnet_val { + nullptr + }; + char **args { nullptr }; + std::string result { "" }; + char *cIP { nullptr }; + + args = (char **)util_common_calloc_s(sizeof(char *) * 5); + if (args == nullptr) { + error.SetError("Out of memory"); + return result; + } + + args[0] = util_strdup_s(nsenterPath.c_str()); + args[1] = util_strdup_s(netnsPath.c_str()); + args[2] = util_strdup_s(interfaceName.c_str()); + args[3] = util_strdup_s(addrType.c_str()); + if (!util_exec_cmd(runGetIP, args, nullptr, &stdout_str, &stderr_str)) { + error.Errorf("Unexpected command output %s with error: %s", stdout_str, stderr_str); + goto free_out; + } + + DEBUG("get ip : %s", stdout_str); + /* get ip from stdout str */ + lines = util_string_split(stdout_str, '\n'); + if (lines == nullptr) { + error.SetError("Out of memory"); + goto free_out; + } + if (util_array_len(lines) < 1) { + error.Errorf("Unexpected command output %s", stdout_str); + goto free_out; + } + + fields = util_string_split(lines[0], ' '); + if (fields == nullptr) { + error.SetError("Out of memory"); + goto free_out; + } + if (util_array_len(fields) < 4) { + error.Errorf("Unexpected address output %s ", lines[0]); + goto free_out; + } + + if (parse_cidr(fields[3], &ipnet_val, &strErr) != 0) { + error.Errorf("CNI failed to parse ip from output %s due to %s", stdout_str, strErr); + goto free_out; + } + cIP = ip_to_string(ipnet_val->ip, ipnet_val->ip_len); + if (cIP == nullptr) { + error.SetError("Out of memory"); + goto free_out; + } + result = cIP; + free(cIP); + +free_out: + free_ipnet_type(ipnet_val); + free(stdout_str); + free(stderr_str); + util_free_array(args); + util_free_array(lines); + util_free_array(fields); + return result; +} + +std::string GetPodIP(const std::string &nsenterPath, const std::string &netnsPath, + const std::string &interfaceName, Errors &error) +{ + std::string ip = GetOnePodIP(nsenterPath, netnsPath, interfaceName, "-4", error); + if (error.NotEmpty()) { + return GetOnePodIP(nsenterPath, netnsPath, interfaceName, "-6", error); + } + + return ip; +} + +void InitNetworkPlugin(std::vector> *plugins, std::string networkPluginName, + CRIRuntimeServiceImpl *criImpl, std::string hairpinMode, std::string nonMasqueradeCIDR, int mtu, + std::shared_ptr *result, Errors &err) +{ + std::string allErr { "" }; + + if (networkPluginName.empty()) { + DEBUG("network plugin name empty"); + *result = std::shared_ptr(new NoopNetworkPlugin); + (*result)->Init(criImpl, hairpinMode, nonMasqueradeCIDR, mtu, err); + return; + } + + std::map> pluginMap; + + for (auto it = plugins->begin(); it != plugins->end(); ++it) { + std::string tmpName = (*it)->Name(); + // qualify plugin name + if (pluginMap.find(tmpName) != pluginMap.end()) { + allErr += ("network plugin " + tmpName + "was registered more than once"); + continue; + } + + pluginMap[tmpName] = *it; + } + + if (pluginMap.find(networkPluginName) == pluginMap.end()) { + allErr += ("Network plugin " + networkPluginName + "not found."); + err.SetError(allErr); + pluginMap.clear(); + return; + } + *result = pluginMap.find(networkPluginName)->second; + + (*result)->Init(criImpl, hairpinMode, nonMasqueradeCIDR, mtu, err); + if (err.NotEmpty()) { + allErr += ("Network plugin " + networkPluginName + " failed init: " + err.GetMessage()); + err.SetError(allErr); + } else { + INFO("Loaded network plugin %s", networkPluginName.c_str()); + } + + pluginMap.clear(); + return; +} + +const std::string &NetworkPluginConf::GetDockershimRootDirectory() const +{ + return m_dockershimRootDirectory; +} + +void NetworkPluginConf::SetDockershimRootDirectory(const std::string &rootDir) +{ + m_dockershimRootDirectory = rootDir; +} + +const std::string &NetworkPluginConf::GetPluginConfDir() const +{ + return m_pluginConfDir; +} + +void NetworkPluginConf::SetPluginConfDir(const std::string &confDir) +{ + m_pluginConfDir = confDir; +} + +const std::string &NetworkPluginConf::GetPluginBinDir() const +{ + return m_pluginBinDir; +} + +void NetworkPluginConf::SetPluginBinDir(const std::string &binDir) +{ + m_pluginBinDir = binDir; +} + +const std::string &NetworkPluginConf::GetPluginName() const +{ + return m_pluginName; +} + +void NetworkPluginConf::SetPluginName(const std::string &name) +{ + m_pluginName = name; +} + +const std::string &NetworkPluginConf::GetHairpinMode() const +{ + return m_hairpinMode; +} + +void NetworkPluginConf::SetHairpinMode(const std::string &mode) +{ + m_hairpinMode = mode; +} + +const std::string &NetworkPluginConf::GetNonMasqueradeCIDR() const +{ + return m_nonMasqueradeCIDR; +} + +void NetworkPluginConf::SetNonMasqueradeCIDR(const std::string &cidr) +{ + m_nonMasqueradeCIDR = cidr; +} + +int NetworkPluginConf::GetMTU() +{ + return m_mtu; +} + +void NetworkPluginConf::SetMTU(int mtu) +{ + m_mtu = mtu; +} + +const std::string &PodNetworkStatus::GetKind() const +{ + return m_kind; +} + +void PodNetworkStatus::SetKind(const std::string &kind) +{ + m_kind = kind; +} + +const std::string &PodNetworkStatus::GetAPIVersion() const +{ + return m_apiVersion; +} + +void PodNetworkStatus::SetAPIVersion(const std::string &version) +{ + m_apiVersion = version; +} + +const std::string &PodNetworkStatus::GetIP() const +{ + return m_ip; +} + +void PodNetworkStatus::SetIP(const std::string &ip) +{ + m_ip = ip; +} + +void PluginManager::Lock(const std::string &fullPodName, Errors &error) +{ + if (pthread_mutex_lock(&m_podsLock) != 0) { + error.SetError("plugin manager lock failed"); + return; + } + auto iter = m_pods.find(fullPodName); + PodLock *lock { nullptr }; + if (iter == m_pods.end()) { + auto tmpLock = std::unique_ptr(new PodLock()); + lock = tmpLock.get(); + m_pods[fullPodName] = std::move(tmpLock); + } else { + lock = iter->second.get(); + } + lock->Increase(); + + if (pthread_mutex_unlock(&m_podsLock) != 0) { + error.SetError("plugin manager unlock failed"); + } + + lock->Lock(error); +} + +void PluginManager::Unlock(const std::string &fullPodName, Errors &error) +{ + if (pthread_mutex_lock(&m_podsLock) != 0) { + error.SetError("plugin manager lock failed"); + return; + } + + auto iter = m_pods.find(fullPodName); + PodLock *lock { nullptr }; + if (iter == m_pods.end()) { + WARN("Unbalanced pod lock unref for %s", fullPodName.c_str()); + goto unlock; + } + lock = iter->second.get(); + if (lock->GetRefcount() == 0) { + m_pods.erase(iter); + WARN("Pod lock for %s still in map with zero refcount", fullPodName.c_str()); + goto unlock; + } + lock->Decrease(); + lock->Unlock(error); + if (lock->GetRefcount() == 0) { + m_pods.erase(iter); + } +unlock: + if (pthread_mutex_unlock(&m_podsLock) != 0) { + error.SetError("plugin manager unlock failed"); + } +} + +std::string PluginManager::PluginName() +{ + if (m_plugin != nullptr) { + return m_plugin->Name(); + } + return ""; +} + +void PluginManager::Event(const std::string &name, std::map &details) +{ + if (m_plugin != nullptr) { + m_plugin->Event(name, details); + } +} + +void PluginManager::Status(Errors &error) +{ + if (m_plugin != nullptr) { + m_plugin->Status(error); + } +} + +void PluginManager::GetPodNetworkStatus(const std::string &ns, const std::string &name, + const std::string &interfaceName, const std::string &podSandboxID, + PodNetworkStatus &status, Errors &error) +{ + std::string fullName = name + "_" + ns; + + Lock(fullName, error); + if (error.NotEmpty()) { + return; + } + if (m_plugin != nullptr) { + Errors tmpErr; + m_plugin->GetPodNetworkStatus(ns, name, interfaceName, podSandboxID, status, tmpErr); + if (tmpErr.NotEmpty()) { + error.Errorf("NetworkPlugin %s failed on the status hook for pod %s: %s", m_plugin->Name().c_str(), + fullName.c_str(), tmpErr.GetCMessage()); + } + } + Unlock(fullName, error); +} + +void PluginManager::SetUpPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + std::map &annotations, Errors &error) +{ + if (m_plugin == nullptr) { + return; + } + + std::string fullName = name + "_" + ns; + Lock(fullName, error); + if (error.NotEmpty()) { + return; + } + INFO("Calling network plugin %s to set up pod %s", m_plugin->Name().c_str(), fullName.c_str()); + + Errors tmpErr; + m_plugin->SetUpPod(ns, name, networkPlane, interfaceName, podSandboxID, annotations, tmpErr); + if (tmpErr.NotEmpty()) { + error.Errorf("NetworkPlugin %s failed to set up pod %s network: %s", m_plugin->Name().c_str(), fullName.c_str(), + tmpErr.GetCMessage()); + } + Unlock(fullName, error); +} + +void PluginManager::TearDownPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + std::map &annotations, Errors &error) +{ + Errors tmpErr; + std::string fullName = name + "_" + ns; + Lock(fullName, error); + if (error.NotEmpty()) { + return; + } + if (m_plugin == nullptr) { + goto unlock; + } + + INFO("Calling network plugin %s to tear down pod %s", m_plugin->Name().c_str(), fullName.c_str()); + m_plugin->TearDownPod(ns, name, networkPlane, interfaceName, podSandboxID, annotations, tmpErr); + if (tmpErr.NotEmpty()) { + error.Errorf("NetworkPlugin %s failed to teardown pod %s network: %s", m_plugin->Name().c_str(), + fullName.c_str(), tmpErr.GetCMessage()); + } +unlock: + Unlock(fullName, error); +} + +void NoopNetworkPlugin::Init(CRIRuntimeServiceImpl *criImpl, const std::string &hairpinMode, + const std::string &nonMasqueradeCIDR, + int mtu, Errors &error) +{ + char *stderr_str { nullptr }; + char *stdout_str { nullptr }; + int ret; + char *err { nullptr }; + + if (util_exec_cmd(run_modprobe, nullptr, nullptr, &stdout_str, &stderr_str) != 0) { + WARN("exec failed: [%s], [%s]", stdout_str, stderr_str); + } + + ret = set_sysctl(SYSCTL_BRIDGE_CALL_IPTABLES.c_str(), 1, &err); + if (ret != 0) { + WARN("can't set sysctl %s: 1, err: %s", SYSCTL_BRIDGE_CALL_IPTABLES.c_str(), err); + free(err); + err = nullptr; + } + + ret = get_sysctl(SYSCTL_BRIDGE_CALL_IP6TABLES.c_str(), &err); + if (ret != 1) { + free(err); + err = nullptr; + ret = set_sysctl(SYSCTL_BRIDGE_CALL_IP6TABLES.c_str(), 1, &err); + if (ret != 0) { + WARN("can't set sysctl %s: 1, err: %s", SYSCTL_BRIDGE_CALL_IP6TABLES.c_str(), err); + } + } + + free(err); + free(stderr_str); + free(stdout_str); +} + +void NoopNetworkPlugin::Event(const std::string &name, std::map &details) +{ + return; +} + +const std::string &NoopNetworkPlugin::Name() const +{ + return DEFAULT_PLUGIN_NAME; +} + +std::map *NoopNetworkPlugin::Capabilities() +{ + std::map *ret { new (std::nothrow) std::map }; + return ret; +} + +void NoopNetworkPlugin::SetUpPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + const std::map &annotations, Errors &error) +{ + return; +} + +void NoopNetworkPlugin::TearDownPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + const std::map &annotations, Errors &error) +{ + return; +} + +void NoopNetworkPlugin::GetPodNetworkStatus(const std::string &ns, const std::string &name, + const std::string &interfaceName, const std::string &podSandboxID, + PodNetworkStatus &status, Errors &error) +{ + return; +} + +void NoopNetworkPlugin::Status(Errors &error) +{ + return; +} + +} // namespace Network diff --git a/src/services/cri/network_plugin.h b/src/services/cri/network_plugin.h new file mode 100644 index 0000000..ac2bf9f --- /dev/null +++ b/src/services/cri/network_plugin.h @@ -0,0 +1,222 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide network plugin function definition + **********************************************************************************/ +#ifndef _CRI_NETWORK_PLUGIN_H_ +#define _CRI_NETWORK_PLUGIN_H_ + +#include +#include +#include +#include + +#include "errors.h" + +class CRIRuntimeServiceImpl; + +namespace Network { +const std::string DEFAULT_NETWORK_PLANE_NAME = "default"; +const std::string DEFAULT_NETWORK_INTERFACE_NAME = "eth0"; + +class NetworkPluginConf { +public: + /* settings for net plugin */ + NetworkPluginConf(const std::string &dockershimRootDirectory = "/var/lib/lcrd/shim", + const std::string &pluginConfDir = "/etc/cni/net.d/", + const std::string &pluginBinDir = "/opt/cni/bin", + const std::string &pluginName = "", + const std::string &hairpinMode = "hairpin-veth", + const std::string &nonMasqueradeCIDR = "", int32_t mtu = 1460) + : m_dockershimRootDirectory(dockershimRootDirectory), m_pluginConfDir(pluginConfDir), + m_pluginBinDir(pluginBinDir), m_pluginName(pluginName), + m_hairpinMode(hairpinMode), m_nonMasqueradeCIDR(nonMasqueradeCIDR), m_mtu(mtu) {} + ~NetworkPluginConf() = default; + + const std::string &GetDockershimRootDirectory() const; + void SetDockershimRootDirectory(const std::string &rootDir); + const std::string &GetPluginConfDir() const; + void SetPluginConfDir(const std::string &confDir); + const std::string &GetPluginBinDir() const; + void SetPluginBinDir(const std::string &binDir); + const std::string &GetPluginName() const; + void SetPluginName(const std::string &name); + const std::string &GetHairpinMode() const; + void SetHairpinMode(const std::string &mode); + const std::string &GetNonMasqueradeCIDR() const; + void SetNonMasqueradeCIDR(const std::string &cidr); + int32_t GetMTU(); + void SetMTU(int32_t mtu); + +private: + std::string m_dockershimRootDirectory; + std::string m_pluginConfDir; + std::string m_pluginBinDir; + std::string m_pluginName; + std::string m_hairpinMode; + std::string m_nonMasqueradeCIDR; + int32_t m_mtu; + /* finish net plugin */ +}; + +class PodNetworkStatus { +public: + PodNetworkStatus() = default; + ~PodNetworkStatus() = default; + const std::string &GetKind() const; + void SetKind(const std::string &kind); + const std::string &GetAPIVersion() const; + void SetAPIVersion(const std::string &version); + const std::string &GetIP() const; + void SetIP(const std::string &ip); + +private: + std::string m_kind; + std::string m_apiVersion; + std::string m_ip; +}; + +class NetworkPlugin { +public: + virtual void Init(CRIRuntimeServiceImpl *criImpl, const std::string &hairpinMode, + const std::string &nonMasqueradeCIDR, int mtu, Errors &error) = 0; + + virtual void Event(const std::string &name, std::map &details) = 0; + + virtual const std::string &Name() const = 0; + + virtual std::map *Capabilities() = 0; + + virtual void SetUpPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + const std::map &annotations, Errors &error) = 0; + + virtual void TearDownPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + const std::map &annotations, Errors &error) = 0; + + virtual void GetPodNetworkStatus(const std::string &ns, const std::string &name, const std::string &interfaceName, + const std::string &podSandboxID, PodNetworkStatus &status, Errors &error) = 0; + + virtual void Status(Errors &error) = 0; +}; + +class NoopNetworkPlugin : public NetworkPlugin { +private: + std::string SYSCTL_BRIDGE_CALL_IPTABLES = "net/bridge/bridge-nf-call-iptables"; + std::string SYSCTL_BRIDGE_CALL_IP6TABLES = "net/bridge/bridge-nf-call-ip6tables"; + std::string DEFAULT_PLUGIN_NAME = "kubernetes.io/no-op"; + +public: + NoopNetworkPlugin() = default; + + virtual ~NoopNetworkPlugin() = default; + + void Init(CRIRuntimeServiceImpl *criImpl, const std::string &hairpinMode, + const std::string &nonMasqueradeCIDR, int mtu, Errors &error) override; + + void Event(const std::string &name, std::map &details) override; + + const std::string &Name() const override; + + std::map *Capabilities() override; + + void SetUpPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + const std::map &annotations, Errors &error) override; + + void TearDownPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + const std::map &annotations, Errors &error) override; + + void GetPodNetworkStatus(const std::string &ns, const std::string &name, const std::string &interfaceName, + const std::string &podSandboxID, PodNetworkStatus &status, Errors &error) override; + + void Status(Errors &error) override; +}; + +class PodLock { +public: + PodLock() = default; + ~PodLock() = default; + uint32_t GetRefcount() + { + return m_refcount; + } + void Increase() + { + m_refcount++; + } + void Decrease() + { + m_refcount--; + } + void Lock(Errors &error) + { + int ret = pthread_mutex_lock(&m_mu); + if (ret != 0) { + error.Errorf("mutex lock failed: %d", ret); + } + } + void Unlock(Errors &error) + { + int ret = pthread_mutex_unlock(&m_mu); + if (ret != 0) { + error.Errorf("mutex unlock failed: %d", ret); + } + } + +private: + // Count of in-flight operations for this pod; when this reaches zero the lock can be removed from the pod map + uint32_t m_refcount = 0; + + // Lock to synchronize operations for this specific pod + pthread_mutex_t m_mu = PTHREAD_MUTEX_INITIALIZER; +}; + +class PluginManager { +public: + explicit PluginManager(std::shared_ptr plugin) : m_plugin(plugin) {} + ~PluginManager() = default; + std::string PluginName(); + void Event(const std::string &name, std::map &details); + void Status(Errors &error); + void GetPodNetworkStatus(const std::string &ns, const std::string &name, const std::string &interfaceName, + const std::string &podSandboxID, PodNetworkStatus &status, Errors &error); + void SetUpPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + std::map &annotations, Errors &error); + void TearDownPod(const std::string &ns, const std::string &name, const std::string &networkPlane, + const std::string &interfaceName, const std::string &podSandboxID, + std::map &annotations, Errors &error); + +private: + void Lock(const std::string &fullPodName, Errors &error); + void Unlock(const std::string &fullPodName, Errors &error); + + pthread_mutex_t m_podsLock = PTHREAD_MUTEX_INITIALIZER; + std::map> m_pods; + std::shared_ptr m_plugin = nullptr; +}; + +void InitNetworkPlugin(std::vector> *plugins, std::string networkPluginName, + CRIRuntimeServiceImpl *criImpl, std::string hairpinMode, std::string nonMasqueradeCIDR, int mtu, + std::shared_ptr *result, Errors &error); + +void ProbeNetworkPlugins(const std::string &pluginDir, const std::string &binDir, + std::vector> *plugins); + +std::string GetPodIP(const std::string &nsenterPath, const std::string &netnsPath, + const std::string &interfaceName, Errors &error); +} // namespace Network + +#endif diff --git a/src/services/cri/request_cache.cc b/src/services/cri/request_cache.cc new file mode 100644 index 0000000..c5b85df --- /dev/null +++ b/src/services/cri/request_cache.cc @@ -0,0 +1,153 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide request cache function definition + *********************************************************************************/ +#include "request_cache.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" + +std::atomic RequestCache::m_instance; +std::mutex RequestCache::m_mutex; +RequestCache *RequestCache::GetInstance() noexcept +{ + RequestCache *cache = m_instance.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (cache == nullptr) { + std::lock_guard lock(m_mutex); + cache = m_instance.load(std::memory_order_relaxed); + if (cache == nullptr) { + cache = new RequestCache; + std::atomic_thread_fence(std::memory_order_release); + m_instance.store(cache, std::memory_order_relaxed); + } + } + return cache; +} + +std::string RequestCache::Insert(::google::protobuf::Message *req) +{ + if (req == nullptr) { + ERROR("invalid request"); + return ""; + } + std::lock_guard lock(m_mutex); + // Remove expired entries. + GarbageCollection(); + // If the cache is full, reject the request. + if (m_ll.size() == MaxInFlight) { + ERROR("too many cache in flight!"); + return ""; + } + auto token = UniqueToken(); + CacheEntry tmp { token, req, std::chrono::system_clock::now() + std::chrono::minutes(1) }; + m_ll.push_front(tmp); + m_tokens.insert(std::make_pair(token, tmp)); + return token; +} + +void RequestCache::GarbageCollection() +{ + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + while (!m_ll.empty()) { + CacheEntry oldest = m_ll.back(); + if (now < oldest.expireTime) { + return; + } + m_ll.pop_back(); + m_tokens.erase(oldest.token); + } +} + +std::string RequestCache::UniqueToken() +{ + const int maxTries { 50 }; + std::random_device r; + std::default_random_engine e1(r()); + std::uniform_int_distribution uniform_dist(1, 254); + // Number of bytes to be TokenLen when base64 encoded. + const int tokenSize { 16 }; + char rawToken[tokenSize + 1] { 0 }; + for (int i {}; i < maxTries; ++i) { + char buf[40] { 0 }; + for (size_t j {}; j < tokenSize; ++j) { + rawToken[j] = (char)uniform_dist(e1); + } + + lws_b64_encode_string(rawToken, (int)strlen(rawToken), buf, (int)sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + if (strlen(buf) < TokenLen) { + continue; + } + std::string token(buf, buf + TokenLen); + if (token.length() != TokenLen) { + continue; + } + + bool ok { true }; + std::string subDelims { R"(-._:~!$&'()*+,;/=%@)" }; + for (const auto &t : token) { + if ((subDelims.find(t) != std::string::npos)) { + ok = false; + break; + } + } + if (!ok) { + continue; + } + auto it = m_tokens.find(token); + if (it == m_tokens.end()) { + return token; + } + } + ERROR("create unique token failed!"); + return ""; +} +bool RequestCache::IsValidToken(const std::string &token) +{ + return static_cast(m_tokens.count(token)); +} + +// Consume the token (remove it from the cache) and return the cached request, if found. +::google::protobuf::Message *RequestCache::Consume(const std::string &token, bool &found) +{ + std::lock_guard lock(m_mutex); + + found = false; + if (!IsValidToken(token)) { + ERROR("Invalid token"); + return nullptr; + } + + CacheEntry ele = m_tokens[token]; + for (auto it = m_ll.begin(); it != m_ll.end(); it++) { + if (it->token == ele.token) { + m_ll.erase(it); + break; + } + } + m_tokens.erase(token); + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + if (now > ele.expireTime) { + return nullptr; + } + found = true; + return ele.req; +} + diff --git a/src/services/cri/request_cache.h b/src/services/cri/request_cache.h new file mode 100644 index 0000000..622ae54 --- /dev/null +++ b/src/services/cri/request_cache.h @@ -0,0 +1,59 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: store streaming requests and generates a single-use random token for retrieval. + * Author: wujing + * Create: 2019-01-02 + ******************************************************************************/ + +#ifndef __REQUEST_CACHE_H_ +#define __REQUEST_CACHE_H_ +#include +#include +#include +#include +#include +#include +#include + +typedef struct sCacheEntry { + std::string token; + ::google::protobuf::Message *req; + std::chrono::system_clock::time_point expireTime; +} CacheEntry, *pCacheEntry; + +class RequestCache { +public: + static RequestCache *GetInstance() noexcept; + std::string Insert(::google::protobuf::Message *req); + ::google::protobuf::Message *Consume(const std::string &token, bool &found); + bool IsValidToken(const std::string &token); + +private: + void GarbageCollection(); + std::string UniqueToken(); + +private: + RequestCache() = default; + RequestCache(const RequestCache &) = delete; + RequestCache &operator=(const RequestCache &) = delete; + ~RequestCache() = default; + // clock is used to obtain the current time + std::time_t m_clock; + // tokens maps the generate token to the request for fast retrieval. + std::unordered_map m_tokens; + // ll maintains an age-ordered request list for faster garbage collection of expired requests. + std::list m_ll; + static std::mutex m_mutex; + static std::atomic m_instance; + const size_t MaxInFlight { 1000 }; + const size_t TokenLen { 8 }; +}; +#endif /*__REQUEST_CACHE_H_*/ diff --git a/src/services/execution/CMakeLists.txt b/src/services/execution/CMakeLists.txt new file mode 100644 index 0000000..f8bfe2c --- /dev/null +++ b/src/services/execution/CMakeLists.txt @@ -0,0 +1,23 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_execution_srcs) +add_subdirectory(events) +add_subdirectory(execute) +add_subdirectory(manager) +add_subdirectory(spec) + +set(EXECUTION_SRCS + ${local_execution_srcs} + ${EVENTS_SRCS} + ${EXECUTE_SRCS} + ${MANAGER_SRCS} + ${SPEC_SRCS} + PARENT_SCOPE + ) +set(EXECUTION_INCS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/events + ${CMAKE_CURRENT_SOURCE_DIR}/execute + ${CMAKE_CURRENT_SOURCE_DIR}/manager + ${CMAKE_CURRENT_SOURCE_DIR}/spec + PARENT_SCOPE + ) diff --git a/src/services/execution/events/CMakeLists.txt b/src/services/execution/events/CMakeLists.txt new file mode 100644 index 0000000..6655e0f --- /dev/null +++ b/src/services/execution/events/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_events_srcs) + +set(EVENTS_SRCS + ${local_events_srcs} + PARENT_SCOPE + ) diff --git a/src/services/execution/events/collector.c b/src/services/execution/events/collector.c new file mode 100644 index 0000000..9ba4be4 --- /dev/null +++ b/src/services/execution/events/collector.c @@ -0,0 +1,834 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container collector functions + ******************************************************************************/ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "log.h" +#include +#include "collector.h" +#include "lcrd_config.h" +#include "securec.h" +#include "liblcrd.h" +#include "containers_store.h" + +static struct context_lists g_context_lists; + +struct events_lists { + unsigned int size; + pthread_mutex_t event_mutex; + struct linked_list event_list; +}; +static struct events_lists g_events_buffer; + +#define EVENTSLIMIT 64 + +struct context_elem { + stream_func_wrapper stream; + char *name; + sem_t context_sem; + const types_timestamp_t *since; + const types_timestamp_t *until; +}; + +/* get idreg */ +static bool get_idreg(regex_t *preg, const char *id) +{ + char *regexp = NULL; + size_t len = 0; + int nret = 0; + bool ret = false; + + if (id == NULL) { + ERROR("Invalid event id"); + return false; + } + + len = strlen(id) + 3; + regexp = util_common_calloc_s(len); + if (regexp == NULL) { + ERROR("failed to allocate memory"); + return false; + } + + nret = sprintf_s(regexp, len, "^%s$", id); + if (nret < 0) { + ERROR("Failed to print string"); + goto error; + } + + if (regcomp(preg, regexp, REG_NOSUB | REG_EXTENDED)) { + ERROR("failed to compile the regex '%s'", id); + goto error; + } + + ret = true; + +error: + free(regexp); + return ret; +} + +static container_events_type_t lcrsta2Evetype(int value) +{ + container_events_type_t et = EVENTS_TYPE_EXIT; + + switch (value) { + case STOPPED: + et = EVENTS_TYPE_STOPPED1; + break; + case STARTING: + et = EVENTS_TYPE_STARTING; + break; + case RUNNING: + et = EVENTS_TYPE_RUNNING1; + break; + case STOPPING: + et = EVENTS_TYPE_STOPPING; + break; + case ABORTING: + et = EVENTS_TYPE_ABORTING; + break; + case FREEZING: + et = EVENTS_TYPE_FREEZING; + break; + case FROZEN: + et = EVENTS_TYPE_FROZEN; + break; + case THAWED: + et = EVENTS_TYPE_THAWED; + break; + default: + et = EVENTS_TYPE_EXIT; + break; + } + return et; +} + +/* format_msg */ +static bool format_msg(struct lcrd_events_format *r, struct monitord_msg *msg) +{ + bool ret = false; + int err = 0; + struct timespec ts; + + err = clock_gettime(CLOCK_REALTIME, &ts); + if (err != 0) { + ERROR("failed to get time"); + return false; + } + r->timestamp.has_seconds = true; + r->timestamp.seconds = (int64_t)ts.tv_sec; + r->timestamp.has_nanos = true; + r->timestamp.nanos = (int32_t)ts.tv_nsec; + + msg->name[sizeof(msg->name) - 1] = '\0'; + + r->has_pid = false; + switch (msg->type) { + case monitord_msg_state: + r->id = msg->name; + if (msg->pid != -1) { + r->has_pid = true; + r->pid = (uint32_t)msg->pid; + } + r->has_type = true; + r->type = lcrsta2Evetype(msg->value); + if (r->type == EVENTS_TYPE_STOPPED1) { + r->has_exit_status = true; + if (msg->exit_code >= 0) { + r->exit_status = (uint32_t)msg->exit_code; + } else { + r->exit_status = 125; + } + } + ret = true; + break; + case monitord_msg_priority: + case monitord_msg_exit_code: + default: + /* ignore garbage */ + ret = false; + DEBUG("Ignore received %d event", msg->type); + break; + } + return ret; +} + +static const char * const g_lcrd_event_strtype[] = { + "EXIT", "STOPPED", "STARTING", "RUNNING", "STOPPING", "ABORTING", "FREEZING", + "FROZEN", "THAWED", "OOM", "CREATE", "START", "EXEC_ADDED", "PAUSED1", +}; + +/* lcrd event sta2str */ +static const char *lcrd_event_sta2str(container_events_type_t sta) +{ + if (sta > EVENTS_TYPE_PAUSED1) { + return NULL; + } + return g_lcrd_event_strtype[sta]; +} + +/* lcrd monitor fifo send */ +static void lcrd_monitor_fifo_send(const struct monitord_msg *msg, const char *statedir) +{ + int fd = -1; + ssize_t ret = 0; + char *fifo_path = NULL; + + fifo_path = lcrd_monitor_fifo_name(statedir); + if (fifo_path == NULL) { + return; + } + + /* Open the fifo nonblock in case the monitor is dead, we don't want the + * open to wait for a reader since it may never come. + */ + fd = util_open(fifo_path, O_WRONLY | O_NONBLOCK, 0); + if (fd < 0) { + /* It is normal for this open() to fail with ENXIO when there is + * no monitor running, so we don't log it. + */ + if (errno == ENXIO || errno == ENOENT) { + goto out; + } + + ERROR("Failed to open fifo to send message: %s.", strerror(errno)); + goto out; + } + + ret = write(fd, msg, sizeof(struct monitord_msg)); + if (ret < 0 || (size_t)ret != sizeof(struct monitord_msg)) { + ERROR("Failed to write to monitor fifo \"%s\": %s.", fifo_path, strerror(errno)); + goto out; + } + +out: + free(fifo_path); + if (fd >= 0) { + close(fd); + } +} + +/* lcrd monitor send event */ +int lcrd_monitor_send_event(const char *name, runtime_state_t state, int pid, int exit_code) +{ + int ret = 0; + char *statedir = NULL; + errno_t nret; + struct monitord_msg msg = { + .type = monitord_msg_state, + .value = state, + .pid = -1, + .exit_code = -1 + }; + + if (name == NULL) { + CRIT("Invalid input arguments"); + ret = -1; + goto out; + } + + statedir = conf_get_lcrd_statedir(); + if (statedir == NULL) { + CRIT("Can not get lcrd root path"); + ret = -1; + goto out; + } + + nret = strncpy_s(msg.name, sizeof(msg.name), name, sizeof(msg.name) - 1); + if (nret != EOK) { + ERROR("Fail at lcrd_monitor_send_event string copy!"); + ret = -1; + goto out; + } + msg.name[sizeof(msg.name) - 1] = 0; + if (pid > 0) { + msg.pid = pid; + } + if (exit_code >= 0) { + msg.exit_code = exit_code; + } + + lcrd_monitor_fifo_send(&msg, statedir); + +out: + free(statedir); + return ret; +} + +/* write events log */ +static int write_events_log(const struct lcrd_events_format *events) +{ +#define PID_PREFIX ", Pid: " +#define EXIT_CODE_PREFIX ", ExitCode: " + + int ret = 0; + int nret = 0; + char *pid_str = NULL; + char *exit_status_str = NULL; + + if (events == NULL) { + goto out; + } + + if (events->has_pid) { + nret = asprintf(&pid_str, "%s%u", PID_PREFIX, events->pid); + if (nret < 0) { + ERROR("Sprintf pid failed"); + ret = -1; + goto out; + } + } + + if (events->has_exit_status) { + nret = asprintf(&exit_status_str, "%s%u", EXIT_CODE_PREFIX, events->exit_status); + if (nret < 0) { + ERROR("Sprintf exit status failed"); + ret = -1; + goto out; + } + } + + EVENT("Event: {Object: %s, Type: %s%s%s}", events->id, + (events->has_type ? lcrd_event_sta2str((container_events_type_t)events->type) : "-"), + (events->has_pid ? pid_str : ""), (events->has_exit_status ? exit_status_str : "")); + +out: + free(pid_str); + free(exit_status_str); + return ret; +} + +/* events copy*/ +static void event_copy(const struct lcrd_events_format *src, struct lcrd_events_format *dest) +{ + if (src == NULL || dest == NULL) { + return; + } + + free(dest->id); + dest->id = util_strdup_s(src->id); + dest->has_type = src->has_type; + dest->type = src->type; + dest->has_pid = src->has_pid; + dest->pid = src->pid; + dest->has_exit_status = src->has_exit_status; + dest->exit_status = src->exit_status; + dest->timestamp.has_seconds = src->timestamp.has_seconds; + dest->timestamp.seconds = src->timestamp.seconds; + dest->timestamp.has_nanos = src->timestamp.has_nanos; + dest->timestamp.nanos = src->timestamp.nanos; +} + +/* events append */ +static void events_append(const struct lcrd_events_format *event) +{ + struct lcrd_events_format *tmpevent = NULL; + struct linked_list *newnode = NULL; + struct linked_list *firstnode = NULL; + + if (pthread_mutex_lock(&g_events_buffer.event_mutex)) { + WARN("Failed to lock"); + return; + } + + if (g_events_buffer.size < EVENTSLIMIT) { + newnode = util_common_calloc_s(sizeof(struct linked_list)); + if (newnode == NULL) { + CRIT("Memory allocation error."); + goto unlock; + } + + tmpevent = util_common_calloc_s(sizeof(struct lcrd_events_format)); + if (tmpevent == NULL) { + CRIT("Memory allocation error."); + free(newnode); + goto unlock; + } + + event_copy(event, tmpevent); + + linked_list_add_elem(newnode, tmpevent); + linked_list_add_tail(&g_events_buffer.event_list, newnode); + g_events_buffer.size++; + } else { + firstnode = linked_list_first_node(&g_events_buffer.event_list); + if (firstnode != NULL) { + linked_list_del(firstnode); + + tmpevent = (struct lcrd_events_format *)firstnode->elem; + event_copy(event, tmpevent); + + linked_list_add_tail(&g_events_buffer.event_list, firstnode); + } + } + +unlock: + if (pthread_mutex_unlock(&g_events_buffer.event_mutex)) { + WARN("Failed to unlock"); + return; + } +} + +static int do_write_events(const stream_func_wrapper *stream, struct lcrd_events_format *event) +{ + int ret = 0; + + if (stream->write_func == NULL || stream->writer == NULL) { + ERROR("Unimplemented write function"); + ret = -1; + goto out; + } + if (!stream->write_func(stream->writer, event)) { + ERROR("Failed to send exit event for 'events' client"); + ret = -1; + goto out; + } +out: + return ret; +} + +static int check_since_time(const types_timestamp_t *since, const struct lcrd_events_format *event) +{ + if (since != NULL && (since->has_seconds || since->has_nanos)) { + if (types_timestamp_cmp(&event->timestamp, since) < 0) { + return -1; + } + } + return 0; +} + +static int check_util_time(const types_timestamp_t *until, const struct lcrd_events_format *event) +{ + if (until != NULL && (until->has_seconds || until->has_nanos)) { + if (types_timestamp_cmp(&event->timestamp, until) > 0) { + return -1; + } + } + return 0; +} + +static int do_subscribe(const char *name, const types_timestamp_t *since, const types_timestamp_t *until, + const stream_func_wrapper *stream) +{ + bool regflag = false; + int ret = 0; + regex_t preg; + regmatch_t regmatch = { 0 }; + struct linked_list *it = NULL; + struct linked_list *next = NULL; + struct lcrd_events_format *c_event = NULL; + + if (pthread_mutex_lock(&g_events_buffer.event_mutex)) { + WARN("Failed to lock"); + return -1; + } + + linked_list_for_each_safe(it, &g_events_buffer.event_list, next) { + c_event = (struct lcrd_events_format *)it->elem; + + if (check_since_time(since, c_event) != 0) { + continue; + } + + if (check_util_time(until, c_event) != 0) { + break; + } + + if (regflag) { + regfree(&preg); + } + regflag = get_idreg(&preg, c_event->id); + + if (name != NULL && regflag) { + if (regexec(&preg, name, 1, ®match, 0)) { + continue; + } + } + + ret = do_write_events(stream, c_event); + if (ret != 0) { + break; + } + } + + if (pthread_mutex_unlock(&g_events_buffer.event_mutex)) { + WARN("Failed to unlock"); + } + if (regflag) { + regfree(&preg); + } + + return ret; +} + +/* events subscribe */ +int events_subscribe(const char *name, const types_timestamp_t *since, const types_timestamp_t *until, + const stream_func_wrapper *stream) +{ + if (stream == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + if (since == NULL && until == NULL) { + return 0; + } + + if (since != NULL && (since->has_seconds || since->has_nanos) && until != NULL && + (until->has_seconds || until->has_nanos)) { + if (types_timestamp_cmp(since, until) > 0) { + ERROR("'since' time cannot be after 'until' time"); + return -1; + } + } + + return do_subscribe(name, since, until, stream); +} + +/* events forward */ +static void events_forward(struct lcrd_events_format *r) +{ + struct linked_list *it = NULL; + struct linked_list *next = NULL; + struct context_elem *context_info = NULL; + char *name = NULL; + regex_t preg; + bool regflag = false; + regmatch_t regmatch = { 0 }; + + events_append(r); + regflag = get_idreg(&preg, r->id); + + if (pthread_mutex_lock(&g_context_lists.context_mutex)) { + WARN("Failed to lock"); + return; + } + + linked_list_for_each_safe(it, &g_context_lists.context_list, next) { + context_info = (struct context_elem *)it->elem; + name = context_info->name; + + if (context_info->since != NULL) { + if (types_timestamp_cmp(&r->timestamp, context_info->since) < 0) { + continue; + } + } + + if (name != NULL && regflag) { + if (regexec(&preg, name, 1, ®match, 0)) { + continue; + } + } + + if (context_info->stream.write_func == NULL || context_info->stream.writer == NULL) { + INFO("Unimplemented write function"); + goto delete_and_continue; + } + if (!context_info->stream.write_func(context_info->stream.writer, r)) { + INFO("Failed to send exit event for 'events' client"); + goto delete_and_continue; + } + + continue; + +delete_and_continue: + linked_list_del(it); + sem_post(&context_info->context_sem); + continue; + } + + if (pthread_mutex_unlock(&g_context_lists.context_mutex)) { + WARN("Failed to unlock"); + } + + if (regflag) { + regfree(&preg); + } +} + +/* event should exit */ +static void *event_should_exit(void *arg) +{ + int res = 0; + int err = 0; + + res = pthread_detach(pthread_self()); + if (res != 0) { + CRIT("Set thread detach fail"); + goto error; + } + + prctl(PR_SET_NAME, "Clients_checker"); + + struct linked_list *it = NULL; + struct linked_list *next = NULL; + struct context_elem *context_info = NULL; + struct timespec ts_now = { 0 }; + types_timestamp_t t_now = { 0 }; + + for (;;) { + if (pthread_mutex_lock(&g_context_lists.context_mutex)) { + WARN("Failed to lock"); + continue; + } + + linked_list_for_each_safe(it, &g_context_lists.context_list, next) { + context_info = (struct context_elem *)it->elem; + + if (context_info->stream.is_cancelled(context_info->stream.context)) { + DEBUG("Client has exited, stop sending events"); + linked_list_del(it); + sem_post(&context_info->context_sem); + continue; + } + + if (context_info->until == NULL || + (context_info->until->has_seconds == 0 && context_info->until->has_nanos == 0)) { + continue; + } + + err = clock_gettime(CLOCK_REALTIME, &ts_now); + if (err != 0) { + ERROR("Failed to get time"); + continue; + } + + t_now.has_seconds = true; + t_now.seconds = ts_now.tv_sec; + t_now.has_nanos = true; + t_now.nanos = (int32_t)ts_now.tv_nsec; + + if (types_timestamp_cmp(&t_now, context_info->until) > 0) { + INFO("Finish response for RPC, client should exit"); + linked_list_del(it); + sem_post(&context_info->context_sem); + continue; + } + } + + if (pthread_mutex_unlock(&g_context_lists.context_mutex)) { + WARN("Failed to unlock"); + } + + sleep(1); + } +error: + return NULL; +} + +/* post event to events hander */ +static int post_event_to_events_hander(const struct lcrd_events_format *events) +{ + int ret = 0; + container_t *cont = NULL; + + if (events == NULL || events->id == NULL) { + return -1; + } + + /*only post STOPPED event to events_hander */ + if (events->type != EVENTS_TYPE_STOPPED1) { + return 0; + } + + cont = containers_store_get(events->id); + if (cont == NULL) { + ERROR("No such container:%s", events->id); + return -1; + } + + if (events_handler_post_events(cont->handler, events)) { + ERROR("Failed to post events to events handler:%s", events->id); + ret = -1; + goto out; + } + +out: + container_unref(cont); + return ret; +} + +/* events handler */ +void events_handler(struct monitord_msg *msg) +{ + struct lcrd_events_format events = { 0 }; + + if (msg == NULL) { + ERROR("Invalid input arguments"); + return; + } + + if (format_msg(&events, msg) != true) { + return; + } + + /* post events to events handler */ + if (post_event_to_events_hander(&events)) { + ERROR("Failed to handle %s STOPPED events with pid %d", events.id, events.pid); + return; + } + + /* forward events to grpc clients */ + events_forward(&events); + + /* log event into lcrd.log */ + (void)write_events_log(&events); +} + +/* dup event */ +struct lcrd_events_format *dup_event(const struct lcrd_events_format *event) +{ + struct lcrd_events_format *out = NULL; + + if (event == NULL || event->id == NULL) { + return NULL; + } + + out = util_common_calloc_s(sizeof(struct lcrd_events_format)); + if (out == NULL) { + return NULL; + } + + event_copy(event, out); + + return out; +} + +/* free event */ +void free_event(struct lcrd_events_format *event) +{ + if (event == NULL) { + return; + } + free(event->id); + event->id = NULL; + free(event); + return; +} + +/* add monitor client */ +int add_monitor_client(char *name, const types_timestamp_t *since, const types_timestamp_t *until, + const stream_func_wrapper *stream) +{ + int ret = 0; + struct linked_list *newnode = NULL; + struct context_elem *context_info = NULL; + + if (stream == NULL) { + CRIT("Should provide stream functions"); + return -1; + } + + newnode = util_common_calloc_s(sizeof(struct linked_list)); + if (newnode == NULL) { + CRIT("Memory allocation error."); + return -1; + } + + context_info = util_common_calloc_s(sizeof(struct context_elem)); + if (context_info == NULL) { + CRIT("Memory allocation error."); + ret = -1; + goto free_out; + } + + if (sem_init(&context_info->context_sem, 0, 0)) { + ERROR("Semaphore initialization failed"); + ret = -1; + goto free_out; + } + + context_info->name = name; + context_info->since = since; + context_info->until = until; + context_info->stream.is_cancelled = stream->is_cancelled; + context_info->stream.context = stream->context; + context_info->stream.write_func = stream->write_func; + context_info->stream.writer = stream->writer; + + if (pthread_mutex_lock(&g_context_lists.context_mutex)) { + ERROR("Failed to lock"); + ret = -1; + goto sem_free; + } + + linked_list_add_elem(newnode, context_info); + linked_list_add_tail(&g_context_lists.context_list, newnode); + + if (pthread_mutex_unlock(&g_context_lists.context_mutex)) { + WARN("Failed to unlock"); + ret = -1; + goto sem_free; + } + + sem_wait(&context_info->context_sem); + +sem_free: + sem_destroy(&context_info->context_sem); + +free_out: + free(context_info); + free(newnode); + return ret; +} + +/* newcollector */ +int newcollector() +{ + int ret = -1; + pthread_t exit_thread; + + linked_list_init(&(g_context_lists.context_list)); + linked_list_init(&(g_events_buffer.event_list)); + g_events_buffer.size = 0; + + ret = pthread_mutex_init(&(g_context_lists.context_mutex), NULL); + if (ret != 0) { + CRIT("Mutex initialization failed"); + goto out; + } + + ret = pthread_mutex_init(&(g_events_buffer.event_mutex), NULL); + if (ret != 0) { + CRIT("Mutex initialization failed"); + pthread_mutex_destroy(&(g_context_lists.context_mutex)); + goto out; + } + + INFO("Starting collector..."); + ret = pthread_create(&exit_thread, NULL, event_should_exit, NULL); + if (ret != 0) { + CRIT("Thread creation failed"); + pthread_mutex_destroy(&(g_context_lists.context_mutex)); + pthread_mutex_destroy(&(g_events_buffer.event_mutex)); + goto out; + } + + ret = 0; +out: + return ret; +} diff --git a/src/services/execution/events/collector.h b/src/services/execution/events/collector.h new file mode 100644 index 0000000..f7c3d97 --- /dev/null +++ b/src/services/execution/events/collector.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container collector definition + ******************************************************************************/ +#ifndef __COLLECTOR_H +#define __COLLECTOR_H + +#include +#include +#include "linked_list.h" +#include "liblcrd.h" +#include "monitord.h" + +struct context_lists { + pthread_mutex_t context_mutex; + struct linked_list context_list; +}; + +int newcollector(); + +void events_handler(struct monitord_msg *msg); + +int add_monitor_client(char *name, const types_timestamp_t *since, const types_timestamp_t *until, + const stream_func_wrapper *stream); + +int events_subscribe(const char *name, const types_timestamp_t *since, const types_timestamp_t *until, + const stream_func_wrapper *stream); + +struct lcrd_events_format *dup_event(const struct lcrd_events_format *event); + +void free_event(struct lcrd_events_format *event); + +int lcrd_monitor_send_event(const char *name, runtime_state_t state, int pid, int exit_code); + +#endif /* __COLLECTOR_H */ diff --git a/src/services/execution/events/events_handler.c b/src/services/execution/events/events_handler.c new file mode 100644 index 0000000..685035b --- /dev/null +++ b/src/services/execution/events/events_handler.c @@ -0,0 +1,329 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container events handler functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "log.h" +#include "lcrd_config.h" +#include "collector.h" +#include "events_handler.h" +#include "utils.h" +#include "securec.h" +#include "containers_store.h" +#include "execution.h" +#include "plugin.h" + +/* events handler lock */ +static void events_handler_lock(events_handler_t *handler) +{ + if (pthread_mutex_lock(&(handler->mutex)) != 0) { + ERROR("Failed to lock events handler"); + } +} + +/* events handler unlock */ +static void events_handler_unlock(events_handler_t *handler) +{ + if (pthread_mutex_unlock(&(handler->mutex)) != 0) { + ERROR("Failed to unlock events handler"); + } +} + +/* events handler free */ +void events_handler_free(events_handler_t *handler) +{ + struct lcrd_events_format *event = NULL; + struct linked_list *it = NULL; + struct linked_list *next = NULL; + + if (handler == NULL) { + return; + } + + linked_list_for_each_safe(it, &(handler->events_list), next) { + event = (struct lcrd_events_format *)it->elem; + linked_list_del(it); + free_event(event); + free(it); + it = NULL; + } + if (handler->init_mutex) { + pthread_mutex_destroy(&(handler->mutex)); + } + free(handler); +} + +/* events handler new */ +events_handler_t *events_handler_new() +{ + int ret; + events_handler_t *handler = NULL; + + handler = util_common_calloc_s(sizeof(events_handler_t)); + if (handler == NULL) { + ERROR("Out of memory"); + return NULL; + } + + ret = pthread_mutex_init(&(handler->mutex), NULL); + if (ret != 0) { + ERROR("Failed to init mutex of events_handler"); + goto cleanup; + } + handler->init_mutex = true; + + linked_list_init(&(handler->events_list)); + + handler->has_handler = false; + + return handler; +cleanup: + events_handler_free(handler); + return NULL; +} + +/* container state changed */ +static int container_state_changed(container_t *cont, const struct lcrd_events_format *events) +{ + int ret = 0; + int pid = 0; + uint64_t timeout; + char *id = events->id; + char *started_at = NULL; + bool should_restart = false; + bool auto_remove = false; + + /*only handle Exit event*/ + if (events->type != EVENTS_TYPE_STOPPED1) { + return 0; + } + + switch (events->type) { + case EVENTS_TYPE_STOPPED1: + container_lock(cont); + + if (false == is_running(cont->state)) { + DEBUG("Container is not in running state ignore STOPPED event"); + container_unlock(cont); + ret = 0; + goto out; + } + + pid = state_get_pid(cont->state); + if (pid != (int)events->pid) { + DEBUG("Container's pid \'%d\' is not equal to event's pid \'%d\', ignore STOPPED event", + pid, events->pid); + container_unlock(cont); + ret = 0; + goto out; + } + + started_at = state_get_started_at(cont->state); + + should_restart = restart_manager_should_restart(id, events->exit_status, + cont->common_config->has_been_manually_stopped, + time_seconds_since(started_at), &timeout); + free(started_at); + started_at = NULL; + + if (should_restart) { + cont->common_config->restart_count++; + state_set_restarting(cont->state, (int)events->exit_status); + container_wait_stop_cond_broadcast(cont); + INFO("Try to restart container %s after %.2fs", id, (double)timeout / Time_Second); + (void)container_restart_in_thread(id, timeout, (int)events->exit_status); + } else { + state_set_stopped(cont->state, (int)events->exit_status); + container_wait_stop_cond_broadcast(cont); + plugin_event_container_post_stop(cont); + stop_health_checks(cont->common_config->id); + } + + auto_remove = !should_restart && cont->hostconfig != NULL && cont->hostconfig->auto_remove; + if (auto_remove) { + ret = set_container_to_removal(cont); + if (ret != 0) { + ERROR("Failed to set container %s state to removal", cont->common_config->id); + } + } + + if (container_to_disk(cont)) { + container_unlock(cont); + ERROR("Failed to save container \"%s\" to disk", id); + ret = -1; + goto out; + } + + container_unlock(cont); + + if (auto_remove) { + ret = cleanup_container(cont, true); + if (ret != 0) { + ERROR("Failed to cleanup container %s", cont->common_config->id); + ret = -1; + goto out; + } + } + + break; + case EVENTS_TYPE_EXIT: + case EVENTS_TYPE_STARTING: + case EVENTS_TYPE_RUNNING1: + case EVENTS_TYPE_STOPPING: + case EVENTS_TYPE_ABORTING: + case EVENTS_TYPE_FREEZING: + case EVENTS_TYPE_FROZEN: + case EVENTS_TYPE_THAWED: + case EVENTS_TYPE_OOM: + case EVENTS_TYPE_CREATE: + case EVENTS_TYPE_START: + case EVENTS_TYPE_EXEC_ADDED: + case EVENTS_TYPE_PAUSED1: + case EVENTS_TYPE_MAX_STATE: + default: + /* ignore garbage */ + break; + } +out: + return ret; +} + +static int handle_one(container_t *cont, events_handler_t *handler) +{ + struct linked_list *it = NULL; + struct lcrd_events_format *events = NULL; + + events_handler_lock(handler); + + if (linked_list_empty(&(handler->events_list))) { + handler->has_handler = false; + events_handler_unlock(handler); + return -1; + } + + it = linked_list_first_node(&(handler->events_list)); + linked_list_del(it); + + events_handler_unlock(handler); + + events = (struct lcrd_events_format *)it->elem; + INFO("Received event %s with pid %d", events->id, events->pid); + + if (container_state_changed(cont, events)) { + ERROR("Failed to change container %s state", cont->common_config->id); + } + + free_event(events); + events = NULL; + + free(it); + it = NULL; + + return 0; +} + +/* events handler thread */ +static void *events_handler_thread(void *args) +{ + int ret = 0; + char *name = args; + container_t *cont = NULL; + events_handler_t *handler = NULL; + + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Set thread detach fail"); + goto out; + } + + prctl(PR_SET_NAME, "events_handler"); + + cont = containers_store_get(name); + if (cont == NULL) { + INFO("Container '%s' already removed", name); + goto out; + } + + handler = cont->handler; + if (handler == NULL) { + INFO("Container '%s' event handler already removed", name); + goto out; + } + + while (handle_one(cont, handler) == 0) {} + +out: + container_unref(cont); + free(name); + DAEMON_CLEAR_ERRMSG(); + return NULL; +} + +/* events handler post events */ +int events_handler_post_events(events_handler_t *handler, + const struct lcrd_events_format *event) +{ + int ret = 0; + char *name = NULL; + pthread_t td; + struct lcrd_events_format *post_event = NULL; + struct linked_list *it = NULL; + + if (handler == NULL || event == NULL) { + return -1; + } + + it = util_common_calloc_s(sizeof(struct linked_list)); + if (it == NULL) { + ERROR("Failed to malloc for linked_list"); + return -1; + } + + linked_list_init(it); + + post_event = dup_event(event); + if (post_event == NULL) { + ERROR("Failed to dup event"); + free(it); + return -1; + } + + linked_list_add_elem(it, post_event); + + events_handler_lock(handler); + + linked_list_add_tail(&(handler->events_list), it); + + if (handler->has_handler == false) { + name = util_strdup_s(event->id); + ret = pthread_create(&td, NULL, events_handler_thread, name); + if (ret) { + CRIT("Events handler thread create failed"); + free(name); + goto out; + } + handler->has_handler = true; + } +out: + events_handler_unlock(handler); + return ret; +} diff --git a/src/services/execution/events/events_handler.h b/src/services/execution/events/events_handler.h new file mode 100644 index 0000000..33cacb3 --- /dev/null +++ b/src/services/execution/events/events_handler.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container events handler definition + ******************************************************************************/ +#ifndef __EVENTS_HANDLER_H +#define __EVENTS_HANDLER_H + +#include +#include +#include "linked_list.h" + +#include "liblcrd.h" + +typedef struct _events_handler_t { + pthread_mutex_t mutex; + bool init_mutex; + struct linked_list events_list; + bool has_handler; +} events_handler_t; + + +events_handler_t *events_handler_new(); + +void events_handler_free(events_handler_t *handler); + +int events_handler_post_events(events_handler_t *handler, const struct lcrd_events_format *event); + +#endif /* __EVENTS_HANDLER_H */ diff --git a/src/services/execution/execute/CMakeLists.txt b/src/services/execution/execute/CMakeLists.txt new file mode 100644 index 0000000..0b22b88 --- /dev/null +++ b/src/services/execution/execute/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_execute_srcs) + +set(EXECUTE_SRCS + ${local_execute_srcs} + PARENT_SCOPE + ) diff --git a/src/services/execution/execute/execution.c b/src/services/execution/execute/execution.c new file mode 100644 index 0000000..76e9ee9 --- /dev/null +++ b/src/services/execution/execute/execution.c @@ -0,0 +1,1808 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "log.h" +#include "engine.h" +#include "console.h" +#include "lcrd_config.h" +#include "config.h" +#include "image.h" +#include "securec.h" +#include "execution.h" +#include "verify.h" +#include "container_inspect.h" +#include "containers_store.h" +#include "supervisor.h" +#include "containers_gc.h" +#include "execution_extend.h" +#include "execution_information.h" +#include "execution_stream.h" +#include "execution_create.h" +#include "plugin.h" +#include "health_check.h" +#include "execution_network.h" +#include "runtime_interface.h" +#include "specs_extend.h" +#include "utils.h" +#include "error.h" + +static int filter_by_label(const container_t *cont, const container_get_id_request *request) +{ + int ret = 0; + size_t i, len_key, len_val; + char *p_equal = NULL; + json_map_string_string *labels = NULL; + + if (request->label == NULL) { + ret = 0; + goto out; + } + + if (cont->common_config->config == NULL || cont->common_config->config->labels == NULL || \ + cont->common_config->config->labels->len == 0) { + ERROR("No such container: %s", request->id_or_name); + lcrd_set_error_message("No such container: %s", request->id_or_name); + ret = -1; + goto out; + } + p_equal = strchr(request->label, '='); + if (p_equal == NULL) { + ERROR("Invalid label: %s", request->label); + lcrd_set_error_message("Invalid label: %s", request->label); + ret = -1; + goto out; + } + len_key = (size_t)(p_equal - request->label); + len_val = (strlen(request->label) - len_key) - 1; + labels = cont->common_config->config->labels; + for (i = 0; i < labels->len; i++) { + if (strlen(labels->keys[i]) == len_key && strncmp(labels->keys[i], request->label, len_key) == 0 && \ + strlen(labels->values[i]) == len_val && strncmp(labels->values[i], p_equal + 1, len_val) == 0) { + ret = 0; + goto out; + } + } + ret = -1; + ERROR("No such container: %s", request->id_or_name); + lcrd_set_error_message("No such container: %s", request->id_or_name); + +out: + return ret; +} + +static void pack_get_id_response(container_get_id_response *response, const char *id, uint32_t cc) +{ + if (response == NULL) { + return; + } + + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + if (id != NULL) { + response->id = util_strdup_s(id); + } +} + +/* + * This function gets long id of container by name or short id + */ +static int container_get_id_cb(const container_get_id_request *request, container_get_id_response **response) +{ + char *id = NULL; + uint32_t cc = LCRD_SUCCESS; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_get_id_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + if (!util_valid_container_id_or_name(request->id_or_name)) { + ERROR("Invalid container name: %s", request->id_or_name ? request->id_or_name : ""); + lcrd_set_error_message("Invalid container name: %s", request->id_or_name ? request->id_or_name : ""); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cont = containers_store_get(request->id_or_name); + if (cont == NULL) { + cc = LCRD_ERR_EXEC; + ERROR("No such container: %s", request->id_or_name); + lcrd_set_error_message("No such container: %s", request->id_or_name); + goto pack_response; + } + + if (filter_by_label(cont, request) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + +pack_response: + pack_get_id_response(*response, id, cc); + + container_unref(cont); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int send_signal_to_process(pid_t pid, unsigned long long start_time, uint32_t signal) +{ + if (util_process_alive(pid, start_time) == false) { + if (signal == SIGTERM || signal == SIGKILL) { + WARN("Process %d is not alive", pid); + return 0; + } else { + ERROR("Process (pid=%d) is not alive, can not kill with signal %u", pid, signal); + return -1; + } + } else { + int ret = kill(pid, (int)signal); + if (ret < 0) { + ERROR("Can not kill process (pid=%d) with signal %u: %s", pid, signal, strerror(errno)); + return -1; + } + } + + return 0; +} + +static int umount_dev_tmpfs_for_system_container(const container_t *cont) +{ + if (cont->hostconfig != NULL && cont->hostconfig->system_container) { + char rootfs_dev_path[PATH_MAX] = { 0 }; + if (sprintf_s(rootfs_dev_path, sizeof(rootfs_dev_path), "%s/dev", cont->common_config->base_fs) < 0) { + ERROR("Out of memory"); + return -1; + } + if (umount(rootfs_dev_path) < 0 && errno != EINVAL) { + ERROR("Failed to umount dev tmpfs: %s, error: %s", rootfs_dev_path, strerror(errno)); + return -1; + } + } + return 0; +} + +static int do_clean_container(const container_t *cont, pid_t pid) +{ + int ret = 0; + char *engine_log_path = NULL; + char *loglevel = NULL; + char *logdriver = NULL; + const char *id = cont->common_config->id; + const char *runtime = cont->runtime; + + if (conf_get_daemon_log_config(&loglevel, &logdriver, &engine_log_path) != 0) { + ERROR("Failed to get log config"); + ret = -1; + goto out; + } + + ret = runtime_clean_resource(id, runtime, cont->root_path, engine_log_path, loglevel, pid); + if (ret != 0) { + ERROR("Failed to clean failed started container %s", id); + ret = -1; + goto out; + } + + if (im_umount_container_rootfs(cont->common_config->image_type, cont->common_config->image, id)) { + ERROR("Failed to umount rootfs for container %s", id); + ret = -1; + goto out; + } + + if (umount_dev_tmpfs_for_system_container(cont) < 0) { + ret = -1; + goto out; + } + +out: + free(loglevel); + free(engine_log_path); + free(logdriver); + return ret; +} + +int clean_container_resource(const char *id, const char *runtime, pid_t pid) +{ + int ret = 0; + container_t *cont = NULL; + + if (id == NULL || runtime == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + cont = containers_store_get(id); + if (cont == NULL) { + WARN("No such container:%s", id); + lcrd_set_error_message("No such container:%s", id); + ret = -1; + goto out; + } + + ret = do_clean_container(cont, pid); + if (ret != 0) { + ERROR("Runtime clean container resource failed"); + ret = -1; + goto out; + } +out: + container_unref(cont); + return ret; +} + +static int start_request_check(const container_start_request *h) +{ + int ret = 0; + + if (h == NULL || h->id == NULL) { + ERROR("recive NULL Request id"); + ret = -1; + goto out; + } + + if (!util_valid_container_id_or_name(h->id)) { + ERROR("Invalid container name %s", h->id); + lcrd_set_error_message("Invalid container name %s", h->id); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int prepare_start_state_files(const container_t *cont, char **exit_fifo, int *exit_fifo_fd, char **pid_file) +{ + int ret = 0; + int nret = 0; + char container_state[PATH_MAX] = { 0 }; + char pidfile[PATH_MAX] = { 0 }; + const char *id = cont->common_config->id; + + nret = sprintf_s(container_state, sizeof(container_state), "%s/%s", cont->state_path, id); + if (nret < 0 || nret >= (int)sizeof(container_state)) { + ERROR("Failed to sprintf container_state"); + ret = -1; + goto out; + } + + nret = util_mkdir_p(container_state, TEMP_DIRECTORY_MODE); + if (nret < 0) { + ERROR("Unable to create container state directory %s.", container_state); + ret = -1; + goto out; + } + + nret = sprintf_s(pidfile, sizeof(pidfile), "%s/pid.file", container_state); + if (nret < 0 || nret >= (int)sizeof(pidfile)) { + ERROR("Failed to sprintf pidfile"); + ret = -1; + goto out; + } + *pid_file = util_strdup_s(pidfile); + if (*pid_file == NULL) { + ERROR("Failed to dup pid file in state directory %s", container_state); + ret = -1; + goto out; + } + + *exit_fifo = exit_fifo_create(container_state); + if (*exit_fifo == NULL) { + ERROR("Failed to create exit FIFO in state directory %s", container_state); + ret = -1; + goto out; + } + + *exit_fifo_fd = exit_fifo_open(*exit_fifo); + if (*exit_fifo_fd < 0) { + ERROR("Failed to open exit FIFO %s", *exit_fifo); + ret = -1; + goto out; + } + +out: + return ret; +} + +static void umount_rootfs_on_failure(const container_t *cont) +{ + const char *id = cont->common_config->id; + int nret = im_umount_container_rootfs(cont->common_config->image_type, cont->common_config->image, id); + if (nret != 0) { + ERROR("Failed to umount rootfs for container %s", id); + } +} + +static int chmod_runtime_bundle_permission(const char *runtime) +{ + int ret = 0; + char *bundle_dir = NULL; + char *engine_dir = NULL; + char *root_dir = NULL; + + bundle_dir = conf_get_routine_rootdir(runtime); + if (bundle_dir == NULL) { + ret = -1; + goto error_out; + } + + engine_dir = conf_get_engine_rootpath(); + if (engine_dir == NULL) { + ret = -1; + goto error_out; + } + + root_dir = conf_get_lcrd_rootdir(); + if (root_dir == NULL) { + ret = -1; + goto error_out; + } + + ret = chmod(bundle_dir, USER_REMAP_DIRECTORY_MODE); + if (ret != 0) { + ERROR("Failed to chmod bundle dir '%s' for user remap", bundle_dir); + goto error_out; + } + ret = chmod(engine_dir, USER_REMAP_DIRECTORY_MODE); + if (ret != 0) { + ERROR("Failed to chmod engine dir '%s' for user remap", engine_dir); + goto error_out; + } + ret = chmod(root_dir, USER_REMAP_DIRECTORY_MODE); + if (ret != 0) { + ERROR("Failed to chmod root dir '%s' for user remap", root_dir); + goto error_out; + } + +error_out: + free(bundle_dir); + free(engine_dir); + free(root_dir); + return ret; +} + +static int mount_host_channel(const host_config_host_channel *host_channel, const char *user_remap) +{ + char properties[MOUNT_PROPERTIES_SIZE] = { 0 }; + + if (host_channel == NULL) { + return 0; + } + if (detect_mount(host_channel->path_on_host)) { + return 0; + } + if (sprintf_s(properties, sizeof(properties), "mode=1777,size=%llu", + (long long unsigned int)host_channel->size) < 0) { + ERROR("Failed to generate mount properties"); + return -1; + } + if (mount("tmpfs", host_channel->path_on_host, "tmpfs", + MS_NOEXEC | MS_NOSUID | MS_NODEV, + (void *)properties)) { + ERROR("Failed to mount host path '%s'", host_channel->path_on_host); + return -1; + } + if (user_remap != NULL) { + unsigned int host_uid = 0; + unsigned int host_gid = 0; + unsigned int size = 0; + if (util_parse_user_remap(user_remap, &host_uid, &host_gid, &size)) { + ERROR("Failed to split string '%s'.", user_remap); + return -1; + } + if (chown(host_channel->path_on_host, host_uid, host_gid) != 0) { + ERROR("Failed to chown host path '%s'.", host_channel->path_on_host); + return -1; + } + } + return 0; +} + +static int mount_dev_tmpfs_for_system_container(const container_t *cont) +{ + char rootfs_dev_path[PATH_MAX] = { 0 }; + + if (cont == NULL || cont->hostconfig == NULL || cont->common_config == NULL) { + return 0; + } + if (!cont->hostconfig->system_container) { + return 0; + } + if (sprintf_s(rootfs_dev_path, sizeof(rootfs_dev_path), "%s/dev", cont->common_config->base_fs) < 0) { + ERROR("Out of memory"); + return -1; + } + if (!util_dir_exists(rootfs_dev_path)) { + if (util_mkdir_p(rootfs_dev_path, CONFIG_DIRECTORY_MODE)) { + ERROR("Failed to mkdir '%s'", rootfs_dev_path); + return -1; + } + } + if (mount("tmpfs", rootfs_dev_path, "tmpfs", 0, "size=500000,mode=755")) { + ERROR("Failed to mount dev tmpfs on '%s'", rootfs_dev_path); + return -1; + } + if (cont->hostconfig->user_remap != NULL) { + unsigned int host_uid = 0; + unsigned int host_gid = 0; + unsigned int size = 0; + if (util_parse_user_remap(cont->hostconfig->user_remap, &host_uid, &host_gid, &size)) { + ERROR("Failed to split string '%s'.", cont->hostconfig->user_remap); + return -1; + } + if (chown(rootfs_dev_path, host_uid, host_gid) != 0) { + ERROR("Failed to chown host path '%s'.", rootfs_dev_path); + return -1; + } + } + return 0; +} + +static int prepare_user_remap_config(const container_t *cont) +{ + if (cont == NULL) { + return 0; + } + + if (cont->hostconfig == NULL) { + return 0; + } + + if (cont->hostconfig->user_remap != NULL) { + if (chmod_runtime_bundle_permission(cont->runtime)) { + ERROR("Failed to chmod bundle permission for user remap"); + return -1; + } + } + + if (cont->hostconfig->host_channel != NULL) { + if (mount_host_channel(cont->hostconfig->host_channel, cont->hostconfig->user_remap)) { + ERROR("Failed to mount host channel"); + return -1; + } + } + return 0; +} + +static int generate_user_and_groups_conf(const container_t *cont, oci_runtime_spec_process_user **puser) +{ + int ret = -1; + char *username = NULL; + + if (cont == NULL || cont->common_config == NULL) { + ERROR("Can not found container config"); + return -1; + } + + *puser = util_common_calloc_s(sizeof(oci_runtime_spec_process_user)); + if (*puser == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (cont->common_config->config != NULL) { + username = cont->common_config->config->user; + } + + ret = im_get_user_conf(cont->common_config->image_type, cont->common_config->base_fs, cont->hostconfig, username, + *puser); + if (ret != 0) { + ERROR("Get user failed with '%s'", username ? username : ""); + free_oci_runtime_spec_process_user(*puser); + *puser = NULL; + } + + return ret; +} + +static int create_env_path_dir(const char *env_path) +{ + int ret = 0; + size_t len = 0; + size_t i = 0; + char *dir = NULL; + + len = strlen(env_path); + if (len == 0) { + return 0; + } + dir = util_strdup_s(env_path); + for (i = len - 1; i > 0; i--) { + if (dir[i] == '/') { + dir[i] = '\0'; + break; + } + } + if (strlen(dir) == 0) { + free(dir); + return 0; + } + ret = util_mkdir_p(dir, DEFAULT_SECURE_DIRECTORY_MODE); + free(dir); + return ret; +} + +static int write_env_content(const char *env_path, const char **env, size_t env_len) +{ + int ret = 0; + int fd = -1; + size_t i = 0; + ssize_t nret = 0; + + ret = create_env_path_dir(env_path); + if (ret < 0) { + ERROR("Failed to create env path dir"); + return ret; + } + fd = util_open(env_path, O_WRONLY | O_CREAT | O_TRUNC, DEFAULT_SECURE_FILE_MODE); + if (fd < 0) { + SYSERROR("Failed to create env file: %s", env_path); + ret = -1; + goto out; + } + if (env != NULL) { + for (i = 0; i < env_len; i++) { + size_t len = strlen(env[i]) + strlen("\n") + 1; + char *env_content = NULL; + env_content = util_common_calloc_s(len); + if (env_content == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + if (sprintf_s(env_content, len, "%s\n", env[i]) < 0) { + ERROR("Out of memory"); + free(env_content); + ret = -1; + goto out; + } + nret = util_write_nointr(fd, env_content, strlen(env_content)); + if (nret < 0 || nret != len - 1) { + SYSERROR("Write env file failed"); + free(env_content); + ret = -1; + goto out; + } + free(env_content); + } + } +out: + if (fd >= 0) { + close(fd); + } + return ret; +} + +static int write_env_to_target_file(const container_t *cont) +{ + int ret = 0; + char *env_path = NULL; + + if (cont->hostconfig->env_target_file == NULL || cont->common_config->config == NULL) { + return 0; + } + env_path = util_path_join(cont->common_config->base_fs, cont->hostconfig->env_target_file); + if (env_path == NULL) { + ERROR("Failed to get env target file path: %s", cont->hostconfig->env_target_file); + return -1; + } + ret = write_env_content(env_path, + (const char **)cont->common_config->config->env, + cont->common_config->config->env_len); + free(env_path); + return ret; +} + +static int do_post_start_on_success(const char *id, const char *runtime, + const char *pidfile, int exit_fifo_fd, container_pid_t **pid_info) +{ + int ret = 0; + + *pid_info = container_read_pidfile(pidfile); + if (*pid_info == NULL) { + ERROR("Failed to get started container's pid info, start container fail"); + close(exit_fifo_fd); + ret = -1; + goto out; + } + + // exit_fifo_fd was closed in supervisor_add_exit_monitor + if (supervisor_add_exit_monitor(exit_fifo_fd, *pid_info, id, runtime)) { + ERROR("Failed to add exit monitor to supervisor"); + ret = -1; + } +out: + return ret; +} + +static void do_post_start_on_failure(const container_t *cont, int exit_fifo_fd, + const char *engine_log_path, const char *loglevel) +{ + int ret = 0; + const char *id = cont->common_config->id; + const char *runtime = cont->runtime; + + close(exit_fifo_fd); + + ret = runtime_clean_resource(id, runtime, cont->root_path, engine_log_path, loglevel, 0); + if (ret != 0) { + ERROR("Failed to clean failed started container %s", id); + } + + return; +} + +static int do_start_container(container_t *cont, const char *console_fifos[], bool reset_rm, + container_pid_t **pid_info) +{ + int ret = 0; + int nret = 0; + int exit_fifo_fd = -1; + bool tty = false; + bool open_stdin = false; + unsigned int start_timeout = 0; + char *engine_log_path = NULL; + char *loglevel = NULL; + char *logdriver = NULL; + char *exit_fifo = NULL; + char *pidfile = NULL; + oci_runtime_spec_process_user *puser = NULL; + const char *runtime = cont->runtime; + const char *id = cont->common_config->id; + + if (mount_dev_tmpfs_for_system_container(cont) < 0) { + ret = -1; + goto out; + } + + if (prepare_user_remap_config(cont) != 0) { + ret = -1; + goto out; + } + + if (write_env_to_target_file(cont) < 0) { + ret = -1; + goto out; + } + + if (reset_rm && !reset_restart_manager(cont, true)) { + ERROR("Failed to reset restart manager"); + lcrd_set_error_message("Failed to reset restart manager"); + ret = -1; + goto out; + } + + if (conf_get_daemon_log_config(&loglevel, &logdriver, &engine_log_path) != 0) { + ret = -1; + goto out; + } + + nret = prepare_start_state_files(cont, &exit_fifo, &exit_fifo_fd, &pidfile); + if (nret != 0) { + ret = -1; + goto out; + } + + nret = im_mount_container_rootfs(cont->common_config->image_type, cont->common_config->image, id); + if (nret != 0) { + ERROR("Failed to mount rootfs for container %s", id); + ret = -1; + goto close_exit_fd; + } + + if (generate_user_and_groups_conf(cont, &puser) != 0) { + ret = -1; + goto close_exit_fd; + } + + if (plugin_event_container_pre_start(cont)) { + ERROR("Plugin event pre start failed "); + plugin_event_container_post_stop(cont); /* ignore error */ + ret = -1; + goto close_exit_fd; + } + + start_timeout = conf_get_start_timeout(); + if (cont->common_config->config != NULL) { + tty = cont->common_config->config->tty; + open_stdin = cont->common_config->config->open_stdin; + } + + ret = runtime_start(id, runtime, cont->root_path, tty, open_stdin, engine_log_path, loglevel, + console_fifos, NULL, start_timeout, pidfile, exit_fifo, puser); + if (ret == 0) { + if (do_post_start_on_success(id, runtime, pidfile, exit_fifo_fd, pid_info) != 0) { + ERROR("Failed to do post start on runtime start success"); + ret = -1; + } + } else { + do_post_start_on_failure(cont, exit_fifo_fd, engine_log_path, loglevel); + } + goto out; + +close_exit_fd: + close(exit_fifo_fd); +out: + free_oci_runtime_spec_process_user(puser); + free(loglevel); + free(engine_log_path); + free(logdriver); + free(exit_fifo); + free(pidfile); + if (ret != 0) { + umount_rootfs_on_failure(cont); + (void)umount_dev_tmpfs_for_system_container(cont); + } + return ret; +} + +static bool save_after_auto_remove(container_t *cont) +{ + if (cont->hostconfig != NULL && cont->hostconfig->auto_remove) { + int nret = set_container_to_removal(cont); + if (nret != 0) { + ERROR("Failed to set container %s state to removal", cont->common_config->id); + return true; + } + container_unlock(cont); + nret = cleanup_container(cont, true); + container_lock(cont); + if (nret != 0) { + ERROR("Failed to cleanup container %s", cont->common_config->id); + return true; + } + return false; /*do not save container if already auto removed*/ + } + + return true; +} + +int start_container(container_t *cont, const char *console_fifos[], bool reset_rm) +{ + int ret = 0; + container_pid_t *pid_info = NULL; + + if (cont == NULL || console_fifos == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + container_lock(cont); + + if (reset_rm && is_running(cont->state)) { + ret = 0; + goto out; + } + + if (is_removal_in_progress(cont->state) || is_dead(cont->state)) { + ERROR("Container is marked for removal and cannot be started."); + lcrd_set_error_message("Container is marked for removal and cannot be started."); + ret = -1; + goto out; + } + + if (gc_is_gc_progress(cont->common_config->id)) { + lcrd_set_error_message("You cannot start container %s in garbage collector progress.", cont->common_config->id); + ERROR("You cannot start container %s in garbage collector progress.", cont->common_config->id); + ret = -1; + goto out; + } + + if (container_initialize_networking(cont) != 0) { + ERROR("Failed to initialize networking"); + ret = -1; + goto out; + } + + ret = do_start_container(cont, console_fifos, reset_rm, &pid_info); + if (ret != 0) { + int exit_code = 125; + ERROR("Runtime start container failed"); + container_state_set_error(cont->state, (const char *)g_lcrd_errmsg); + ret = -1; + util_contain_errmsg(g_lcrd_errmsg, &exit_code); + state_set_stopped(cont->state, exit_code); + container_wait_stop_cond_broadcast(cont); + if (save_after_auto_remove(cont)) { + goto save_container; + } else { + goto out; + } + } else { + state_set_running(cont->state, pid_info, true); + cont->common_config->has_been_manually_stopped = false; + init_health_monitor(cont->common_config->id); + goto save_container; + } + +save_container: + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", cont->common_config->id); + ret = -1; + goto out; + } +out: + container_unlock(cont); + free(pid_info); + return ret; +} + +static int prepare_start_io(container_t *cont, const container_start_request *request, + char **fifopath, char *fifos[], int stdinfd, struct io_write_wrapper *stdout_handler, + struct io_write_wrapper *stderr_handler) +{ + int ret = 0; + char *id = NULL; + pthread_t tid = 0; + + id = cont->common_config->id; + + if (request->attach_stdin || request->attach_stdout || request->attach_stderr) { + if (create_daemon_fifos(id, cont->runtime, request->attach_stdin, request->attach_stdout, + request->attach_stderr, "start", fifos, fifopath)) { + ret = -1; + goto out; + } + + if (ready_copy_io_data(-1, true, request->stdin, request->stdout, request->stderr, + stdinfd, stdout_handler, stderr_handler, (const char **)fifos, &tid)) { + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static void pack_start_response(container_start_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + if (id != NULL) { + response->id = util_strdup_s(id); + } +} + +static int container_start_prepare(container_t *cont, const container_start_request *request, + int stdinfd, struct io_write_wrapper *stdout_handler, + struct io_write_wrapper *stderr_handler, + char **fifopath, char *fifos[]) +{ + const char *id = cont->common_config->id; + + if (container_to_disk_locking(cont)) { + ERROR("Failed to save container \"%s\" to disk", id); + lcrd_set_error_message("Failed to save container \"%s\" to disk", id); + return -1; + } + + if (verify_container_settings_start(cont->root_path, id) != 0) { + return -1; + } + + if (prepare_start_io(cont, request, fifopath, fifos, stdinfd, stdout_handler, stderr_handler) != 0) { + return -1; + } + + return 0; +} + +static int container_start_cb(const container_start_request *request, container_start_response **response, + int stdinfd, struct io_write_wrapper *stdout_handler, + struct io_write_wrapper *stderr_handler) +{ + uint32_t cc = LCRD_SUCCESS; + char *id = NULL; + char *fifos[3] = { NULL, NULL, NULL }; + char *fifopath = NULL; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_start_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + if (start_request_check(request)) { + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + cont = containers_store_get(request->id); + if (cont == NULL) { + cc = LCRD_ERR_EXEC; + ERROR("No such container:%s", request->id); + lcrd_set_error_message("No such container:%s", request->id); + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + EVENT("Event: {Object: %s, Type: Starting}", id); + + state_set_starting(cont->state); + + if (is_running(cont->state)) { + INFO("Container is already running"); + goto pack_response; + } + + if (container_start_prepare(cont, request, stdinfd, stdout_handler, stderr_handler, &fifopath, fifos) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (start_container(cont, (const char **)fifos, true)) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: Running}", id); + +pack_response: + delete_daemon_fifos(fifopath, (const char **)fifos); + free(fifos[0]); + free(fifos[1]); + free(fifos[2]); + free(fifopath); + pack_start_response(*response, cc, id); + if (cont != NULL) { + state_reset_starting(cont->state); + container_unref(cont); + } + free_log_prefix(); + malloc_trim(0); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int kill_with_signal(container_t *cont, uint32_t signal) +{ + int ret = 0; + const char *id = cont->common_config->id; + + if (container_exit_on_next(cont)) { + ERROR("Failed to cancel restart manager"); + ret = -1; + goto out; + } + cont->common_config->has_been_manually_stopped = true; + (void)container_to_disk(cont); + + if (!is_running(cont->state)) { + INFO("Container %s is already stopped", id); + ret = 0; + goto out; + } + if (is_restarting(cont->state)) { + INFO("Container %s is currently restarting we do not need to send the signal to the process", id); + ret = 0; + goto out; + } + + stop_health_checks(id); + + ret = send_signal_to_process(cont->state->state->pid, cont->state->state->start_time, signal); + + if (ret != 0) { + ERROR("Failed to grace shutdown container %s", id); + } + +out: + return ret; +} + +static int force_kill(container_t *cont) +{ + int ret = 0; + const char *id = cont->common_config->id; + + ret = kill_with_signal(cont, SIGKILL); + if (ret != 0) { + WARN("Failed to stop Container(%s), try to wait 'STOPPED' for 90 seconds", id); + } + ret = container_wait_stop(cont, 90); + if (ret != 0) { + WARN("Container(%s) stuck for 90 seconds, try to kill the monitor of container", id); + ret = send_signal_to_process(cont->state->state->p_pid, cont->state->state->p_start_time, SIGKILL); + if (ret != 0) { + ERROR("Container stuck for 90 seconds and failed to kill the monitor of container, " + "please check the config"); + lcrd_set_error_message("Container stuck for 90 seconds " + "and failed to kill the monitor of container, please check configuration files"); + goto out; + } + ret = container_wait_stop(cont, -1); + } +out: + return ret; +} + +int stop_container(container_t *cont, int timeout, bool force, bool restart) +{ + int ret = 0; + char *id = NULL; + + if (cont == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + id = cont->common_config->id; + + container_lock(cont); + + // set AutoRemove flag to false before stop so the container won't be + // removed during restart process + if (restart) { + cont->hostconfig->auto_remove = false; + } + + if (!force) { + ret = kill_with_signal(cont, SIGTERM); + if (ret) { + ERROR("Failed to grace shutdown container %s", id); + } + ret = container_wait_stop(cont, timeout); + if (ret != 0) { + ERROR("Failed to wait Container(%s) 'STOPPED' for %d seconds, force killing", + id, timeout); + ret = force_kill(cont); + if (ret != 0) { + ERROR("Failed to force kill container %s", id); + goto out; + } + } + } else { + ret = force_kill(cont); + if (ret != 0) { + ERROR("Failed to force kill container %s", id); + goto out; + } + } +out: + if (restart) { + cont->hostconfig->auto_remove = cont->hostconfig->auto_remove_bak; + } + container_unlock(cont); + return ret; +} + +static int restart_container(container_t *cont) +{ + int ret = 0; + char timebuffer[512] = { 0 }; + const char *id = cont->common_config->id; + const char *runtime = cont->runtime; + const char *rootpath = cont->root_path; + + container_lock(cont); + + if (is_removal_in_progress(cont->state) || is_dead(cont->state)) { + ERROR("Container is marked for removal and cannot be started."); + lcrd_set_error_message("Container is marked for removal and cannot be started."); + goto out; + } + + (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); + + ret = runtime_restart(id, runtime, rootpath); + if (ret == -2) { + goto out; + } + + if (ret == 0) { + update_start_and_finish_time(cont->state, timebuffer); + } + + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", cont->common_config->id); + ret = -1; + goto out; + } +out: + container_unlock(cont); + return ret; +} + +static uint32_t stop_and_start(container_t *cont, int timeout) +{ + int ret = 0; + uint32_t cc = LCRD_SUCCESS; + const char *console_fifos[3] = { NULL, NULL, NULL }; + const char *id = cont->common_config->id; + + ret = stop_container(cont, timeout, false, true); + if (ret != 0) { + cc = LCRD_ERR_EXEC; + container_state_set_error(cont->state, (const char *)g_lcrd_errmsg); + goto out; + } + + /* begin start container */ + state_set_starting(cont->state); + if (container_to_disk_locking(cont)) { + ERROR("Failed to save container \"%s\" to disk", id); + cc = LCRD_ERR_EXEC; + lcrd_set_error_message("Failed to save container \"%s\" to disk", id); + goto out; + } + + if (verify_container_settings_start(cont->root_path, id)) { + cc = LCRD_ERR_EXEC; + goto out; + } + if (is_running(cont->state)) { + INFO("Container is already running"); + goto out; + } + + if (start_container(cont, console_fifos, true)) { + cc = LCRD_ERR_EXEC; + goto out; + } +out: + state_reset_starting(cont->state); + return cc; +} + +static void pack_restart_response(container_restart_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + if (id != NULL) { + response->id = util_strdup_s(id); + } +} + +static uint32_t do_restart_container(container_t *cont, int timeout) +{ + int ret = 0; + + ret = restart_container(cont); + if (ret == -1) { + container_state_set_error(cont->state, (const char *)g_lcrd_errmsg); + return LCRD_ERR_EXEC; + } else if (ret == -2) { + /* runtime don't implement restart, use stop and start */ + return stop_and_start(cont, timeout); + } + + return LCRD_SUCCESS; +} + +static int container_restart_cb(const container_restart_request *request, + container_restart_response **response) +{ + int timeout = 0; + uint32_t cc = LCRD_SUCCESS; + char *name = NULL; + char *id = NULL; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_restart_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + timeout = request->timeout; + + if (!util_valid_container_id_or_name(name)) { + cc = LCRD_ERR_EXEC; + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + goto pack_response; + } + + cont = containers_store_get(name); + if (cont == NULL) { + cc = LCRD_ERR_EXEC; + ERROR("No such container: %s", name); + lcrd_set_error_message("No such container:%s", name); + goto pack_response; + } + + id = cont->common_config->id; + + set_log_prefix(id); + + EVENT("Event: {Object: %s, Type: restarting}", id); + + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("You cannot restart container %s in garbage collector progress.", id); + ERROR("You cannot restart container %s in garbage collector progress.", id); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cc = do_restart_container(cont, timeout); + if (cc != LCRD_SUCCESS) { + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: Restarted}", id); +pack_response: + pack_restart_response(*response, cc, id); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static void pack_stop_response(container_stop_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + if (id != NULL) { + response->id = util_strdup_s(id); + } +} + +static int container_stop_cb(const container_stop_request *request, + container_stop_response **response) +{ + int timeout = 0; + bool force = false; + char *name = NULL; + char *id = NULL; + uint32_t cc = LCRD_SUCCESS; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_stop_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + force = request->force; + timeout = request->timeout; + + if (name == NULL) { + ERROR("Stop: receive NULL id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cont = containers_store_get(name); + if (cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + EVENT("Event: {Object: %s, Type: Stopping}", id); + + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("You cannot stop container %s in garbage collector progress.", id); + ERROR("You cannot stop container %s in garbage collector progress.", id); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (stop_container(cont, timeout, force, false)) { + cc = LCRD_ERR_EXEC; + container_state_set_error(cont->state, (const char *)g_lcrd_errmsg); + goto pack_response; + } + + INFO("Stoped Container:%s", id); + +pack_response: + pack_stop_response(*response, cc, id); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int kill_container(container_t *cont, uint32_t signal) +{ + int ret = 0; + char *id = NULL; + + id = cont->common_config->id; + + container_lock(cont); + + if (!is_running(cont->state)) { + ERROR("Cannot kill container: Container %s is not running", id); + lcrd_set_error_message("Cannot kill container: Container %s is not running", id); + ret = -1; + goto out; + } + + if (signal == 0 || signal == SIGKILL) { + ret = force_kill(cont); + } else { + ret = kill_with_signal(cont, signal); + } + + if (ret != 0) { + ret = -1; + goto out; + } + +out: + container_unlock(cont); + return ret; +} + +static void pack_kill_response(container_kill_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + if (id != NULL) { + response->id = util_strdup_s(id); + } +} + +static int container_kill_cb(const container_kill_request *request, + container_kill_response **response) +{ + int ret = 0; + char *name = NULL; + char *id = NULL; + uint32_t signal = 0; + uint32_t cc = LCRD_SUCCESS; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_kill_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + signal = request->signal; + + if (name == NULL) { + ERROR("Kill: receive NULL id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + if (!util_valid_container_id_or_name(name)) { + lcrd_set_error_message("Invalid container name %s", name); + ERROR("Invalid container name %s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (!util_check_signal_valid((int)signal)) { + lcrd_set_error_message("Not supported signal %d", signal); + ERROR("Not supported signal %d", signal); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cont = containers_store_get(name); + if (cont == NULL) { + cc = LCRD_ERR_EXEC; + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + EVENT("Event: {Object: %s, Type: Killing, Signal:%u}", id, signal); + + + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("You cannot kill container %s in garbage collector progress.", id); + ERROR("You cannot kill container %s in garbage collector progress.", id); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + ret = kill_container(cont, signal); + if (ret != 0) { + cc = LCRD_ERR_EXEC; + container_state_set_error(cont->state, (const char *)g_lcrd_errmsg); + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: Killed, Signal:%u}", id, signal); + +pack_response: + pack_kill_response(*response, cc, id); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int do_runtime_rm_helper(const char *id, const char *runtime, const char *rootpath) +{ + int ret = 0; + /* notes: we do not use force stop in lcr, we do stop above */ + if (runtime_rm(id, runtime, rootpath)) { + if (strstr(g_lcrd_errmsg, "No such container") != NULL) { + // container root path may been corrupted, try to remove by daemon + char cont_root_path[PATH_MAX] = { 0 }; + WARN("container %s root path may been corrupted, try to remove by daemon", id); + ret = sprintf_s(cont_root_path, sizeof(cont_root_path), "%s/%s", rootpath, id); + if (ret < 0) { + ERROR("Failed to sprintf container_state"); + ret = -1; + goto out; + } + ret = util_recursive_rmdir(cont_root_path, 0); + if (ret != 0) { + const char *tmp_err = (errno != 0) ? strerror(errno) : "error"; + ERROR("Failed to delete container's root directory %s: %s", cont_root_path, tmp_err); + lcrd_set_error_message("Failed to delete container's root directory %s: %s", cont_root_path, tmp_err); + ret = -1; + goto out; + } + } else { + ERROR("Runtime remove container failed"); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int do_cleanup_container_resources(container_t *cont) +{ + int ret = 0; + char *id = NULL; + char *name = NULL; + char *statepath = NULL; + char container_state[PATH_MAX] = { 0 }; + const char *runtime = NULL; + const char *rootpath = NULL; + container_t *cont_tmp = NULL; + + container_lock(cont); + + id = cont->common_config->id; + name = cont->common_config->name; + statepath = cont->state_path; + runtime = cont->runtime; + rootpath = cont->root_path; + + /* check if container was deregistered by previous rm already */ + cont_tmp = containers_store_get(id); + if (cont_tmp == NULL) { + ret = 0; + goto out; + } + container_unref(cont_tmp); + + (void)container_to_disk(cont); + + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("You cannot remove container %s in garbage collector progress.", id); + ERROR("You cannot remove container %s in garbage collector progress.", id); + ret = -1; + goto out; + } + + ret = sprintf_s(container_state, sizeof(container_state), "%s/%s", statepath, id); + if (ret < 0) { + ERROR("Failed to sprintf container_state"); + ret = -1; + goto out; + } + ret = util_recursive_rmdir(container_state, 0); + if (ret != 0) { + ERROR("Failed to delete container's state directory %s: %s", container_state, strerror(errno)); + ret = -1; + goto out; + } + + if (im_remove_container_rootfs(cont->common_config->image_type, id)) { + ERROR("Failed to remove rootfs for container %s", id); + ret = -1; + goto out; + } + + umount_host_channel(cont->hostconfig->host_channel); + + if (do_runtime_rm_helper(id, runtime, rootpath) != 0) { + ret = -1; + goto out; + } + + /* broadcast remove condition */ + container_wait_rm_cond_broadcast(cont); + + if (!containers_store_remove(id)) { + ERROR("Failed to remove container '%s' from containers store", id); + ret = -1; + goto out; + } + + if (!name_index_remove(name)) { + ERROR("Failed to remove '%s' from name index", name); + ret = -1; + } + +out: + container_unlock(cont); + return ret; +} + +int set_container_to_removal(const container_t *cont) +{ + int ret = 0; + char *id = NULL; + + if (cont == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + id = cont->common_config->id; + + bool removal_progress = state_set_removal_in_progress(cont->state); + if (removal_progress) { + lcrd_set_error_message("Container:%s was already in removal progress", id); + ERROR("Container:%s was already in removal progress", id); + ret = -1; + goto out; + } +out: + return ret; +} + +int cleanup_container(container_t *cont, bool force) +{ + int ret = 0; + char *id = NULL; + + if (cont == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + id = cont->common_config->id; + + if (is_running(cont->state)) { + if (!force) { + lcrd_set_error_message("You cannot remove a running container %s. " + "Stop the container before attempting removal or use -f", id); + ERROR("You cannot remove a running container %s. Stop the container before attempting removal or use -f", + id); + ret = -1; + goto reset_removal_progress; + } + ret = stop_container(cont, 3, force, false); + if (ret != 0) { + lcrd_try_set_error_message("Could not stop running container %s, cannot remove", id); + ERROR("Could not stop running container %s, cannot remove", id); + ret = -1; + goto reset_removal_progress; + } + } + + plugin_event_container_post_remove(cont); + + ret = do_cleanup_container_resources(cont); + if (ret != 0) { + goto reset_removal_progress; + } + + goto out; + +reset_removal_progress: + state_reset_removal_in_progress(cont->state); +out: + return ret; +} + +static void pack_delete_response(container_delete_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + if (id != NULL) { + response->id = util_strdup_s(id); + } +} + +static int container_delete_cb(const container_delete_request *request, + container_delete_response **response) +{ + bool force = false; + uint32_t cc = LCRD_SUCCESS; + char *name = NULL; + char *id = NULL; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_delete_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + force = request->force; + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + cont = containers_store_get(name); + if (cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + EVENT("Event: {Object: %s, Type: Deleting}", id); + + container_lock(cont); + int nret = set_container_to_removal(cont); + container_unlock(cont); + if (nret != 0) { + ERROR("Failed to set container %s state to removal", id); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (cleanup_container(cont, force)) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: Deleted}", id); + +pack_response: + pack_delete_response(*response, cc, id); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +void container_callback_init(service_container_callback_t *cb) +{ + cb->get_id = container_get_id_cb; + cb->create = container_create_cb; + cb->start = container_start_cb; + cb->stop = container_stop_cb; + cb->restart = container_restart_cb; + cb->kill = container_kill_cb; + cb->remove = container_delete_cb; + + container_information_callback_init(cb); + container_stream_callback_init(cb); + container_extend_callback_init(cb); +} + diff --git a/src/services/execution/execute/execution.h b/src/services/execution/execute/execution.h new file mode 100644 index 0000000..b6c7792 --- /dev/null +++ b/src/services/execution/execute/execution.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + *******************************************************************************/ + +#ifndef __EXECUTION_H_ +#define __EXECUTION_H_ + +#include "callback.h" +#include "container_unix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void container_callback_init(service_container_callback_t *cb); + +int start_container(container_t *cont, const char *console_fifos[], bool reset_rm); + +int clean_container_resource(const char *id, const char *runtime, pid_t pid); + +int cleanup_container(container_t *cont, bool force); + +int stop_container(container_t *cont, int timeout, bool force, bool restart); + +int set_container_to_removal(const container_t *cont); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/execution/execute/execution_create.c b/src/services/execution/execute/execution_create.c new file mode 100644 index 0000000..e68db9e --- /dev/null +++ b/src/services/execution/execute/execution_create.c @@ -0,0 +1,902 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container create callback function definition + ********************************************************************************/ +#include "execution_create.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "engine.h" +#include "console.h" +#include "lcrd_config.h" +#include "config.h" +#include "securec.h" +#include "specs.h" +#include "verify.h" +#include "containers_store.h" +#include "execution_network.h" +#include "runtime_interface.h" +#include "plugin.h" +#include "image.h" +#include "utils.h" +#include "error.h" +#include "constants.h" + +static int create_request_check(const container_create_request *request) +{ + int ret = 0; + parser_error err = NULL; + + if (request == NULL) { + ERROR("Receive NULL container id"); + ret = -1; + goto out; + } + + if ((request->rootfs == NULL && request->image == NULL)) { + ERROR("Container image or rootfs error"); + ret = -1; + goto out; + } + + if (request->image != NULL && !util_valid_image_name(request->image)) { + ERROR("invalid image name %s", request->image); + lcrd_set_error_message("Invalid image name '%s'", request->image); + ret = -1; + goto out; + } + + if (request->runtime == NULL) { + ERROR("Receive NULL Request runtime"); + ret = -1; + goto out; + } + + if (!util_valid_runtime_name(request->runtime)) { + ERROR("Invalid runtime name:%s", request->runtime); + lcrd_set_error_message("Invalid runtime name (%s), only \"lcr\" supported.", + request->runtime); + ret = -1; + goto out; + } + + if (request->hostconfig == NULL) { + ERROR("Receive NULL Request hostconfig"); + ret = -1; + goto out; + } + + if (request->customconfig == NULL) { + ERROR("Receive NULL Request customconfig"); + ret = -1; + goto out; + } + +out: + free(err); + return ret; +} + +static host_config *get_host_spec_from_request(const container_create_request *request) +{ + parser_error err = NULL; + host_config *host_spec = NULL; + + host_spec = host_config_parse_data(request->hostconfig, NULL, &err); + if (host_spec == NULL) { + ERROR("Failed to parse host config data:%s", err); + } + + free(err); + return host_spec; +} + +static int merge_external_rootfs_to_host_config(host_config *host_spec, const char *external_rootfs) +{ + if (host_spec == NULL) { + return -1; + } + host_spec->external_rootfs = external_rootfs != NULL ? util_strdup_s(external_rootfs) : NULL; + + return 0; +} + +static host_config *get_host_spec(const container_create_request *request) +{ + host_config *host_spec = NULL; + + host_spec = get_host_spec_from_request(request); + if (host_spec == NULL) { + return NULL; + } + + if (merge_external_rootfs_to_host_config(host_spec, request->rootfs) != 0) { + goto error_out; + } + + if (verify_host_config_settings(host_spec, false)) { + ERROR("Failed to verify host config settings"); + goto error_out; + } + + return host_spec; + +error_out: + free_host_config(host_spec); + return NULL; +} + +static container_custom_config *get_custom_spec_from_request(const container_create_request *request) +{ + parser_error err = NULL; + + container_custom_config *custom_spec = container_custom_config_parse_data(request->customconfig, NULL, &err); + if (custom_spec == NULL) { + ERROR("Failed to parse custom config data:%s", err); + } + + free(err); + return custom_spec; +} + +static int add_default_log_config_to_custom_spec(const char *id, const char *runtime_root, + container_custom_config *custom_config) +{ + int ret = 0; + + /* generate default log path */ + if (custom_config->log_config == NULL) { + custom_config->log_config = util_common_calloc_s(sizeof(container_custom_config_log_config)); + if (custom_config->log_config == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + } + + if (custom_config->log_config->log_file == NULL) { + char default_path[PATH_MAX] = { 0 }; + int nret = sprintf_s(default_path, PATH_MAX, "%s/%s/console.log", runtime_root, id); + if (nret < 0) { + ERROR("Create default log path for container %s failed", id); + ret = -1; + goto out; + } + custom_config->log_config->log_file = util_strdup_s(default_path); + } + +out: + return ret; +} + +static container_custom_config *get_custom_spec(const char *id, const char *runtime_root, + const container_create_request *request) +{ + container_custom_config *custom_spec = NULL; + + custom_spec = get_custom_spec_from_request(request); + if (custom_spec == NULL) { + return NULL; + } + + if (add_default_log_config_to_custom_spec(id, runtime_root, custom_spec)) { + goto error_out; + } + + return custom_spec; + +error_out: + free_container_custom_config(custom_spec); + return NULL; +} + + +static int generateID(char *id, size_t len) +{ + int fd = -1; + int num = 0; + size_t i; + const int m = 256; + + len = len / 2; + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) { + ERROR("Failed to open /dev/urandom"); + return -1; + } + for (i = 0; i < len; i++) { + int nret; + if (read(fd, &num, sizeof(int)) < 0) { + ERROR("Failed to read urandom value"); + close(fd); + return -1; + } + unsigned char rs = (unsigned char)(num % m); + nret = sprintf_s((id + i * 2), ((len - i) * 2 + 1), "%02x", (unsigned int)rs); + if (nret < 0) { + close(fd); + return -1; + } + } + close(fd); + id[i * 2] = '\0'; + return 0; +} + +static oci_runtime_spec *merge_config(const char *id, const char *image_type, const char *image_name, + const char *ext_config_image, host_config *host_spec, + container_custom_config *custom_spec, + container_config_v2_common_config *v2_spec, char **real_rootfs) +{ + oci_runtime_spec *oci_spec = NULL; + + oci_spec = merge_container_config(id, image_type, image_name, ext_config_image, host_spec, custom_spec, + v2_spec, real_rootfs); + if (oci_spec == NULL) { + ERROR("Failed to merge config"); + goto error_out; + } + + if (merge_global_config(oci_spec) != 0) { + ERROR("Failed to merge global config"); + goto error_out; + } + + return oci_spec; + +error_out: + free_oci_runtime_spec(oci_spec); + return NULL; +} + +static int merge_config_for_syscontainer(const container_create_request *request, const host_config *host_spec, + const container_custom_config *custom_spec, const oci_runtime_spec *oci_spec) +{ + int ret = 0; + + if (!host_spec->system_container || request->rootfs == NULL) { + return 0; + } + + if (merge_network(host_spec, request->rootfs, custom_spec->hostname) != 0) { + ERROR("Failed to merge network config"); + ret = -1; + goto out; + } + + if (append_json_map_string_string(oci_spec->annotations, "rootfs.mount", request->rootfs)) { + ERROR("Realloc annotations failed"); + ret = -1; + goto out; + } + +out: + return ret; +} + + +static char *try_generate_id() +{ + int i = 0; + int max_time = 10; + char *id = NULL; + char *value = NULL; + + id = util_common_calloc_s(sizeof(char) * (CONTAINER_ID_MAX_LEN + 1)); + if (id == NULL) { + ERROR("Out of memory"); + return NULL; + } + + for (i = 0; i < max_time; i++) { + if (generateID(id, (size_t)CONTAINER_ID_MAX_LEN)) { + ERROR("Generate id failed"); + goto err_out; + } + + value = name_index_get(id); + if (value != NULL) { + continue; + } else { + goto out; + } + } + +err_out: + free(id); + id = NULL; +out: + return id; +} + +static int register_new_container(const char *id, const char *runtime, host_config **host_spec, + container_config_v2_common_config **v2_spec) +{ + int ret = -1; + bool registed = false; + char *runtime_root = NULL; + char *runtime_stat = NULL; + container_t *cont = NULL; + + runtime_root = conf_get_routine_rootdir(runtime); + if (runtime_root == NULL) { + goto out; + } + + runtime_stat = conf_get_routine_statedir(runtime); + if (runtime_stat == NULL) { + goto out; + } + + cont = container_new(runtime, runtime_root, runtime_stat, host_spec, v2_spec); + if (cont == NULL) { + ERROR("Failed to create container '%s'", id); + goto out; + } + + if (container_to_disk_locking(cont)) { + ERROR("Failed to save container '%s'", id); + goto out; + } + + registed = containers_store_add(id, cont); + if (!registed) { + ERROR("Failed to register container '%s'", id); + goto out; + } + + ret = 0; +out: + free(runtime_root); + free(runtime_stat); + if (ret != 0) { + container_unref(cont); + } + return ret; +} + +static int maintain_container_id(const container_create_request *request, char **out_id, char **out_name) +{ + int ret = 0; + char *id = NULL; + char *name = NULL; + + id = try_generate_id(); + if (id == NULL) { + ERROR("Failed to generate container ID"); + lcrd_set_error_message("Failed to generate container ID"); + ret = -1; + goto out; + } + + set_log_prefix(id); + + if (request->id != NULL) { + name = util_strdup_s(request->id); + } else { + name = util_strdup_s(id); + } + + if (!util_valid_container_name(name)) { + ERROR("Invalid container name (%s), only [a-zA-Z0-9][a-zA-Z0-9_.-]+$ are allowed.", name); + lcrd_set_error_message("Invalid container name (%s), only [a-zA-Z0-9][a-zA-Z0-9_.-]+$ are allowed.", name); + ret = -1; + goto out; + } + + EVENT("Event: {Object: %s, Type: Creating %s}", id, name); + + if (!name_index_add(name, id)) { + ERROR("Name %s is in use", name); + lcrd_set_error_message("Conflict. The name \"%s\" is already in use by container %s. " + "You have to remove (or rename) that container to be able to reuse that name.", + name, name); + ret = -1; + goto out; + } + +out: + *out_id = id; + *out_name = name; + return ret; +} + +static char *get_runtime_from_request(const container_create_request *request) +{ + return strings_to_lower(request->runtime); +} + +static void pack_create_response(container_create_response *response, const char *id, uint32_t cc) +{ + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + if (id != NULL) { + response->id = util_strdup_s(id); + } +} + +static int prepare_host_channel(const host_config_host_channel *host_channel, const char *user_remap) +{ + unsigned int host_uid = 0; + unsigned int host_gid = 0; + unsigned int size = 0; + + if (host_channel == NULL) { + return 0; + } + if (util_dir_exists(host_channel->path_on_host)) { + ERROR("Host path '%s' already exist", host_channel->path_on_host); + return -1; + } + if (util_mkdir_p(host_channel->path_on_host, HOST_PATH_MODE)) { + ERROR("Failed to create host path '%s'.", host_channel->path_on_host); + return -1; + } + if (user_remap != NULL) { + if (util_parse_user_remap(user_remap, &host_uid, &host_gid, &size)) { + ERROR("Failed to split string '%s'.", user_remap); + return -1; + } + if (chown(host_channel->path_on_host, host_uid, host_gid) != 0) { + ERROR("Failed to chown host path '%s'.", host_channel->path_on_host); + return -1; + } + } + return 0; +} + +void umount_host_channel(const host_config_host_channel *host_channel) +{ + if (host_channel == NULL) { + return; + } + + if (detect_mount(host_channel->path_on_host)) { + if (umount2(host_channel->path_on_host, MNT_DETACH)) { + ERROR("Failed to umount the target: %s", host_channel->path_on_host); + } + } + if (util_recursive_rmdir(host_channel->path_on_host, 0)) { + ERROR("Failed to delete host path: %s", host_channel->path_on_host); + } +} + +static int generate_merged_oci_config_json(const char *id, oci_runtime_spec *oci_spec, + char **oci_config_data) +{ + char *err = NULL; + + if (verify_container_settings(oci_spec)) { + ERROR("Failed to verify container settings"); + return -1; + } + + /* modify oci_spec by plugin. */ + if (plugin_event_container_pre_create(id, oci_spec)) { + ERROR("Plugin event pre create failed"); + (void)plugin_event_container_post_remove2(id, oci_spec); /* ignore error */ + return -1; + } + + *oci_config_data = oci_runtime_spec_generate_json(oci_spec, 0, &err); + if (*oci_config_data == NULL) { + ERROR("Failed to generate runtime spec json,erro:%s", err); + free(err); + lcrd_set_error_message("Failed to generate runtime spec json"); + return -1; + } + free(err); + return 0; +} + +static int create_container_root_dir(const char *id, const char *runtime_root) +{ + int ret = 0; + int nret; + char container_root[PATH_MAX] = { 0x00 }; + mode_t mask = umask(S_IWOTH); + + nret = sprintf_s(container_root, sizeof(container_root), "%s/%s", runtime_root, id); + if (nret < 0) { + ret = -1; + goto out; + } + // create container dir + nret = util_mkdir_p(container_root, CONFIG_DIRECTORY_MODE); + if (nret != 0 && errno != EEXIST) { + SYSERROR("Failed to create container path %s", container_root); + ret = -1; + goto out; + } + +out: + umask(mask); + return ret; +} + +static int delete_container_root_dir(const char *id, const char *runtime_root) +{ + int ret = 0; + char container_root[PATH_MAX] = { 0x00 }; + + ret = sprintf_s(container_root, sizeof(container_root), "%s/%s", runtime_root, id); + if (ret < 0) { + ERROR("Failed to sprintf invalid root directory %s/%s", runtime_root, id); + ret = -1; + goto out; + } + + ret = util_recursive_rmdir(container_root, 0); + if (ret != 0) { + ERROR("Failed to delete container's state directory %s", container_root); + ret = -1; + goto out; + } + +out: + return ret; +} + +static container_config_v2_common_config *get_config_v2_spec(const char *id, const char *runtime_root, + const host_config *host_spec) +{ + container_config_v2_common_config *v2_spec = NULL; + + v2_spec = util_common_calloc_s(sizeof(container_config_v2_common_config)); + if (v2_spec == NULL) { + ERROR("Failed to malloc container_config_v2_common_config"); + return NULL; + } + + /* set network config to v2_spec */ + if (init_container_network_confs(id, runtime_root, host_spec, v2_spec) != 0) { + ERROR("Init Network files failed"); + goto error_out; + } + + return v2_spec; + +error_out: + free_container_config_v2_common_config(v2_spec); + return NULL; +} + +/* save config v2 */ +static int create_v2_config_json(const char *id, const char *runtime_root, container_config_v2_common_config *v2_spec) +{ + int ret = 0; + char *json_v2 = NULL; + parser_error err = NULL; + container_config_v2 config_v2 = { 0 }; + container_config_v2_state state = { 0 }; + + config_v2.common_config = v2_spec; + config_v2.state = &state; + + json_v2 = container_config_v2_generate_json(&config_v2, NULL, &err); + if (json_v2 == NULL) { + ERROR("Failed to generate container config V2 json string:%s", err ? err : " "); + ret = -1; + goto out; + } + + ret = save_config_v2_json(id, runtime_root, json_v2); + if (ret != 0) { + ERROR("Failed to save container config V2 json to file"); + ret = -1; + goto out; + } + +out: + free(json_v2); + free(err); + return ret; +} + +/* save host config */ +static int create_host_config_json(const char *id, const char *runtime_root, const host_config *host_spec) +{ + int ret = 0; + char *json_host_config = NULL; + parser_error err = NULL; + + json_host_config = host_config_generate_json(host_spec, NULL, &err); + if (json_host_config == NULL) { + ERROR("Failed to generate container host config json string:%s", err ? err : " "); + ret = -1; + goto out; + } + + ret = save_host_config(id, runtime_root, json_host_config); + if (ret != 0) { + ERROR("Failed to save container host config json to file"); + ret = -1; + goto out; + } + +out: + free(json_host_config); + free(err); + + return ret; +} + +static int save_container_config_before_create(const char *id, const char *runtime_root, host_config *host_spec, + container_config_v2_common_config *v2_spec) +{ + if (create_v2_config_json(id, runtime_root, v2_spec) != 0) { + return -1; + } + + if (create_host_config_json(id, runtime_root, host_spec) != 0) { + return -1; + } + + return 0; +} + +static host_config_host_channel *dup_host_channel(const host_config_host_channel *channel) +{ + host_config_host_channel *dup_channel = NULL; + + if (channel == NULL) { + return NULL; + } + + dup_channel = (host_config_host_channel *)util_common_calloc_s(sizeof(host_config_host_channel)); + if (dup_channel == NULL) { + ERROR("Out of memory"); + return NULL; + } + + dup_channel->path_on_host = channel->path_on_host != NULL ? util_strdup_s(channel->path_on_host) : NULL; + dup_channel->path_in_container = channel->path_in_container != NULL ? + util_strdup_s(channel->path_in_container) : NULL; + dup_channel->permissions = channel->permissions != NULL ? util_strdup_s(channel->permissions) : NULL; + dup_channel->size = channel->size; + + return dup_channel; +} + +static int verify_merged_custom_config(const container_custom_config *custom_spec) +{ + if (verify_health_check_parameter(custom_spec) != 0) { + return -1; + } + + return 0; +} + +int container_create_cb(const container_create_request *request, + container_create_response **response) +{ + uint32_t cc = LCRD_SUCCESS; + char *real_rootfs = NULL; + char *image_type = NULL; + char *runtime_root = NULL; + char *oci_config_data = NULL; + char *runtime = NULL; + char *name = NULL; + char *id = NULL; + const char *image_name = NULL; + const char *ext_config_image = NULL; + oci_runtime_spec *oci_spec = NULL; + host_config *host_spec = NULL; + container_custom_config *custom_spec = NULL; + container_config_v2_common_config *v2_spec = NULL; + host_config_host_channel *host_channel = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_create_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (create_request_check(request) != 0) { + ERROR("Invalid create container request"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + if (maintain_container_id(request, &id, &name) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + image_type = im_get_image_type(request->image, request->rootfs); + if (image_type == NULL) { + cc = LCRD_ERR_EXEC; + goto clean_nameindex; + } + + if (request->rootfs != NULL) { + image_name = request->rootfs; + // Do not use none image because none image has no config. + if (strcmp(request->image, "none") && strcmp(request->image, "none:latest")) { + ext_config_image = request->image; + } + } else { + image_name = request->image; + } + + // Check if config image exist if provided. + if (ext_config_image != NULL) { + if (!im_config_image_exist(ext_config_image)) { + cc = LCRD_ERR_EXEC; + goto clean_nameindex; + } + } + + runtime = get_runtime_from_request(request); + if (runtime == NULL) { + cc = LCRD_ERR_INPUT; + goto clean_nameindex; + } + + runtime_root = conf_get_routine_rootdir(runtime); + if (runtime_root == NULL) { + cc = LCRD_ERR_EXEC; + goto clean_nameindex; + } + + if (create_container_root_dir(id, runtime_root) != 0) { + cc = LCRD_ERR_EXEC; + goto clean_nameindex; + } + + host_spec = get_host_spec(request); + if (host_spec == NULL) { + cc = LCRD_ERR_INPUT; + goto clean_container_root_dir; + } + + custom_spec = get_custom_spec(id, runtime_root, request); + if (custom_spec == NULL) { + cc = LCRD_ERR_INPUT; + goto clean_container_root_dir; + } + + v2_spec = get_config_v2_spec(id, runtime_root, host_spec); + if (v2_spec == NULL) { + ERROR("Failed to malloc container_config_v2_common_config"); + cc = LCRD_ERR_EXEC; + goto clean_container_root_dir; + } + + if (v2_spec_make_basic_info(id, name, request->image, image_type, v2_spec) != 0) { + ERROR("Failed to malloc container_config_v2_common_config"); + cc = LCRD_ERR_EXEC; + goto clean_container_root_dir; + } + + if (save_container_config_before_create(id, runtime_root, host_spec, v2_spec) != 0) { + cc = LCRD_ERR_EXEC; + goto clean_container_root_dir; + } + + host_channel = dup_host_channel(host_spec->host_channel); + + if (prepare_host_channel(host_channel, host_spec->user_remap)) { + ERROR("Failed to prepare host channel with '%s'", host_spec->host_channel); + cc = LCRD_ERR_EXEC; + goto clean_container_root_dir; + } + oci_spec = merge_config(id, image_type, image_name, ext_config_image, host_spec, custom_spec, v2_spec, + &real_rootfs); + if (oci_spec == NULL) { + cc = LCRD_ERR_EXEC; + goto clean_rootfs; + } + if (real_rootfs == NULL) { + ERROR("Can not found rootfs"); + cc = LCRD_ERR_INPUT; + goto clean_rootfs; + } + + if (verify_merged_custom_config(custom_spec)) { + cc = LCRD_ERR_INPUT; + goto clean_rootfs; + } + + if (merge_config_for_syscontainer(request, host_spec, custom_spec, oci_spec) != 0) { + ERROR("Failed to merge config for syscontainer"); + cc = LCRD_ERR_EXEC; + goto clean_rootfs; + } + + if (generate_merged_oci_config_json(id, oci_spec, &oci_config_data) != 0) { + cc = LCRD_ERR_EXEC; + goto clean_rootfs; + } + + if (runtime_create(id, runtime, real_rootfs, oci_config_data) != 0) { + ERROR("Runtime create container failed"); + cc = LCRD_ERR_EXEC; + goto clean_rootfs; + } + + if (v2_spec_merge_custom_spec(custom_spec, v2_spec) != 0) { + ERROR("Failed to malloc container_config_v2_common_config"); + cc = LCRD_ERR_EXEC; + goto clean_on_error; + } + + if (v2_spec_merge_oci_spec(oci_spec, v2_spec) != 0) { + ERROR("Failed to malloc container_config_v2_common_config"); + cc = LCRD_ERR_EXEC; + goto clean_on_error; + } + + if (register_new_container(id, runtime, &host_spec, &v2_spec)) { + ERROR("Failed to register new container"); + cc = LCRD_ERR_EXEC; + goto clean_on_error; + } + + EVENT("Event: {Object: %s, Type: Created %s}", id, name); + goto pack_response; +clean_on_error: + (void)runtime_rm(id, runtime, runtime_root); + +clean_rootfs: + (void)im_remove_container_rootfs(image_type, id); + umount_host_channel(host_channel); + +clean_container_root_dir: + (void)delete_container_root_dir(id, runtime_root); + +clean_nameindex: + name_index_remove(name); + +pack_response: + pack_create_response(*response, id, cc); + free(runtime); + free(oci_config_data); + free(runtime_root); + free(real_rootfs); + free(image_type); + free(name); + free(id); + free_oci_runtime_spec(oci_spec); + free_host_config(host_spec); + free_container_custom_config(custom_spec); + free_container_config_v2_common_config(v2_spec); + free_host_config_host_channel(host_channel); + free_log_prefix(); + malloc_trim(0); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + diff --git a/src/services/execution/execute/execution_create.h b/src/services/execution/execute/execution_create.h new file mode 100644 index 0000000..1fc11c4 --- /dev/null +++ b/src/services/execution/execute/execution_create.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + *******************************************************************************/ + +#ifndef __EXECUTION_CREATE_H_ +#define __EXECUTION_CREATE_H_ + +#include "callback.h" +#include "container_unix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int container_create_cb(const container_create_request *request, + container_create_response **response); + +void umount_host_channel(const host_config_host_channel *host_channel); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/execution/execute/execution_extend.c b/src/services/execution/execute/execution_extend.c new file mode 100644 index 0000000..65dd90a --- /dev/null +++ b/src/services/execution/execute/execution_extend.c @@ -0,0 +1,1358 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container extend callback function definition + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "engine.h" +#include "collector.h" +#include "console.h" +#include "lcrd_config.h" +#include "config.h" +#include "restartmanager.h" +#include "image.h" +#include "securec.h" +#include "verify.h" +#include "container_inspect.h" +#include "containers_store.h" +#include "containers_gc.h" +#include "execution_extend.h" +#include "sysinfo.h" +#include "health_check.h" + +#ifdef ENABLE_OCI_IMAGE +#include "oci_rootfs_export.h" +#endif + +#include "filters.h" +#include "utils.h" +#include "error.h" + +struct stats_context { + struct filters_args *stats_filters; + container_stats_request *stats_config; +}; + +static int service_events_handler(const struct lcrd_events_request *request, + const stream_func_wrapper *stream) +{ + int ret = 0; + char *name = NULL; + container_t *container = NULL; + + name = request->id; + + /* check whether specified container exists */ + if (name != NULL) { + container = containers_store_get(name); + if (container == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + ret = -1; + goto out; + } + + container_unref(container); + } + + ret = events_subscribe(name, &request->since, &request->until, stream); + if (ret < 0) { + ERROR("Failed to subscribe events buffer"); + ret = -1; + goto out; + } + + if (add_monitor_client(name, &request->since, &request->until, stream)) { + ERROR("Failed to add events monitor client"); + ret = -1; + goto out; + } +out: + return ret; +} + +static int container_events_cb(const struct lcrd_events_request *request, const stream_func_wrapper *stream) +{ + int ret = 0; + uint32_t cc = LCRD_SUCCESS; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + if (stream == NULL) { + ERROR("Should provide stream function in events"); + cc = LCRD_ERR_INPUT; + goto out; + } + + ret = service_events_handler(request, stream); + if (ret != 0) { + ERROR("Failed to add events monitor"); + cc = LCRD_ERR_INPUT; + goto out; + } + +out: + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int dup_container_stats_request(const container_stats_request *src, container_stats_request **dest) +{ + int ret = -1; + char *json = NULL; + parser_error err = NULL; + + if (src == NULL) { + *dest = NULL; + return 0; + } + + json = container_stats_request_generate_json(src, NULL, &err); + if (json == NULL) { + ERROR("Failed to generate json: %s", err); + goto out; + } + *dest = container_stats_request_parse_data(json, NULL, &err); + if (*dest == NULL) { + ERROR("Failed to parse json: %s", err); + goto out; + } + ret = 0; + +out: + free(err); + free(json); + return ret; +} + +static void free_stats_context(struct stats_context *ctx) +{ + if (ctx == NULL) { + return; + } + filters_args_free(ctx->stats_filters); + ctx->stats_filters = NULL; + free_container_stats_request(ctx->stats_config); + ctx->stats_config = NULL; + free(ctx); +} +static struct stats_context *stats_context_new(const container_stats_request *request) +{ + struct stats_context *ctx = NULL; + + ctx = util_common_calloc_s(sizeof(struct stats_context)); + if (ctx == NULL) { + ERROR("Out of memory"); + return NULL; + } + ctx->stats_filters = filters_args_new(); + if (ctx->stats_filters == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + if (dup_container_stats_request(request, &(ctx->stats_config)) != 0) { + ERROR("Failed to dup stats request"); + goto cleanup; + } + + return ctx; +cleanup: + free_stats_context(ctx); + return NULL; +} +static const char *accepted_stats_filter_tags[] = { + "id", + "label", + "name", + NULL +}; + +static int copy_map_labels(const container_config *config, map_t **map_labels) +{ + *map_labels = map_new(MAP_STR_STR, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC); + if (*map_labels == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (config != NULL && config->labels != NULL && config->labels->len != 0) { + size_t i; + json_map_string_string *labels = config->labels; + + for (i = 0; i < labels->len; i++) { + // Copy labels to internal map for filters + if (!map_replace(*map_labels, (void *)labels->keys[i], labels->values[i])) { + ERROR("Failed to insert labels to map"); + return -1; + } + } + } + return 0; +} + +static container_info *get_container_stats(const container_t *cont, const struct engine_container_info *einfo, + const struct stats_context *ctx) +{ + int ret = 0; + uint64_t sysmem_limit; + uint64_t sys_cpu_usage = 0; + container_info *info = NULL; + map_t *map_labels = NULL; + + info = util_common_calloc_s(sizeof(container_info)); + if (info == NULL) { + ERROR("Out of memory"); + return NULL; + } + + info->id = util_strdup_s(cont->common_config->id); + info->has_pid = einfo->has_pid; + info->pid = (int32_t)einfo->pid; + info->status = (int)einfo->status; + info->pids_current = einfo->pids_current; + info->cpu_use_nanos = einfo->cpu_use_nanos; + info->blkio_read = einfo->blkio_read; + info->blkio_write = einfo->blkio_write; + info->mem_used = einfo->mem_used; + info->mem_limit = einfo->mem_limit; + info->kmem_used = einfo->kmem_used; + info->kmem_limit = einfo->kmem_limit; + + sysmem_limit = get_default_total_mem_size(); + if (get_system_cpu_usage(&sys_cpu_usage)) { + WARN("Failed to get system cpu usage"); + } + + if (sysmem_limit > 0) { + if (info->mem_limit > sysmem_limit) { + info->mem_limit = sysmem_limit; + } + if (info->kmem_limit > sysmem_limit) { + info->kmem_limit = sysmem_limit; + } + } + info->cpu_system_use = sys_cpu_usage; + info->online_cpus = (uint32_t)get_nprocs(); + + info->image_type = util_strdup_s(cont->common_config->image_type); + + if (copy_map_labels(cont->common_config->config, &map_labels) != 0) { + ret = -1; + goto cleanup; + } + + if (!filters_args_match(ctx->stats_filters, "id", info->id)) { + ret = -1; + goto cleanup; + } + + // Do not include container if any of the labels don't match + if (!filters_args_match_kv_list(ctx->stats_filters, "label", map_labels)) { + ret = -1; + goto cleanup; + } + +cleanup: + map_free(map_labels); + if (ret != 0) { + free_container_info(info); + info = NULL; + } + + return info; +} + +static struct stats_context *fold_stats_filter(const container_stats_request *request) +{ + size_t i, j; + struct stats_context *ctx = NULL; + + ctx = stats_context_new(request); + if (ctx == NULL) { + ERROR("Out of memory"); + return NULL; + } + + if (request->containers != NULL && request->containers_len > 0) { + ctx->stats_config->all = true; + } + + if (request->filters == NULL) { + return ctx; + } + + for (i = 0; i < request->filters->len; i++) { + if (!filters_args_valid_key(accepted_stats_filter_tags, sizeof(accepted_stats_filter_tags) / sizeof(char *), + request->filters->keys[i])) { + ERROR("Invalid filter '%s'", request->filters->keys[i]); + lcrd_set_error_message("Invalid filter '%s'", request->filters->keys[i]); + goto error_out; + } + for (j = 0; j < request->filters->values[i]->len; j++) { + bool bret = false; + bret = filters_args_add(ctx->stats_filters, request->filters->keys[i], + request->filters->values[i]->keys[j]); + if (!bret) { + ERROR("Add filter args failed"); + goto error_out; + } + } + } + + return ctx; +error_out: + free_stats_context(ctx); + return NULL; +} + +static int service_stats_make_memory(container_info ***stats_arr, size_t num) +{ + if (num > SIZE_MAX / sizeof(container_info *)) { + return -1; + } + + *stats_arr = util_common_calloc_s(num * sizeof(container_info *)); + if (*stats_arr == NULL) { + ERROR("Out of memory"); + return -1; + } + + return 0; +} + +static void pack_stats_response(container_stats_response *response, uint32_t cc, size_t info_len, + container_info **info) +{ + if (response == NULL) { + return; + } + response->container_stats_len = info_len; + response->container_stats = info; + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } +} + +static int stats_get_all_containers_id(const container_stats_request *request, char ***idsarray, size_t *ids_len, + bool *check_exists) +{ + int ret = -1; + char **array = NULL; + + if (request == NULL) { + return 0; + } + + if (request->containers_len > 0 && request->containers != NULL) { + size_t n; + for (n = 0; n < request->containers_len; n++) { + if (!util_valid_container_id_or_name(request->containers[n])) { + ERROR("Invalid container name: %s", request->containers[n]); + lcrd_set_error_message("Invalid container name: %s", request->containers[n]); + goto cleanup; + } + if (util_array_append(&array, request->containers[n]) != 0) { + ERROR("Can not append array"); + goto cleanup; + } + } + *check_exists = true; + } else { + array = containers_store_list_ids(); + } + *ids_len = util_array_len(array); + *idsarray = array; + array = NULL; + ret = 0; + +cleanup: + util_free_array(array); + return ret; +} + +static int get_containers_stats(const char *runtime, char **idsarray, size_t ids_len, const struct stats_context *ctx, + bool check_exists, container_info ***info, size_t *info_len) +{ + int ret = 0; + int nret; + size_t i; + char *engine_path = NULL; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_get_container_status_op == NULL || + engine_ops->engine_free_container_status_op == NULL) { + ERROR("Failed to get engine stats operations"); + ret = -1; + goto cleanup; + } + + engine_path = conf_get_routine_rootdir(runtime); + if (engine_path == NULL) { + ERROR("Get engine path failed"); + ret = -1; + goto cleanup; + } + + nret = service_stats_make_memory(info, ids_len); + if (nret != 0) { + ret = -1; + goto cleanup; + } + + for (i = 0; i < ids_len; i++) { + struct engine_container_info einfo = { 0 }; + container_t *cont = NULL; + + cont = containers_store_get(idsarray[i]); + if (cont == NULL) { + if (check_exists) { + ERROR("No such container: %s", idsarray[i]); + lcrd_set_error_message("No such container: %s", idsarray[i]); + ret = -1; + goto cleanup; + } + continue; + } + if (is_running(cont->state)) { + nret = engine_ops->engine_get_container_status_op(cont->common_config->id, engine_path, &einfo); + if (nret != 0) { + engine_ops->engine_clear_errmsg_op(); + container_unref(cont); + continue; + } + } else { + if (!ctx->stats_config->all) { + container_unref(cont); + continue; + } + } + + (*info)[*info_len] = get_container_stats(cont, &einfo, ctx); + container_unref(cont); + engine_ops->engine_free_container_status_op(&einfo); + if ((*info)[*info_len] == NULL) { + continue; + } + (*info_len)++; + } +cleanup: + free(engine_path); + return ret; +} + +static int container_stats_cb(const container_stats_request *request, + container_stats_response **response) +{ + bool check_exists = false; + size_t ids_len = 0; + size_t info_len = 0; + uint32_t cc = LCRD_SUCCESS; + char **idsarray = NULL; + container_info **info = NULL; + struct stats_context *ctx = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_stats_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + if (request->runtime == NULL) { + ERROR("Receive NULL Request runtime"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + ctx = fold_stats_filter(request); + if (ctx == NULL) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (stats_get_all_containers_id(request, &idsarray, &ids_len, &check_exists) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + if (ids_len == 0) { + goto pack_response; + } + + if (get_containers_stats(request->runtime, idsarray, ids_len, ctx, check_exists, &info, &info_len)) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + +pack_response: + pack_stats_response(*response, cc, info_len, info); + util_free_array(idsarray); + free_stats_context(ctx); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int runtime_resume(const char *id, const char *runtime, const char *rootpath) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_resume_op == NULL) { + DEBUG("Failed to get engine resume operations"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_resume_op(id, rootpath)) { + DEBUG("Resume container %s failed", id); + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Resume container error;%s", + (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg + : DEF_ERR_RUNTIME_STR); + + engine_ops->engine_clear_errmsg_op(); + ret = -1; + goto out; + } +out: + return ret; +} + +static int resume_container(container_t *cont) +{ + int ret = 0; + const char *id = cont->common_config->id; + + container_lock(cont); + + if (!is_running(cont->state)) { + ERROR("Container %s is not running", id); + lcrd_set_error_message("Container %s is not running", id); + ret = -1; + goto out; + } + + if (!is_paused(cont->state)) { + ERROR("Container %s is not paused", id); + lcrd_set_error_message("Container %s is not paused", id); + ret = -1; + goto out; + } + + if (runtime_resume(id, cont->runtime, cont->root_path)) { + ERROR("Failed to resume container:%s", id); + ret = -1; + goto out; + } + + state_reset_paused(cont->state); + + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", id); + ret = -1; + goto out; + } + +out: + container_unlock(cont); + return ret; +} + +#ifdef ENABLE_OCI_IMAGE +static int oci_image_export_rootfs(const char *id, const char *file) +{ + int ret = 0; + rootfs_export_request *request = NULL; + rootfs_export_response *response = NULL; + + if (id == NULL || file == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + request = util_common_calloc_s(sizeof(rootfs_export_request)); + if (request == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + + request->id = util_strdup_s(id); + request->file = util_strdup_s(file); + + ret = export_rootfs(request, &response); + if (ret != 0) { + ERROR("Failed to export rootfs to %s from container %s", file, id); + ret = -1; + goto out; + } + +out: + free_rootfs_export_request(request); + free_rootfs_export_response(response); + return ret; +} +#endif + +static int export_container(container_t *cont, const char *file) +{ + int ret = 0; + + container_lock(cont); + +#ifdef ENABLE_OCI_IMAGE + if (oci_image_export_rootfs(cont->common_config->id, file)) { + ret = -1; + } +#endif + + container_unlock(cont); + return ret; +} + +static void pack_resume_response(container_resume_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (id != NULL) { + response->id = util_strdup_s(id); + } + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } +} + +static int container_resume_cb(const container_resume_request *request, container_resume_response **response) +{ + int ret = 0; + uint32_t cc = LCRD_SUCCESS; + char *name = NULL; + char *id = NULL; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_resume_response)); + if (*response == NULL) { + ERROR("Resume: Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + if (name == NULL) { + ERROR("Resume: receive NULL id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cont = containers_store_get(name); + if (cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + EVENT("Event: {Object: %s, Type: Resuming}", id); + + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("You cannot resume container %s in garbage collector progress.", id); + ERROR("You cannot resume container %s in garbage collector progress.", id); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + ret = resume_container(cont); + if (ret != 0) { + cc = LCRD_ERR_EXEC; + container_state_set_error(cont->state, (const char *)g_lcrd_errmsg); + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: Resumed}", id); + +pack_response: + pack_resume_response(*response, cc, id); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int runtime_pause(const char *id, const char *runtime, const char *rootpath) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_pause_op == NULL) { + DEBUG("Failed to get engine pause operations"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_pause_op(id, rootpath)) { + DEBUG("Pause container %s failed", id); + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Pause container error;%s", (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? + tmpmsg : DEF_ERR_RUNTIME_STR); + engine_ops->engine_clear_errmsg_op(); + ret = -1; + goto out; + } +out: + return ret; +} + +static int pause_container(container_t *cont) +{ + int ret = 0; + const char *id = cont->common_config->id; + + container_lock(cont); + + if (!is_running(cont->state)) { + ERROR("Container %s is not running", id); + lcrd_set_error_message("Container %s is not running", id); + ret = -1; + goto out; + } + + if (is_paused(cont->state)) { + ERROR("Container %s is already paused", id); + lcrd_set_error_message("Container %s is already paused", id); + ret = -1; + goto out; + } + + if (is_restarting(cont->state)) { + ERROR("Container %s is restarting, wait until the container is running", id); + lcrd_set_error_message("Container %s is restarting, wait until the container is running", id); + ret = -1; + goto out; + } + + if (runtime_pause(id, cont->runtime, cont->root_path)) { + ERROR("Failed to pause container:%s", id); + ret = -1; + goto out; + } + + state_set_paused(cont->state); + + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", id); + ret = -1; + goto out; + } +out: + container_unlock(cont); + return ret; +} + +static void pack_pause_response(container_pause_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (id != NULL) { + response->id = util_strdup_s(id); + } + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } +} + +static int container_pause_cb(const container_pause_request *request, + container_pause_response **response) +{ + int ret = 0; + uint32_t cc = LCRD_SUCCESS; + char *name = NULL; + char *id = NULL; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_pause_response)); + if (*response == NULL) { + ERROR("Pause: Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + + if (name == NULL) { + ERROR("Pause: receive NULL id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cont = containers_store_get(name); + if (cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + EVENT("Event: {Object: %s, Type: Pausing}", id); + + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("You cannot pause container %s in garbage collector progress.", id); + ERROR("You cannot pause container %s in garbage collector progress.", id); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + ret = pause_container(cont); + if (ret != 0) { + cc = LCRD_ERR_EXEC; + container_state_set_error(cont->state, (const char *)g_lcrd_errmsg); + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: Paused}", id); + + update_health_monitor(id); + +pack_response: + pack_pause_response(*response, cc, id); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static void to_engine_resources(const host_config *hostconfig, struct engine_cgroup_resources *cr) +{ + if (hostconfig == NULL || cr == NULL) { + return; + } + + cr->blkio_weight = hostconfig->blkio_weight; + cr->cpu_shares = (uint64_t)hostconfig->cpu_shares; + cr->cpu_period = (uint64_t)hostconfig->cpu_period; + cr->cpu_quota = (uint64_t)hostconfig->cpu_quota; + cr->cpuset_cpus = hostconfig->cpuset_cpus; + cr->cpuset_mems = hostconfig->cpuset_mems; + cr->memory_limit = (uint64_t)hostconfig->memory; + cr->memory_swap = (uint64_t)hostconfig->memory_swap; + cr->memory_reservation = (uint64_t)hostconfig->memory_reservation; + cr->kernel_memory_limit = (uint64_t)hostconfig->kernel_memory; +} + +static void host_config_restore_unlocking(container_t *cont, host_config *backup_hostconfig) +{ + free_host_config(cont->hostconfig); + cont->hostconfig = backup_hostconfig; + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", cont->common_config->id); + } +} + +static void update_container_cpu(const host_config *hostconfig, host_config *chostconfig) +{ + if (hostconfig->cpu_shares != 0) { + chostconfig->cpu_shares = hostconfig->cpu_shares; + } + if (hostconfig->cpu_period != 0) { + chostconfig->cpu_period = hostconfig->cpu_period; + } + if (hostconfig->cpu_quota != 0) { + chostconfig->cpu_quota = hostconfig->cpu_quota; + } + if (hostconfig->cpuset_cpus != NULL) { + free(chostconfig->cpuset_cpus); + chostconfig->cpuset_cpus = util_strdup_s(hostconfig->cpuset_cpus); + } + if (hostconfig->cpuset_mems != NULL) { + free(chostconfig->cpuset_mems); + chostconfig->cpuset_mems = util_strdup_s(hostconfig->cpuset_mems); + } +} + +static int update_container_memory(const char *id, const host_config *hostconfig, host_config *chostconfig) +{ + int ret = 0; + + if (hostconfig->memory != 0) { + // if memory limit smaller than already set memoryswap limit and doesn't + // update the memoryswap limit, then error out. + if (chostconfig->memory_swap > 0 && + hostconfig->memory > chostconfig->memory_swap && + hostconfig->memory_swap == 0) { + ERROR("Memory limit should be smaller than already set memoryswap limit," + " update the memoryswap at the same time"); + lcrd_set_error_message("Cannot update container %s: Memory limit should be smaller than" + "already set memoryswap limit, update the memoryswap at the same time.", id); + ret = -1; + goto out; + } + chostconfig->memory = hostconfig->memory; + } + + if (hostconfig->memory_swap != 0) { + chostconfig->memory_swap = hostconfig->memory_swap; + } + if (hostconfig->memory_reservation != 0) { + chostconfig->memory_reservation = hostconfig->memory_reservation; + } + if (hostconfig->kernel_memory != 0) { + chostconfig->kernel_memory = hostconfig->kernel_memory; + } + +out: + return ret; +} + +static int update_container_restart_policy(const host_config *hostconfig, host_config *chostconfig) +{ + int ret = 0; + + if (hostconfig->restart_policy != NULL && hostconfig->restart_policy->name != NULL) { + free_host_config_restart_policy(chostconfig->restart_policy); + chostconfig->restart_policy = util_common_calloc_s(sizeof(host_config_restart_policy)); + if (chostconfig->restart_policy == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + chostconfig->restart_policy->name = util_strdup_s(hostconfig->restart_policy->name); + chostconfig->restart_policy->maximum_retry_count = hostconfig->restart_policy->maximum_retry_count; + } +out: + return ret; +} + +static int update_container(const container_t *cont, const host_config *hostconfig) +{ + int ret = 0; + char *id = NULL; + host_config *chostconfig = NULL; + + if (cont == NULL || cont->hostconfig == NULL || hostconfig == NULL) { + return -1; + } + + id = cont->common_config->id; + + chostconfig = cont->hostconfig; + + if (hostconfig->blkio_weight != 0) { + chostconfig->blkio_weight = hostconfig->blkio_weight; + } + + update_container_cpu(hostconfig, chostconfig); + + ret = update_container_memory(id, hostconfig, chostconfig); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = update_container_restart_policy(hostconfig, chostconfig); + if (ret != 0) { + ret = -1; + goto out; + } + +out: + return ret; +} + +host_config *dump_host_config(const host_config *origconfig) +{ + char *json = NULL; + parser_error err = NULL; + host_config *newconfig = NULL; + + if (origconfig == NULL) { + return NULL; + } + + json = host_config_generate_json(origconfig, NULL, &err); + if (json == NULL) { + ERROR("Failed to generate json: %s", err); + goto out; + } + newconfig = host_config_parse_data(json, NULL, &err); + if (newconfig == NULL) { + ERROR("Failed to parse json: %s", err); + goto out; + } + +out: + free(err); + free(json); + return newconfig; +} + +static int update_host_config_check(container_t *cont, host_config *hostconfig) +{ + int ret = 0; + const char *id = cont->common_config->id; + + ret = verify_host_config_settings(hostconfig, true); + if (ret != 0) { + goto out; + } + + if (is_removal_in_progress(cont->state) || is_dead(cont->state)) { + ERROR("Container is marked for removal and cannot be \"update\"."); + lcrd_set_error_message("Cannot update container %s: Container is marked for removal and cannot be \"update\".", + id); + ret = -1; + goto out; + } + + if (is_running(cont->state) && hostconfig->kernel_memory) { + ERROR("Can not update kernel memory to a running container, please stop it first."); + lcrd_set_error_message("Cannot update container %s: Can not update kernel memory to a running container," + " please stop it first.", id); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int runtime_update(const char *id, const char *runtime, const char *rootpath, struct engine_cgroup_resources *cr) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_update_op == NULL) { + DEBUG("Failed to get engine update operations"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_update_op(id, rootpath, cr)) { + DEBUG("Update container %s failed", id); + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Cannot update container %s: %s", id, (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? + tmpmsg : DEF_ERR_RUNTIME_STR); + engine_ops->engine_clear_errmsg_op(); + ret = -1; + goto out; + } +out: + return ret; +} + +static int do_update_resources(const container_update_request *request, container_t *cont) +{ + int ret = 0; + const char *id = cont->common_config->id; + parser_error err = NULL; + host_config *hostconfig = NULL; + host_config *backup_hostconfig = NULL; + struct engine_cgroup_resources cr = { 0 }; + + if (request->host_config == NULL) { + DEBUG("receive NULL host config"); + ret = -1; + goto out; + } + + hostconfig = host_config_parse_data(request->host_config, NULL, &err); + if (hostconfig == NULL) { + ERROR("Failed to parse host config data:%s", err); + ret = -1; + goto out; + } + + container_lock(cont); + + if (update_host_config_check(cont, hostconfig)) { + ret = -1; + goto unlock_out; + } + + backup_hostconfig = dump_host_config(cont->hostconfig); + if (backup_hostconfig == NULL) { + ret = -1; + goto unlock_out; + } + + if (update_container(cont, hostconfig)) { + host_config_restore_unlocking(cont, backup_hostconfig); + ret = -1; + goto unlock_out; + } + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", id); + host_config_restore_unlocking(cont, backup_hostconfig); + ret = -1; + goto unlock_out; + } + + if (hostconfig->restart_policy && hostconfig->restart_policy->name) { + container_update_restart_manager(cont, hostconfig->restart_policy); + } + + to_engine_resources(hostconfig, &cr); + if (runtime_update(id, cont->runtime, cont->root_path, &cr)) { + ERROR("Update container %s failed", id); + host_config_restore_unlocking(cont, backup_hostconfig); + ret = -1; + goto unlock_out; + } + +unlock_out: + container_unlock(cont); +out: + if (ret == 0) { + free_host_config(backup_hostconfig); + } + free_host_config(hostconfig); + free(err); + return ret; +} + +static void pack_update_response(container_update_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (id != NULL) { + response->id = util_strdup_s(id); + } + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } +} + +static int container_update_cb(const container_update_request *request, container_update_response **response) +{ + uint32_t cc = LCRD_SUCCESS; + char *container_name = NULL; + char *id = NULL; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_update_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + container_name = request->name; + if (container_name == NULL) { + DEBUG("receive NULL Request id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + if (!util_valid_container_id_or_name(container_name)) { + ERROR("Invalid container name %s", container_name); + lcrd_set_error_message("Invalid container name %s", container_name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cont = containers_store_get(container_name); + if (cont == NULL) { + ERROR("No such container: %s", container_name); + lcrd_set_error_message("No such container: %s", container_name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + EVENT("Event: {Object: %s, Type: updating}", id); + + if (do_update_resources(request, cont) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: updated}", id); + +pack_response: + pack_update_response(*response, cc, id); + + container_unref(cont); + + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static void pack_export_response(container_export_response *response, uint32_t cc, const char *id) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (id != NULL) { + response->id = util_strdup_s(id); + } + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } +} + +static int container_export_cb(const container_export_request *request, container_export_response **response) +{ + int ret = 0; + char *name = NULL; + char *id = NULL; + char *file = NULL; + uint32_t cc = LCRD_SUCCESS; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_export_response)); + if (*response == NULL) { + ERROR("Export: Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + file = request->file; + + if (name == NULL) { + ERROR("Export: receive NULL id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cont = containers_store_get(name); + if (cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("You cannot export container %s in garbage collector progress.", id); + ERROR("You cannot export container %s in garbage collector progress.", id); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + ret = export_container(cont, file); + if (ret != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + +pack_response: + pack_export_response(*response, cc, id); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +void container_extend_callback_init(service_container_callback_t *cb) +{ + cb->update = container_update_cb; + cb->pause = container_pause_cb; + cb->resume = container_resume_cb; + cb->stats = container_stats_cb; + cb->events = container_events_cb; + cb->export_rootfs = container_export_cb; +} + diff --git a/src/services/execution/execute/execution_extend.h b/src/services/execution/execute/execution_extend.h new file mode 100644 index 0000000..ff6f3aa --- /dev/null +++ b/src/services/execution/execute/execution_extend.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + *********************************************************************************/ + + +#ifndef __EXECUTION_EXTEND_H_ +#define __EXECUTION_EXTEND_H_ + +#include "callback.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void container_extend_callback_init(service_container_callback_t *cb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/execution/execute/execution_information.c b/src/services/execution/execute/execution_information.c new file mode 100644 index 0000000..7215f2e --- /dev/null +++ b/src/services/execution/execute/execution_information.c @@ -0,0 +1,1755 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container information callback function definition + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "engine.h" +#include "console.h" +#include "lcrd_config.h" +#include "config.h" +#include "image.h" +#include "securec.h" +#include "execution.h" +#include "container_inspect.h" +#include "containers_store.h" +#include "execution_information.h" +#include "sysinfo.h" +#include "read_file.h" + +#ifdef ENABLE_OCI_IMAGE +#include "oci_images_store.h" +#endif + +#include "container_state.h" +#include "runtime_interface.h" +#include "list.h" +#include "utils.h" +#include "error.h" + +static int container_version_cb(const container_version_request *request, container_version_response **response) +{ + char *rootpath = NULL; + uint32_t cc = LCRD_SUCCESS; + + DAEMON_CLEAR_ERRMSG(); + + if (response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_version_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + (*response)->version = util_strdup_s(VERSION); + (*response)->git_commit = util_strdup_s(LCRD_GIT_COMMIT); + (*response)->build_time = util_strdup_s(LCRD_BUILD_TIME); + + rootpath = conf_get_lcrd_rootdir(); + if (rootpath == NULL) { + ERROR("Failed to get root directory"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + (*response)->root_path = util_strdup_s(rootpath); + +pack_response: + if (*response != NULL) { + (*response)->cc = cc; + } + + free(rootpath); + + free_log_prefix(); + DAEMON_CLEAR_ERRMSG(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +#define STOP_JSON "{\"filters\":{\"status\":{\"exited\":true}}}" +#define CREATED_JSON "{\"filters\":{\"status\":{\"created\":true}}}" +static int get_container_nums(int *cRunning, int *cPaused, int *cStopped) +{ + container_list_request *list_running_request = NULL; + container_list_request *list_stopped_request = NULL; + container_list_request *list_created_request = NULL; + container_list_response *list_running_response = NULL; + container_list_response *list_stopped_response = NULL; + container_list_response *list_created_response = NULL; + int ret = 0; + parser_error err = NULL; + + list_running_request = util_common_calloc_s(sizeof(*list_running_request)); + if (list_running_request == NULL) { + ERROR("Out of memory!"); + ret = -1; + goto out; + } + + ret = container_list_cb(list_running_request, &list_running_response); + if (ret != 0) { + ERROR("Failed to get container list info!"); + ret = -1; + goto out; + } + *cRunning = (int)list_running_response->containers_len; + + list_created_request = container_list_request_parse_data(CREATED_JSON, NULL, &err); + if (list_created_request == NULL) { + ERROR("Failed to parse json: %s", err); + ret = -1; + goto out; + } + + ret = container_list_cb(list_created_request, &list_created_response); + if (ret != 0) { + ERROR("Failed to get container list info!"); + ret = -1; + goto out; + } + + list_stopped_request = container_list_request_parse_data(STOP_JSON, NULL, &err); + if (list_stopped_request == NULL) { + ERROR("Failed to parse json: %s", err); + ret = -1; + goto out; + } + + ret = container_list_cb(list_stopped_request, &list_stopped_response); + if (ret != 0) { + ERROR("Failed to get container list info!"); + ret = -1; + goto out; + } + *cStopped = (int)(list_stopped_response->containers_len + list_created_response->containers_len); + +out: + free_container_list_request(list_running_request); + free_container_list_request(list_stopped_request); + free_container_list_request(list_created_request); + free_container_list_response(list_running_response); + free_container_list_response(list_stopped_response); + free_container_list_response(list_created_response); + free(err); + return ret; +} + +static int get_proxy_env(char **proxy, const char *type) +{ + int ret = 0; + char *tmp = NULL; + + *proxy = getenv(type); + if (*proxy == NULL) { + tmp = strings_to_upper(type); + if (tmp == NULL) { + ERROR("Failed to upper string!"); + ret = -1; + goto out; + } + *proxy = getenv(tmp); + if (*proxy == NULL) { + *proxy = ""; + } + } + +out: + free(tmp); + return ret; +} + +static int isulad_info_cb(const host_info_request *request, host_info_response **response) +{ + int ret = 0; + int cRunning = 0; + int cPaused = 0; + int cStopped = 0; + size_t images_num = 0; + uint32_t cc = LCRD_SUCCESS; + uint64_t total_mem = 0; + uint64_t sysmem_limit = 0; + char *http_proxy = NULL; + char *https_proxy = NULL; + char *no_proxy = NULL; + char *operating_system = NULL; + char *huge_page_size = NULL; + struct utsname u; + + DAEMON_CLEAR_ERRMSG(); + + if (response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(host_info_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + ret = get_container_nums(&cRunning, &cPaused, &cStopped); + if (ret != 0) { + ERROR("Failed to get container status info!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } +#ifdef ENABLE_OCI_IMAGE + images_num = oci_images_store_size(); +#endif + operating_system = get_operating_system(); + if (operating_system == NULL) { + ERROR("Failed to get operating system info!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + huge_page_size = get_default_huge_page_size(); + if (huge_page_size == NULL) { + ERROR("Failed to get system hugepage size!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + sysmem_limit = get_default_total_mem_size(); + if (sysmem_limit == 0) { + ERROR("Failed to get total mem!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + total_mem = sysmem_limit / SIZE_GB; + + if (uname(&u) != 0) { + ERROR("Failed to get kernel info!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + ret = get_proxy_env(&http_proxy, HTTP_PROXY); + if (ret != 0) { + ERROR("Failed to get http proxy env!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + ret = get_proxy_env(&https_proxy, HTTPS_PROXY); + if (ret != 0) { + ERROR("Failed to get https proxy env!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + ret = get_proxy_env(&no_proxy, NO_PROXY); + if (ret != 0) { + ERROR("Failed to get no proxy env!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + (*response)->containers_num = (cRunning + cPaused + cStopped); + (*response)->c_running = cRunning; + (*response)->c_paused = cPaused; + (*response)->c_stopped = cStopped; + (*response)->images_num = (int)images_num; + (*response)->version = util_strdup_s(VERSION); + (*response)->kversion = util_strdup_s(u.release); + (*response)->os_type = util_strdup_s(u.sysname); + (*response)->architecture = util_strdup_s(u.machine); + (*response)->nodename = util_strdup_s(u.nodename); + (*response)->cpus = get_nprocs(); + (*response)->operating_system = util_strdup_s(operating_system); + (*response)->cgroup_driver = util_strdup_s("cgroupfs"); + (*response)->logging_driver = util_strdup_s("json-file"); + (*response)->huge_page_size = util_strdup_s(huge_page_size); + (*response)->isulad_root_dir = util_strdup_s(LCRD_ROOT_PATH); + (*response)->total_mem = (uint32_t)total_mem; + (*response)->http_proxy = util_strdup_s(http_proxy); + (*response)->https_proxy = util_strdup_s(https_proxy); + (*response)->no_proxy = util_strdup_s(no_proxy); + +pack_response: + if (*response != NULL) { + (*response)->cc = cc; + } + free(huge_page_size); + free(operating_system); + free_log_prefix(); + DAEMON_CLEAR_ERRMSG(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +int get_stime(const char *title_line) +{ + size_t i = 0; + int stime = 0; + char **title_element = NULL; + + title_element = util_string_split(title_line, ' '); + for (i = 0; i < util_array_len(title_element); i++) { + if (strcmp(title_element[i], "STIME") == 0) { + stime = (int)i; + break; + } + } + + util_free_array(title_element); + + return stime; +} + +int get_pid_num(const char *title_line) +{ + size_t i = 0; + int num = 0; + char **title_element = NULL; + + title_element = util_string_split(title_line, ' '); + for (i = 0; i < util_array_len(title_element); i++) { + if (strcmp(title_element[i], "PID") == 0) { + num = (int)i; + break; + } + } + + util_free_array(title_element); + + return num; +} + +static int parse_output_check(char **pid_s, int pid_num, int *out_num) +{ + int ret = 0; + + // be able to display thread line also when "m" option used + // in "lcrc top" client command + if (strcmp(pid_s[pid_num], "-") == 0) { + } else { + if (util_safe_int(pid_s[pid_num], out_num) || *out_num < 0) { + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int parse_output_by_lines(char **process, char **tmp, int pid_num, int stime, const pid_t *pids, size_t pids_len) +{ + int ret = 0; + size_t i = 0; + size_t j = 0; + int k = 0; + char **pid_s = NULL; + char **pid_s_pre = NULL; + + for (i = 1; i < util_array_len(tmp); i++) { + bool flag = false; + int tmp_num = 0; + if (i > 1) { + pid_s_pre = util_string_split(tmp[i - 1], ' '); + if (pid_s_pre == NULL) { + goto out; + } + } + pid_s = util_string_split(tmp[i], ' '); + if (pid_s == NULL) { + util_free_array(pid_s_pre); + goto out; + } + + if (parse_output_check(pid_s, pid_num, &tmp_num)) { + ret = -1; + util_free_array(pid_s); + util_free_array(pid_s_pre); + goto out; + } + + for (j = 0; j < pids_len; j++) { + if (i > 1 && strcmp(pid_s[pid_num], "-") == 0 && !flag) { + flag = true; + if ((tmp_num == pids[j] || !strcmp(pid_s[stime], pid_s_pre[stime]))) { + process[k++] = util_strdup_s(tmp[i]); + } + } else if (tmp_num == pids[j]) { + process[k++] = util_strdup_s(tmp[i]); + } + } + util_free_array(pid_s); + pid_s = NULL; + util_free_array(pid_s_pre); + pid_s_pre = NULL; + } +out: + return ret; +} + +int parse_output(char **title, char ***process, const char *output, const pid_t *pids, size_t pids_len) +{ + int ret = 0; + int pid_num = 0; + int stime = 0; + char **tmp = NULL; + + tmp = util_string_split(output, '\n'); + if (tmp == NULL) { + ERROR("Out of memory"); + return -1; + } + *title = util_strdup_s(tmp[0]); + + pid_num = get_pid_num(*title); + stime = get_stime(*title); + if (util_array_len(tmp) > SIZE_MAX / sizeof(char *)) { + ERROR("Invalid array length"); + ret = -1; + goto out; + } + *process = util_common_calloc_s(util_array_len(tmp) * sizeof(char *)); + if (*process == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + ret = parse_output_by_lines(*process, tmp, pid_num, stime, pids, pids_len); + +out: + util_free_array(tmp); + return ret; +} + +static inline void add_ps_array_elem(char **array, size_t *pos, const char *elem) +{ +#define PARAM_NUM 100 + if (*pos + 1 >= (PARAM_NUM - 1)) { + return; + } + array[*pos] = util_strdup_s(elem); + *pos += 1; +} + +void execute_ps_command(char **args, const char *pid_args, size_t args_len) +{ + size_t i = 0; + char *params[PARAM_NUM] = { NULL }; + + if (util_check_inherited(true, -1) != 0) { + COMMAND_ERROR("Close inherited fds failed"); + goto out; + } + + add_ps_array_elem(params, &i, "ps"); + for (; args && *args && i < args_len + 1; args++) { + add_ps_array_elem(params, &i, *args); + } + + add_ps_array_elem(params, &i, pid_args); + + execvp("ps", params); + + COMMAND_ERROR("Cannot get ps info with '%s':%s", pid_args, strerror(errno)); + +out: + exit(EXIT_FAILURE); +} + +static char *ps_pids_arg(const pid_t *pids, size_t pids_len) +{ + size_t i = 0; + int nret; + int ret = -1; + size_t tmp_len = 0; + char *pid_arg = NULL; + char pid_str[UINT_LEN + 1] = { 0 }; + + if (pids_len > SIZE_MAX / (UINT_LEN + 1)) { + ERROR("Invalid pid size"); + return NULL; + } + tmp_len = pids_len * (UINT_LEN + 1); + pid_arg = util_common_calloc_s(tmp_len); + if (pid_arg == NULL) { + ERROR("Out of memory"); + return NULL; + } + + for (i = 0; i < pids_len; i++) { + if (i != (pids_len - 1)) { + nret = sprintf_s(pid_str, sizeof(pid_str), "%d,", pids[i]); + } else { + nret = sprintf_s(pid_str, sizeof(pid_str), "%d", pids[i]); + } + if (nret < 0) { + ERROR("Failed to sprintf pids!"); + ret = -1; + goto out; + } + + nret = strcat_s(pid_arg, tmp_len, pid_str); + if (nret != EOK) { + ERROR("Failed to cat pids!"); + ret = -1; + goto out; + } + } + + ret = 0; +out: + if (ret != 0) { + free(pid_arg); + pid_arg = NULL; + } + return pid_arg; +} + +static int get_pids(const char *name, const char *runtime, const char *rootpath, pid_t **pids, size_t *pids_len, + char **pid_args) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_get_container_pids_op == NULL) { + DEBUG("Failed to get engine top operations"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_get_container_pids_op(name, rootpath, pids, pids_len)) { + DEBUG("Top container %s failed", name); + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Runtime top container error: %s", + (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg : DEF_ERR_RUNTIME_STR); + + ret = -1; + goto out; + } + + *pid_args = ps_pids_arg(*pids, *pids_len); + if (*pid_args == NULL) { + ERROR("failed to get pid_args"); + ret = -1; + goto out; + } +out: + if (engine_ops != NULL) { + engine_ops->engine_clear_errmsg_op(); + } + return ret; +} + +static int container_top_cb_check(const container_top_request *request, container_top_response **response, uint32_t *cc, + container_t **cont) +{ + char *name = NULL; + + *response = util_common_calloc_s(sizeof(container_top_response)); + if (*response == NULL) { + ERROR("Out of memory"); + *cc = LCRD_ERR_MEMOUT; + return -1; + } + + name = request->id; + + if (name == NULL) { + DEBUG("receive NULL Request id"); + *cc = LCRD_ERR_INPUT; + return -1; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + *cc = LCRD_ERR_EXEC; + return -1; + } + + *cont = containers_store_get(name); + if (*cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + *cc = LCRD_ERR_EXEC; + return -1; + } + + if (!is_running((*cont)->state)) { + ERROR("Container is not running"); + lcrd_set_error_message("Container is is not running."); + *cc = LCRD_ERR_EXEC; + return -1; + } + + if (is_restarting((*cont)->state)) { + ERROR("Container %s is restarting, wait until the container is running.", name); + lcrd_set_error_message("Container %s is restarting, wait until the container is running.", name); + *cc = LCRD_ERR_EXEC; + return -1; + } + + return 0; +} + +static int top_append_args(container_top_request *request) +{ + int ret = 0; + + if (request->args == NULL) { + request->args = (char **)util_common_calloc_s(sizeof(char *)); + if (request->args == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + request->args[0] = util_strdup_s("-ef"); + request->args_len = 1; + } + +out: + return ret; +} + +static int do_top(const container_top_request *request, container_t *cont, size_t pids_len, const char *pid_args, + char **stdout_buffer, char **stderr_buffer) +{ + int ret = 0; + int nret = 0; + bool command_ret = false; + char *ps_args_with_q = NULL; + size_t ps_args_with_q_len = 0; + + if (pids_len > (SIZE_MAX / (UINT_LEN + 1)) - 1) { + ERROR("Invalid pid size"); + return -1; + } + ps_args_with_q_len = (pids_len + 1) * (UINT_LEN + 1); + ps_args_with_q = util_common_calloc_s(ps_args_with_q_len); + if (ps_args_with_q == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + nret = strcat_s(ps_args_with_q, ps_args_with_q_len, "-q"); + if (nret != EOK) { + ERROR("Failed to cat pids!"); + ret = -1; + goto out; + } + + nret = strcat_s(ps_args_with_q, ps_args_with_q_len, pid_args); + if (nret != EOK) { + ERROR("Failed to cat pids!"); + ret = -1; + goto out; + } + + command_ret = util_exec_top_cmd(execute_ps_command, request->args, ps_args_with_q, request->args_len, stdout_buffer, + stderr_buffer); + if (!command_ret) { + ERROR("Failed to get container ps info with error: %s", + *stderr_buffer ? *stderr_buffer : "Failed to exec ps command"); + lcrd_set_error_message("Failed to get container ps info with error: %s", + *stderr_buffer ? *stderr_buffer : "Failed to exec ps command"); + free(*stdout_buffer); + *stdout_buffer = NULL; + free(*stderr_buffer); + *stderr_buffer = NULL; + // some ps options (such as f) can't be used together with q, + // so retry without it + command_ret = util_exec_top_cmd(execute_ps_command, request->args, pid_args, request->args_len, stdout_buffer, + stderr_buffer); + if (!command_ret) { + ERROR("Failed to get container ps info with error: %s", + *stderr_buffer ? *stderr_buffer : "Failed to exec ps command"); + lcrd_set_error_message("Failed to container ps info with error: %s", *stderr_buffer); + ret = -1; + goto out; + } + } +out: + free(ps_args_with_q); + return ret; +} + +static int container_top_cb(container_top_request *request, container_top_response **response) +{ + size_t i = 0; + uint32_t cc = LCRD_SUCCESS; + char *id = NULL; + char *rootpath = NULL; + char *runtime = NULL; + char *pid_args = NULL; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + char *titles = NULL; + char **processes = NULL; + pid_t *pids = NULL; + size_t pids_len = 0; + container_t *cont = NULL; + + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + if (top_append_args(request)) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (container_top_cb_check(request, response, &cc, &cont) < 0) { + goto pack_response; + } + + id = cont->common_config->id; + rootpath = cont->root_path; + runtime = cont->runtime; + set_log_prefix(id); + + if (get_pids(id, runtime, rootpath, &pids, &pids_len, &pid_args) != 0) { + ERROR("failed to get all pids"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (do_top(request, cont, pids_len, pid_args, &stdout_buffer, &stderr_buffer) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (parse_output(&titles, &processes, stdout_buffer, pids, pids_len)) { + ERROR("Failed to parse output!"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + if (util_array_len(processes) > SIZE_MAX / sizeof(char *)) { + ERROR("invalid processe size"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + (*response)->processes = util_common_calloc_s(util_array_len(processes) * sizeof(char *)); + if ((*response)->processes == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + (*response)->titles = titles; + titles = NULL; + for (i = 0; i < util_array_len(processes); i++) { + (*response)->processes[i] = util_strdup_s(processes[i]); + } + (*response)->processes_len = util_array_len(processes); + +pack_response: + if (*response != NULL) { + (*response)->cc = cc; + } + + free(pids); + container_unref(cont); + free(stdout_buffer); + stdout_buffer = NULL; + free(stderr_buffer); + stderr_buffer = NULL; + free(pid_args); + free(titles); + util_free_array(processes); + free_log_prefix(); + DAEMON_CLEAR_ERRMSG(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int dup_path_and_args(const container_t *cont, char **path, char ***args, size_t *args_len) +{ + int ret = 0; + size_t i = 0; + + if (cont->common_config->path != NULL) { + *path = util_strdup_s(cont->common_config->path); + } + if (cont->common_config->args_len > 0) { + if ((cont->common_config->args_len) > SIZE_MAX / sizeof(char *)) { + ERROR("Containers config args len is too many!"); + ret = -1; + goto out; + } + *args = util_common_calloc_s(cont->common_config->args_len * sizeof(char *)); + if ((*args) == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + for (i = 0; i < cont->common_config->args_len; i++) { + if (cont->common_config->args[i] == NULL) { + ERROR("Input value of args is null"); + ret = -1; + goto out; + } + (*args)[*args_len] = util_strdup_s(cont->common_config->args[i]); + (*args_len)++; + } + } +out: + return ret; +} + +// Always modify this function if host_config.json is modified. +static int dup_host_config(const host_config *src, host_config **dest) +{ + int ret = -1; + char *json = NULL; + parser_error err = NULL; + + if (src == NULL) { + *dest = NULL; + return 0; + } + + json = host_config_generate_json(src, NULL, &err); + if (json == NULL) { + ERROR("Failed to generate json: %s", err); + goto out; + } + *dest = host_config_parse_data(json, NULL, &err); + if (*dest == NULL) { + ERROR("Failed to parse json: %s", err); + goto out; + } + ret = 0; + +out: + free(err); + free(json); + return ret; +} + +static int dup_health_check_config(const container_config *src, container_inspect_config *dest) +{ + int ret = 0; + size_t i = 0; + + if (src == NULL || src->health_check == NULL || dest == NULL) { + return 0; + } + dest->health_check = util_common_calloc_s(sizeof(defs_health_check)); + if (dest->health_check == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + if (src->health_check->test != NULL && src->health_check->test_len != 0) { + if (src->health_check->test_len > SIZE_MAX / sizeof(char *)) { + ERROR("health check test is too much!"); + ret = -1; + goto out; + } + dest->health_check->test = util_common_calloc_s(src->health_check->test_len * sizeof(char *)); + if (dest->health_check->test == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + for (i = 0; i < src->health_check->test_len; i++) { + if (src->health_check->test[i] == NULL) { + ERROR("Input value of src health check test is null"); + ret = -1; + goto out; + } + dest->health_check->test[i] = util_strdup_s(src->health_check->test[i]); + dest->health_check->test_len++; + } + dest->health_check->interval = timeout_with_default(src->health_check->interval, DEFAULT_PROBE_INTERVAL); + dest->health_check->start_period = timeout_with_default(src->health_check->start_period, DEFAULT_START_PERIOD); + dest->health_check->timeout = timeout_with_default(src->health_check->timeout, DEFAULT_PROBE_TIMEOUT); + dest->health_check->retries = src->health_check->retries != 0 ? src->health_check->retries + : DEFAULT_PROBE_RETRIES; + + dest->health_check->exit_on_unhealthy = src->health_check->exit_on_unhealthy; + } +out: + return ret; +} + +static int dup_container_config_env(const container_config *src, container_inspect_config *dest) +{ + int ret = 0; + size_t i = 0; + char *tmpstr = NULL; + + if (src->env != NULL && src->env_len > 0) { + if (src->env_len > SIZE_MAX / sizeof(char *)) { + ERROR("Container inspect config env elements is too much!"); + ret = -1; + goto out; + } + dest->env = util_common_calloc_s(src->env_len * sizeof(char *)); + if (dest->env == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + for (i = 0; i < src->env_len; i++) { + if (src->env[i] == NULL) { + ERROR("Input value of src env is null"); + ret = -1; + goto out; + } + tmpstr = src->env[i]; + dest->env[i] = tmpstr ? util_strdup_s(tmpstr) : NULL; + dest->env_len++; + } + } + +out: + return ret; +} + +static int dup_container_config_cmd_and_entrypoint(const container_config *src, container_inspect_config *dest) +{ + int ret = 0; + + if (src == NULL || dest == NULL) { + return 0; + } + + ret = dup_array_of_strings((const char **)(src->cmd), src->cmd_len, &(dest->cmd), &(dest->cmd_len)); + if (ret != 0) { + goto out; + } + + ret = dup_array_of_strings((const char **)(src->entrypoint), src->entrypoint_len, &(dest->entrypoint), + &(dest->entrypoint_len)); +out: + return ret; +} + +static int dup_container_config_labels(const container_config *src, container_inspect_config *dest) +{ + int ret = 0; + + if (src->labels != NULL) { + dest->labels = util_common_calloc_s(sizeof(json_map_string_string)); + if (dest->labels == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + ret = dup_json_map_string_string(src->labels, dest->labels); + if (ret != 0) { + goto out; + } + } +out: + return ret; +} + +static int dup_container_config_annotations(const container_config *src, container_inspect_config *dest) +{ + int ret = 0; + + if (src->annotations != NULL) { + dest->annotations = util_common_calloc_s(sizeof(json_map_string_string)); + if (dest->annotations == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + ret = dup_json_map_string_string(src->annotations, dest->annotations); + if (ret != 0) { + goto out; + } + } +out: + return ret; +} + +static int dup_container_config(const container_config *src, container_inspect_config *dest) +{ + int ret = 0; + + if (src == NULL || dest == NULL) { + return 0; + } + + dest->hostname = src->hostname ? util_strdup_s(src->hostname) : util_strdup_s(""); + dest->user = src->user ? util_strdup_s(src->user) : util_strdup_s(""); + dest->tty = src->tty; + + if (dup_container_config_env(src, dest) != 0) { + ret = -1; + goto out; + } + + if (dup_container_config_cmd_and_entrypoint(src, dest) != 0) { + ret = -1; + goto out; + } + + if (dup_container_config_labels(src, dest) != 0) { + ret = -1; + goto out; + } + + if (dup_container_config_annotations(src, dest) != 0) { + ret = -1; + goto out; + } + + if (dup_health_check_config(src, dest) != 0) { + ERROR("Failed to duplicate health check config"); + ret = -1; + goto out; + } +out: + return ret; +} + +static int mount_point_to_inspect(const container_t *cont, container_inspect *inspect) +{ + size_t i, len; + + if (cont->common_config->mount_points == NULL || cont->common_config->mount_points->len == 0) { + return 0; + } + + len = cont->common_config->mount_points->len; + if (len > SIZE_MAX / sizeof(docker_types_mount_point *)) { + ERROR("Invalid mount point size"); + return -1; + } + inspect->mounts = util_common_calloc_s(sizeof(docker_types_mount_point *) * len); + if (inspect->mounts == NULL) { + ERROR("Out of memory"); + return -1; + } + for (i = 0; i < len; i++) { + container_config_v2_common_config_mount_points_element *mp = cont->common_config->mount_points->values[i]; + inspect->mounts[i] = util_common_calloc_s(sizeof(docker_types_mount_point)); + if (inspect->mounts[i] == NULL) { + ERROR("Out of memory"); + return -1; + } + inspect->mounts[i]->source = util_strdup_s(mp->source); + inspect->mounts[i]->destination = util_strdup_s(mp->destination); + inspect->mounts[i]->name = util_strdup_s(mp->name); + inspect->mounts[i]->driver = util_strdup_s(mp->driver); + inspect->mounts[i]->mode = util_strdup_s(mp->relabel); + inspect->mounts[i]->propagation = util_strdup_s(mp->propagation); + inspect->mounts[i]->rw = mp->rw; + + inspect->mounts_len++; + } + return 0; +} + +static int pack_inspect_container_state(const container_t *cont, container_inspect *inspect) +{ + int ret = 0; + container_config_v2_state *cont_state = NULL; + + cont_state = state_get_info(cont->state); + if (cont_state == NULL) { + ERROR("Failed to read %s state", cont->common_config->id); + ret = -1; + goto out; + } + + inspect->state = util_common_calloc_s(sizeof(container_inspect_state)); + if (inspect->state == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + inspect->state->status = util_strdup_s(state_to_string(state_judge_status(cont_state))); + inspect->state->running = cont_state->running; + inspect->state->paused = cont_state->paused; + inspect->state->restarting = cont_state->restarting; + inspect->state->pid = cont_state->pid; + + inspect->state->exit_code = cont_state->exit_code; + inspect->state->started_at = cont_state->started_at ? util_strdup_s(cont_state->started_at) + : util_strdup_s(defaultContainerTime); + inspect->state->finished_at = cont_state->finished_at ? util_strdup_s(cont_state->finished_at) + : util_strdup_s(defaultContainerTime); + inspect->state->error = cont->state->state->error ? util_strdup_s(cont->state->state->error) : NULL; + inspect->restart_count = cont->common_config->restart_count; + + if (dup_health_check_status(&inspect->state->health, cont_state->health) != 0) { + ERROR("Failed to dup health check info"); + ret = -1; + goto out; + } +out: + free_container_config_v2_state(cont_state); + return ret; +} + +static int pack_inspect_host_config(const container_t *cont, container_inspect *inspect) +{ + int ret = 0; + host_config *hostconfig = NULL; + + hostconfig = cont->hostconfig; + if (hostconfig == NULL) { + ERROR("Failed to read host config"); + ret = -1; + goto out; + } + + if (dup_host_config(hostconfig, &inspect->host_config) != 0) { + ERROR("Failed to dup host config"); + ret = -1; + goto out; + } + + if (cont->runtime != NULL) { + free(inspect->host_config->runtime); + inspect->host_config->runtime = util_strdup_s(cont->runtime); + } +out: + return ret; +} + +static int pack_inspect_general_data(const container_t *cont, container_inspect *inspect) +{ + int ret = 0; + + inspect->id = util_strdup_s(cont->common_config->id); + inspect->name = util_strdup_s(cont->common_config->name); + if (cont->common_config->created != NULL) { + inspect->created = util_strdup_s(cont->common_config->created); + } + + if (dup_path_and_args(cont, &(inspect->path), &(inspect->args), &(inspect->args_len)) != 0) { + ERROR("Failed to dup path and args"); + ret = -1; + goto out; + } + + inspect->image = cont->common_config->image ? util_strdup_s(cont->common_config->image) : util_strdup_s("none"); + + if (cont->common_config->log_path != NULL) { + inspect->log_path = util_strdup_s(cont->common_config->log_path); + } + + if (cont->common_config->hosts_path != NULL) { + inspect->hosts_path = util_strdup_s(cont->common_config->hosts_path); + } + if (cont->common_config->resolv_conf_path != NULL) { + inspect->resolv_conf_path = util_strdup_s(cont->common_config->resolv_conf_path); + } + + if (mount_point_to_inspect(cont, inspect) != 0) { + ERROR("Failed to transform to mount point"); + ret = -1; + goto out; + } +out: + return ret; +} + +static int pack_inspect_config(const container_t *cont, container_inspect *inspect) +{ + int ret = 0; + + inspect->config = util_common_calloc_s(sizeof(container_inspect_config)); + if (inspect->config == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (dup_container_config(cont->common_config->config, inspect->config) != 0) { + ERROR("Failed to dup container config"); + ret = -1; + goto out; + } +out: + return ret; +} + +static int merge_default_ulimit_with_ulimit(container_inspect *out_inspect) +{ + int ret = 0; + host_config_ulimits_element **rlimits = NULL; + size_t i, j, ulimits_len; + + if (conf_get_lcrd_default_ulimit(&rlimits) != 0) { + ERROR("Failed to get lcrd default ulimit"); + ret = -1; + goto out; + } + + ulimits_len = ulimit_array_len(rlimits); + for (i = 0; i < ulimits_len; i++) { + for (j = 0; j < out_inspect->host_config->ulimits_len; j++) { + if (strcmp(rlimits[i]->name, out_inspect->host_config->ulimits[j]->name) == 0) { + break; + } + } + if (j < out_inspect->host_config->ulimits_len) { + continue; + } + + if (ulimit_array_append(&out_inspect->host_config->ulimits, rlimits[i], + out_inspect->host_config->ulimits_len) != 0) { + ERROR("ulimit append failed"); + ret = -1; + goto out; + } + out_inspect->host_config->ulimits_len++; + } + +out: + free_default_ulimit(rlimits); + return ret; +} + + +static int pack_inspect_data(const container_t *cont, container_inspect **out_inspect) +{ + int ret = 0; + container_inspect *inspect = NULL; + + inspect = util_common_calloc_s(sizeof(container_inspect)); + if (inspect == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (pack_inspect_general_data(cont, inspect) != 0) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (pack_inspect_container_state(cont, inspect) != 0) { + ret = -1; + goto out; + } + + if (pack_inspect_host_config(cont, inspect) != 0) { + ret = -1; + goto out; + } + + if (merge_default_ulimit_with_ulimit(inspect) != 0) { + ret = -1; + goto out; + } + + if (pack_inspect_config(cont, inspect) != 0) { + ret = -1; + goto out; + } +out: + *out_inspect = inspect; + return ret; +} + +/* + * RETURN VALUE: + * 0: inspect success + * -1: no such container with "id" + * -2: have the container with "id", but failed to inspect due to other reasons +*/ +static int inspect_container_helper(const char *id, int timeout, char **container_json) +{ + int ret = 0; + container_inspect *inspect = NULL; + parser_error err = NULL; + container_t *cont = NULL; + struct parser_context ctx = { OPT_GEN_KAY_VALUE | OPT_GEN_SIMPLIFY, 0 }; + + if (!util_valid_container_id_or_name(id)) { + ERROR("Inspect invalid name %s", id); + lcrd_set_error_message("Inspect invalid name %s", id); + ret = -1; + goto out; + } + + cont = containers_store_get(id); + if (cont == NULL) { + ret = -1; + lcrd_try_set_error_message("No such image or container or accelerator:%s", id); + goto out; + } + + ret = container_timedlock(cont, timeout); + if (ret != 0) { + ERROR("Container %s inspect failed due to trylock timeout for %ds.", id, timeout); + lcrd_try_set_error_message("Container %s inspect failed due to trylock timeout for %ds.", id, timeout); + ret = -2; + goto out; + } + + if (pack_inspect_data(cont, &inspect) != 0) { + ret = -2; + goto unlock; + } + + *container_json = container_inspect_generate_json(inspect, &ctx, &err); + if (*container_json == NULL) { + ERROR("Failed to generate inspect json:%s", err); + ret = -2; + goto unlock; + } + +unlock: + container_unlock(cont); +out: + container_unref(cont); + free_container_inspect(inspect); + free(err); + + return ret; +} + +static int container_inspect_cb(const container_inspect_request *request, container_inspect_response **response) +{ + int timeout = 0; + uint32_t cc = LCRD_SUCCESS; + char *name = NULL; + char *container_json = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_inspect_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + timeout = request->timeout; + + if (name == NULL) { + ERROR("receive NULL Request id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + set_log_prefix(name); + + INFO("Inspect :%s", name); + + if (inspect_container_helper(name, timeout, &container_json) != 0) { + cc = LCRD_ERR_EXEC; + } + +pack_response: + if (*response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + (*response)->container_json = container_json; + } + + free_log_prefix(); + malloc_trim(0); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static void pack_wait_response(container_wait_response *response, uint32_t cc, uint32_t exit_code) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + response->exit_code = exit_code; +} + +static int container_wait_cb(const container_wait_request *request, container_wait_response **response) +{ + char *name = NULL; + char *id = NULL; + uint32_t cc = LCRD_SUCCESS; + uint32_t exit_code = 0; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_wait_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + + if (name == NULL) { + DEBUG("receive NULL Request id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + cont = containers_store_get(name); + if (cont == NULL) { + ERROR("No such container '%s'", name); + cc = LCRD_ERR_EXEC; + lcrd_try_set_error_message("No such container:%s", name); + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + if (request->condition == WAIT_CONDITION_STOPPED) { + (void)container_wait_stop_locking(cont, -1); + } else { + (void)container_wait_rm_locking(cont, -1); + } + + exit_code = state_get_exitcode(cont->state); + + INFO("Wait Container:%s", id); + +pack_response: + pack_wait_response(*response, cc, exit_code); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int container_conf_request_check(const struct lcrd_container_conf_request *h) +{ + int ret = 0; + + if (h->name == NULL) { + ERROR("Receive NULL container name"); + ret = -1; + goto out; + } + + if (!util_valid_container_id_or_name(h->name)) { + ERROR("Invalid container name %s", h->name); + lcrd_set_error_message("Invalid container name %s", h->name); + ret = -1; + goto out; + } + +out: + return ret; +} + +static void pack_container_conf_response(struct lcrd_container_conf_response *response, uint32_t cc, + const struct engine_console_config *config) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + + if (config->log_path != NULL) { + response->container_logpath = util_strdup_s(config->log_path); + } + response->container_logrotate = (uint32_t)config->log_rotate; + if (config->log_file_size != NULL) { + response->container_logsize = util_strdup_s(config->log_file_size); + } +} + +static int container_conf_cb(const struct lcrd_container_conf_request *request, + struct lcrd_container_conf_response **response) +{ + char *id = NULL; + uint32_t cc = LCRD_SUCCESS; + struct engine_operation *engine_ops = NULL; + struct engine_console_config config = { 0 }; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(struct lcrd_container_conf_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + if (container_conf_request_check(request) != 0) { + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + cont = containers_store_get(request->name); + if (cont == NULL) { + ERROR("No such container:%s", request->name); + lcrd_set_error_message("No such container:%s", request->name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + engine_ops = engines_get_handler(cont->runtime); + if (engine_ops == NULL || engine_ops->engine_free_console_config_op == NULL) { + ERROR("Failed to get engine free_console_config operation"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + if (runtime_get_console_config(id, cont->runtime, cont->root_path, &config) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + +pack_response: + pack_container_conf_response(*response, cc, &config); + container_unref(cont); + if (engine_ops != NULL && engine_ops->engine_free_console_config_op != NULL) { + engine_ops->engine_free_console_config_op(&config); + } + + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int rename_request_check(const struct lcrd_container_rename_request *request) +{ + int ret = 0; + + if (!util_valid_str(request->old_name) || !util_valid_str(request->new_name)) { + ERROR("Neither old nor new names may be empty"); + lcrd_set_error_message("Neither old nor new names may be empty"); + ret = -1; + goto out; + } + + if (!util_valid_container_id_or_name(request->old_name)) { + ERROR("Invalid container old name (%s)", request->old_name); + lcrd_set_error_message("Invalid container old name (%s)", request->old_name); + ret = -1; + goto out; + } + + if (!util_valid_container_name(request->new_name)) { + ERROR("Invalid container new name (%s), only [a-zA-Z0-9][a-zA-Z0-9_.-]+$ are allowed.", request->new_name); + lcrd_set_error_message("Invalid container new name (%s), only [a-zA-Z0-9][a-zA-Z0-9_.-]+$ are allowed.", + request->new_name); + ret = -1; + goto out; + } + +out: + return ret; +} + +static void pack_rename_response(struct lcrd_container_rename_response *response, const char *id, uint32_t cc) +{ + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + if (id != NULL) { + response->id = util_strdup_s(id); + } +} + +static void restore_names_at_fail(container_t *cont, const char *ori_name, const char *new_name) +{ + const char *id = cont->common_config->id; + + free(cont->common_config->name); + cont->common_config->name = util_strdup_s(ori_name); + + if (!name_index_rename(ori_name, new_name, id)) { + ERROR("Failed to restore name from \"%s\" to \"%s\" for container %s", new_name, ori_name, id); + } +} + +static int container_rename(container_t *cont, const char *new_name) +{ + int ret = 0; + char *id = cont->common_config->id; + char *old_name = NULL; + + container_lock(cont); + + old_name = util_strdup_s(cont->common_config->name); + + if (strcmp(old_name, new_name) == 0) { + ERROR("Renaming a container with the same name as its current name"); + lcrd_set_error_message("Renaming a container with the same name as its current name"); + ret = -1; + goto out; + } + + if (is_removal_in_progress(cont->state) || is_dead(cont->state)) { + ERROR("Can't rename container which is dead or marked for removal"); + lcrd_set_error_message("Can't rename container which is dead or marked for removal"); + ret = -1; + goto out; + } + + if (!name_index_rename(new_name, old_name, id)) { + ERROR("Name %s is in use", new_name); + lcrd_set_error_message("Conflict. The name \"%s\" is already in use by container %s. " + "You have to remove (or rename) that container to be able to reuse that name.", + new_name, new_name); + ret = -1; + goto out; + } + + free(cont->common_config->name); + cont->common_config->name = util_strdup_s(new_name); + + if (container_to_disk(cont) != 0) { + ERROR("Failed to save container config of %s in renaming %s progress", id, new_name); + lcrd_set_error_message("Failed to save container config of %s in renaming %s progress", id, new_name); + ret = -1; + goto restore; + } + + goto out; + +restore: + restore_names_at_fail(cont, old_name, new_name); +out: + container_unlock(cont); + free(old_name); + return ret; +} + +static int container_rename_cb(const struct lcrd_container_rename_request *request, + struct lcrd_container_rename_response **response) +{ + uint32_t cc = LCRD_SUCCESS; + char *id = NULL; + char *old_name = NULL; + char *new_name = NULL; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_create_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (rename_request_check(request) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + old_name = request->old_name; + new_name = request->new_name; + + cont = containers_store_get(old_name); + if (cont == NULL) { + ERROR("No such container:%s", old_name); + lcrd_set_error_message("No such container:%s", old_name); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + id = cont->common_config->id; + set_log_prefix(id); + + EVENT("Event: {Object: %s, Type: Renaming}", id); + + if (container_rename(cont, new_name) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: Renamed to %s}", id, new_name); + goto pack_response; + +pack_response: + pack_rename_response(*response, id, cc); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +void container_information_callback_init(service_container_callback_t *cb) +{ + cb->version = container_version_cb; + cb->info = isulad_info_cb; + cb->inspect = container_inspect_cb; + cb->list = container_list_cb; + cb->wait = container_wait_cb; + cb->conf = container_conf_cb; + cb->top = container_top_cb; + cb->rename = container_rename_cb; +} + diff --git a/src/services/execution/execute/execution_information.h b/src/services/execution/execute/execution_information.h new file mode 100644 index 0000000..dec1fbd --- /dev/null +++ b/src/services/execution/execute/execution_information.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + *********************************************************************************/ +#ifndef __EXECUTION_INFORMATION_H_ +#define __EXECUTION_INFORMATION_H_ + + +#include "callback.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void container_information_callback_init(service_container_callback_t *cb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/execution/execute/execution_network.c b/src/services/execution/execute/execution_network.c new file mode 100644 index 0000000..6bf6162 --- /dev/null +++ b/src/services/execution/execute/execution_network.c @@ -0,0 +1,907 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container network callback function definition + ********************************************************************************/ +#include "execution_network.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "lcrd_config.h" +#include "config.h" +#include "containers_store.h" +#include "namespace.h" + +static int write_hostname_to_file(const char *rootfs, const char *hostname) +{ + int ret = 0; + size_t path_len = 0; + char *file_path = NULL; + + path_len = strlen(rootfs) + strlen("/etc/hostname") + 1; + if (path_len > PATH_MAX) { + ERROR("Invalid path length"); + return -1; + } + file_path = util_common_calloc_s(path_len); + if (file_path == NULL) { + ERROR("Failed to malloc hostname path memory"); + return -1; + } + if (sprintf_s(file_path, path_len, "%s%s", rootfs, "/etc/hostname") < 0) { + ERROR("Failed to print string"); + goto error_out; + } + if (hostname != NULL) { + ret = util_write_file(file_path, hostname, strlen(hostname)); + if (ret) { + SYSERROR("Failed to write %s", file_path); + lcrd_set_error_message("Failed to write %s: %s", file_path, strerror(errno)); + goto error_out; + } + } + +error_out: + free(file_path); + return ret; +} + +static int fopen_network(FILE **fp, char **file_path, const char *rootfs, const char *filename) +{ + size_t path_len = strlen(rootfs) + strlen(filename) + 1; + if (path_len > PATH_MAX) { + ERROR("Invalid path length"); + return -1; + } + *file_path = util_common_calloc_s(path_len); + if (*file_path == NULL) { + ERROR("Failed to malloc network path memory"); + return -1; + } + if (sprintf_s(*file_path, path_len, "%s%s", rootfs, filename) < 0) { + ERROR("Failed to print string"); + return -1; + } + *fp = util_fopen(*file_path, "a+"); + if (*fp == NULL) { + SYSERROR("Failed to open %s", *file_path); + lcrd_set_error_message("Failed to open %s: %s", *file_path, strerror(errno)); + return -1; + } + return 0; +} + +static int get_content_and_hosts_map(FILE *fp, char **content, json_map_string_bool *hosts_map) +{ + int ret = 0; + size_t length = 0; + char *pline = NULL; + char *tmp = NULL; + char *host_name = NULL; + char *host_ip = NULL; + char *saveptr = NULL; + + while (getline(&pline, &length, fp) != -1) { + char *tmp_str = NULL; + char host_key[MAX_BUFFER_SIZE] = { 0 }; + if (pline == NULL) { + ERROR("get hosts content failed"); + return -1; + } + if (pline[0] == '#') { + tmp = util_string_append(pline, *content); + free(*content); + *content = tmp; + continue; + } + tmp_str = util_strdup_s(pline); + if (tmp_str == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + util_trim_newline(tmp_str); + host_ip = strtok_r(tmp_str, " ", &saveptr); + host_name = strtok_r(NULL, " ", &saveptr); + if (host_ip != NULL && host_name != NULL) { + if (sprintf_s(host_key, sizeof(host_key), "%s:%s", host_ip, host_name) < 0) { + free(tmp_str); + ERROR("Out of memory"); + ret = -1; + goto out; + } + if (append_json_map_string_bool(hosts_map, host_key, true)) { + free(tmp_str); + ERROR("append data to hosts map failed"); + ret = -1; + goto out; + } + tmp = util_string_append(pline, *content); + free(*content); + *content = tmp; + } + free(tmp_str); + } + +out: + free(pline); + return ret; +} + +static int write_content_to_file(const char *file_path, const char *content) +{ + int ret = 0; + + if (content != NULL) { + ret = util_write_file(file_path, content, strlen(content)); + if (ret != 0) { + SYSERROR("Failed to write file %s", file_path); + lcrd_set_error_message("Failed to write file %s: %s", file_path, strerror(errno)); + return ret; + } + } + return ret; +} + +static int merge_hosts_content(const host_config *host_spec, char **content, json_map_string_bool *hosts_map) +{ + size_t i, j; + char *tmp = NULL; + char *saveptr = NULL; + + for (i = 0; i < host_spec->extra_hosts_len; i++) { + bool need_to_add = true; + char *host_name = NULL; + char *host_ip = NULL; + char *hosts = NULL; + char host_key[MAX_BUFFER_SIZE] = { 0 }; + hosts = util_strdup_s(host_spec->extra_hosts[i]); + if (hosts == NULL) { + ERROR("Out of memory"); + return -1; + } + host_name = strtok_r(hosts, ":", &saveptr); + host_ip = strtok_r(NULL, ":", &saveptr); + if (host_name == NULL || host_ip == NULL) { + free(hosts); + ERROR("extra host '%s' format error.", host_spec->extra_hosts[i]); + return -1; + } + if (sprintf_s(host_key, sizeof(host_key), "%s:%s", host_ip, host_name) < 0) { + free(hosts); + ERROR("Out of memory"); + return -1; + } + for (j = 0; j < hosts_map->len; j++) { + if (strcmp(host_key, hosts_map->keys[j]) == 0) { + need_to_add = false; + break; + } + } + if (need_to_add) { + tmp = util_string_append(host_ip, *content); + free(*content); + *content = tmp; + tmp = util_string_append(" ", *content); + free(*content); + *content = tmp; + tmp = util_string_append(host_name, *content); + free(*content); + *content = tmp; + tmp = util_string_append("\n", *content); + free(*content); + *content = tmp; + if (append_json_map_string_bool(hosts_map, host_key, true)) { + free(hosts); + ERROR("append data to hosts map failed"); + return -1; + } + } + free(hosts); + } + return 0; +} + +static int merge_hosts(const host_config *host_spec, const char *rootfs) +{ + int ret = 0; + char *content = NULL; + char *file_path = NULL; + FILE *fp = NULL; + json_map_string_bool *hosts_map = NULL; + + hosts_map = (json_map_string_bool *)util_common_calloc_s(sizeof(json_map_string_bool)); + if (hosts_map == NULL) { + ERROR("Out of memory"); + ret = -1; + goto error_out; + } + ret = fopen_network(&fp, &file_path, rootfs, "/etc/hosts"); + if (ret != 0) { + goto error_out; + } + ret = get_content_and_hosts_map(fp, &content, hosts_map); + if (ret != 0) { + goto error_out; + } + ret = merge_hosts_content(host_spec, &content, hosts_map); + if (ret != 0) { + goto error_out; + } + ret = write_content_to_file(file_path, content); + if (ret != 0) { + goto error_out; + } + +error_out: + free(content); + free(file_path); + if (fp != NULL) { + fclose(fp); + } + free_json_map_string_bool(hosts_map); + return ret; +} + +static int merge_dns_search(const host_config *host_spec, char **content, const char *token, char *saveptr) +{ + int ret = 0; + size_t i, j; + size_t content_len = strlen(*content); + char *tmp = NULL; + json_map_string_bool *dns_search_map = NULL; + + dns_search_map = (json_map_string_bool *)util_common_calloc_s(sizeof(json_map_string_bool)); + if (dns_search_map == NULL) { + ERROR("Out of memory"); + ret = -1; + goto error_out; + } + while (token != NULL) { + token = strtok_r(NULL, " ", &saveptr); + if (token != NULL) { + if (append_json_map_string_bool(dns_search_map, token, true)) { + ERROR("append data to dns search map failed"); + ret = -1; + goto error_out; + } + } + } + for (i = 0; i < host_spec->dns_search_len; i++) { + bool need_to_add = true; + for (j = 0; j < dns_search_map->len; j++) { + if (strcmp(host_spec->dns_search[i], dns_search_map->keys[j]) == 0) { + need_to_add = false; + break; + } + } + if (need_to_add) { + if (strlen(*content) > 0) { + (*content)[strlen(*content) - 1] = ' '; + } + tmp = util_string_append(host_spec->dns_search[i], *content); + free(*content); + *content = tmp; + tmp = util_string_append(" ", *content); + free(*content); + *content = tmp; + if (append_json_map_string_bool(dns_search_map, host_spec->dns_search[i], true)) { + ERROR("append data to dns search map failed"); + ret = -1; + goto error_out; + } + } + } + if (strlen(*content) > content_len) { + (*content)[strlen(*content) - 1] = '\n'; + } + +error_out: + free_json_map_string_bool(dns_search_map); + return ret; +} + +static int merge_dns_options(const host_config *host_spec, char **content, const char *token, char *saveptr) +{ + int ret = 0; + size_t i, j; + size_t content_len = strlen(*content); + char *tmp = NULL; + json_map_string_bool *dns_options_map = NULL; + + dns_options_map = (json_map_string_bool *)util_common_calloc_s(sizeof(json_map_string_bool)); + if (dns_options_map == NULL) { + ERROR("Out of memory"); + ret = -1; + goto error_out; + } + while (token != NULL) { + token = strtok_r(NULL, " ", &saveptr); + if (token != NULL) { + if (append_json_map_string_bool(dns_options_map, token, true)) { + ERROR("append data to dns options map failed"); + ret = -1; + goto error_out; + } + } + } + for (i = 0; i < host_spec->dns_options_len; i++) { + bool need_to_add = true; + for (j = 0; j < dns_options_map->len; j++) { + if (strcmp(host_spec->dns_options[i], dns_options_map->keys[j]) == 0) { + need_to_add = false; + break; + } + } + if (need_to_add) { + if (strlen(*content) > 0) { + (*content)[strlen(*content) - 1] = ' '; + } + tmp = util_string_append(host_spec->dns_options[i], *content); + free(*content); + *content = tmp; + tmp = util_string_append(" ", *content); + free(*content); + *content = tmp; + if (append_json_map_string_bool(dns_options_map, host_spec->dns_options[i], true)) { + ERROR("append data to dns options map failed"); + ret = -1; + goto error_out; + } + } + } + if (strlen(*content) > content_len) { + (*content)[strlen(*content) - 1] = '\n'; + } + +error_out: + free_json_map_string_bool(dns_options_map); + return ret; +} + +static int merge_dns(const host_config *host_spec, char **content, json_map_string_bool *dns_map) +{ + size_t i, j; + char *tmp = NULL; + + for (i = 0; i < host_spec->dns_len; i++) { + bool need_to_add = true; + for (j = 0; j < dns_map->len; j++) { + if (strcmp(host_spec->dns[i], dns_map->keys[j]) == 0) { + need_to_add = false; + break; + } + } + if (need_to_add) { + tmp = util_string_append("nameserver ", *content); + free(*content); + *content = tmp; + tmp = util_string_append(host_spec->dns[i], *content); + free(*content); + *content = tmp; + tmp = util_string_append("\n", *content); + free(*content); + *content = tmp; + if (append_json_map_string_bool(dns_map, host_spec->dns[i], true)) { + ERROR("append data to dns map failed"); + return -1; + } + } + } + return 0; +} + +static bool is_need_add(const char *dns_search, const json_map_string_bool *dns_search_map) +{ + bool need_to_add = true; + size_t j; + + for (j = 0; j < dns_search_map->len; j++) { + if (strcmp(dns_search, dns_search_map->keys[j]) == 0) { + need_to_add = false; + break; + } + } + + return need_to_add; +} + +static int generate_new_search(const host_config *host_spec, + json_map_string_bool *dns_search_map, + char **content, + bool search) +{ + char *tmp = NULL; + + if (!search && host_spec->dns_search_len > 0) { + size_t i; + tmp = util_string_append("search ", *content); + free(*content); + *content = tmp; + for (i = 0; i < host_spec->dns_search_len; i++) { + if (!is_need_add(host_spec->dns_search[i], dns_search_map)) { + continue; + } + + if (append_json_map_string_bool(dns_search_map, host_spec->dns_search[i], true)) { + ERROR("append data to dns search map failed"); + return -1; + } + tmp = util_string_append(host_spec->dns_search[i], *content); + free(*content); + *content = tmp; + tmp = util_string_append(" ", *content); + free(*content); + *content = tmp; + } + tmp = util_string_append("\n", *content); + free(*content); + *content = tmp; + } + return 0; +} + +static int generate_new_options(const host_config *host_spec, + json_map_string_bool *dns_options_map, + char **content, + bool options) +{ + char *tmp = NULL; + + if (!options && host_spec->dns_options_len > 0) { + size_t i; + tmp = util_string_append("options ", *content); + free(*content); + *content = tmp; + for (i = 0; i < host_spec->dns_options_len; i++) { + if (!is_need_add(host_spec->dns_options[i], dns_options_map)) { + continue; + } + + if (append_json_map_string_bool(dns_options_map, host_spec->dns_options[i], true)) { + ERROR("append data to dns options map failed"); + return -1; + } + tmp = util_string_append(host_spec->dns_options[i], *content); + free(*content); + *content = tmp; + tmp = util_string_append(" ", *content); + free(*content); + *content = tmp; + } + tmp = util_string_append("\n", *content); + free(*content); + *content = tmp; + } + return 0; +} + +static int generate_new_search_and_options(const host_config *host_spec, char **content, bool search, bool options) +{ + int ret = 0; + json_map_string_bool *dns_search_map = NULL; + json_map_string_bool *dns_options_map = NULL; + + dns_search_map = (json_map_string_bool *)util_common_calloc_s(sizeof(json_map_string_bool)); + if (dns_search_map == NULL) { + ERROR("Out of memory"); + ret = -1; + goto error_out; + } + dns_options_map = (json_map_string_bool *)util_common_calloc_s(sizeof(json_map_string_bool)); + if (dns_options_map == NULL) { + ERROR("Out of memory"); + ret = -1; + goto error_out; + } + ret = generate_new_search(host_spec, dns_search_map, content, search); + if (ret) { + goto error_out; + } + ret = generate_new_options(host_spec, dns_options_map, content, options); + if (ret) { + goto error_out; + } + +error_out: + free_json_map_string_bool(dns_search_map); + free_json_map_string_bool(dns_options_map); + return ret; +} + +static int resolve_handle_content(const char *pline, const host_config *host_spec, + char **content, json_map_string_bool *dns_map, bool *search, bool *options) +{ + int ret = 0; + char *tmp = NULL; + char *token = NULL; + char *saveptr = NULL; + char *tmp_str = NULL; + + if (pline[0] == '#') { + tmp = util_string_append(pline, *content); + free(*content); + *content = tmp; + return 0; + } + tmp_str = util_strdup_s(pline); + if (tmp_str == NULL) { + ERROR("Out of memory"); + ret = -1; + goto cleanup; + } + util_trim_newline(tmp_str); + tmp_str = util_trim_space(tmp_str); + if (strcmp("", tmp_str) == 0) { + goto cleanup; + } + token = strtok_r(tmp_str, " ", &saveptr); + if (token == NULL) { + ret = -1; + goto cleanup; + } + if (strcmp(token, "search") == 0) { + *search = true; + tmp = util_string_append(pline, *content); + if (tmp == NULL) { + ERROR("Out of memory"); + ret = -1; + goto cleanup; + } + free(*content); + *content = tmp; + ret = merge_dns_search(host_spec, content, token, saveptr); + } else if (strcmp(token, "options") == 0) { + *options = true; + tmp = util_string_append(pline, *content); + if (tmp == NULL) { + ERROR("Out of memory"); + ret = -1; + goto cleanup; + } + free(*content); + *content = tmp; + ret = merge_dns_options(host_spec, content, token, saveptr); + } else if (strcmp(token, "nameserver") == 0) { + tmp = util_string_append(pline, *content); + free(*content); + *content = tmp; + token = strtok_r(NULL, " ", &saveptr); + if (token == NULL) { + ret = -1; + goto cleanup; + } + if (append_json_map_string_bool(dns_map, token, true)) { + ERROR("append data to dns map failed"); + ret = -1; + goto cleanup; + } + } +cleanup: + free(tmp_str); + return ret; +} + +static int merge_resolv(const host_config *host_spec, const char *rootfs) +{ + int ret = 0; + size_t length = 0; + bool search = false; + bool options = false; + char *pline = NULL; + char *content = NULL; + char *file_path = NULL; + FILE *fp = NULL; + json_map_string_bool *dns_map = NULL; + + dns_map = (json_map_string_bool *)util_common_calloc_s(sizeof(json_map_string_bool)); + if (dns_map == NULL) { + ERROR("Out of memory"); + ret = -1; + goto error_out; + } + ret = fopen_network(&fp, &file_path, rootfs, "/etc/resolv.conf"); + if (ret != 0) { + goto error_out; + } + + while (getline(&pline, &length, fp) != -1) { + if (pline == NULL) { + ERROR("get resolv content failed"); + ret = -1; + goto error_out; + } + ret = resolve_handle_content(pline, host_spec, &content, dns_map, &search, &options); + if (ret != 0) { + goto error_out; + } + } + ret = merge_dns(host_spec, &content, dns_map); + if (ret) { + goto error_out; + } + ret = generate_new_search_and_options(host_spec, &content, search, options); + if (ret) { + goto error_out; + } + ret = write_content_to_file(file_path, content); + if (ret) { + goto error_out; + } + +error_out: + free(pline); + free(file_path); + free(content); + if (fp != NULL) { + fclose(fp); + } + free_json_map_string_bool(dns_map); + return ret; +} + +static int chown_network(const char *user_remap, const char *rootfs, const char *filename) +{ + int ret = 0; + size_t path_len = 0; + char *file_path = NULL; + unsigned int host_uid = 0; + unsigned int host_gid = 0; + unsigned int size = 0; + + if (user_remap == NULL) { + return 0; + } + ret = util_parse_user_remap(user_remap, &host_uid, &host_gid, &size); + if (ret) { + ERROR("Failed to parse user remap:'%s'", user_remap); + ret = -1; + goto out; + } + path_len = strlen(rootfs) + strlen(filename) + 1; + if (path_len > PATH_MAX) { + ERROR("Invalid path length"); + ret = -1; + goto out; + } + file_path = util_common_calloc_s(path_len); + if (file_path == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + if (sprintf_s(file_path, path_len, "%s%s", rootfs, filename) < 0) { + ERROR("Failed to print string"); + ret = -1; + goto out; + } + if (chown(file_path, host_uid, host_gid) != 0) { + SYSERROR("Failed to chown network file '%s' to %u:%u", filename, host_uid, host_gid); + lcrd_set_error_message("Failed to chown network file '%s' to %u:%u: %s", + filename, + host_uid, + host_gid, + strerror(errno)); + ret = -1; + goto out; + } + +out: + free(file_path); + return ret; +} + +int merge_network(const host_config *host_spec, const char *rootfs, const char *hostname) +{ + int ret = 0; + + if (host_spec == NULL) { + return -1; + } + if (!host_spec->system_container || rootfs == NULL) { + return 0; + } + ret = write_hostname_to_file(rootfs, hostname); + if (ret) { + return -1; + } + ret = chown_network(host_spec->user_remap, rootfs, "/etc/hostname"); + if (ret) { + return -1; + } + ret = merge_hosts(host_spec, rootfs); + if (ret) { + return -1; + } + ret = chown_network(host_spec->user_remap, rootfs, "/etc/hosts"); + if (ret) { + return -1; + } + ret = merge_resolv(host_spec, rootfs); + if (ret) { + return -1; + } + ret = chown_network(host_spec->user_remap, rootfs, "/etc/resolv.conf"); + if (ret) { + return -1; + } + return 0; +} + +static container_t *get_networked_container(const char *id, const char *connected_id, bool check_state) +{ + container_t *nc = NULL; + + nc = containers_store_get(connected_id); + if (nc == NULL) { + ERROR("No such container: %s", connected_id); + lcrd_set_error_message("No such container: %s", connected_id); + return NULL; + } + if (strcmp(id, nc->common_config->id) == 0) { + ERROR("cannot join own network"); + lcrd_set_error_message("cannot join own network"); + goto cleanup; + } + if (!check_state) { + return nc; + } + if (!is_running(nc->state)) { + ERROR("cannot join network of a non running container: %s", connected_id); + lcrd_set_error_message("cannot join network of a non running container: %s", connected_id); + goto cleanup; + } + if (is_restarting(nc->state)) { + ERROR("Container %s is restarting, wait until the container is running", connected_id); + lcrd_set_error_message("Container %s is restarting, wait until the container is running", connected_id); + goto cleanup; + } + + return nc; + +cleanup: + container_unref(nc); + return NULL; +} + +static int init_container_network_confs_container(const char *id, const host_config *hc, + container_config_v2_common_config *common_config) +{ + int ret = 0; + size_t len = strlen(SHARE_NAMESPACE_PREFIX); + container_t *nc = NULL; + + nc = get_networked_container(id, hc->network_mode + len, false); + if (nc == NULL) { + ERROR("Error to get networked container"); + return -1; + } + + if (nc->common_config->hostname_path != NULL) { + free(common_config->hostname_path); + common_config->hostname_path = util_strdup_s(nc->common_config->hostname_path); + } + if (nc->common_config->hosts_path != NULL) { + free(common_config->hosts_path); + common_config->hosts_path = util_strdup_s(nc->common_config->hosts_path); + } + if (nc->common_config->resolv_conf_path != NULL) { + free(common_config->resolv_conf_path); + common_config->resolv_conf_path = util_strdup_s(nc->common_config->resolv_conf_path); + } + + if (nc->common_config->config != NULL && nc->common_config->config->hostname != NULL) { + if (common_config->config == NULL) { + common_config->config = util_common_calloc_s(sizeof(container_config)); + if (common_config->config == NULL) { + ERROR("Out of memory"); + ret = -1; + goto cleanup; + } + } + + free(common_config->config->hostname); + common_config->config->hostname = util_strdup_s(nc->common_config->config->hostname); + } + +cleanup: + container_unref(nc); + return ret; +} + +int init_container_network_confs(const char *id, const char *rootpath, const host_config *hc, + container_config_v2_common_config *common_config) +{ + int ret = 0; + char file_path[PATH_MAX] = { 0x0 }; + + // is container mode + if (is_container(hc->network_mode)) { + ret = init_container_network_confs_container(id, hc, common_config); + goto cleanup; + } + + // is host mode + if (is_host(hc->network_mode)) { + if (common_config->config == NULL) { + common_config->config = util_common_calloc_s(sizeof(container_config)); + if (common_config->config == NULL) { + ERROR("Out of memory"); + ret = -1; + goto cleanup; + } + } + if (common_config->config->hostname == NULL) { + char hostname[MAX_HOST_NAME_LEN] = { 0x00 }; + ret = gethostname(hostname, sizeof(hostname)); + if (ret != 0) { + ERROR("Get hostname error"); + goto cleanup; + } + common_config->config->hostname = util_strdup_s(hostname); + } + } + + // create hosts, resolv.conf and so + if (sprintf_s(file_path, PATH_MAX, "%s/%s/%s", rootpath, id, "hosts") < 0) { + ERROR("Failed to print string"); + ret = -1; + goto cleanup; + } + free(common_config->hosts_path); + common_config->hosts_path = util_strdup_s(file_path); + if (sprintf_s(file_path, PATH_MAX, "%s/%s/%s", rootpath, id, "resolv.conf") < 0) { + ERROR("Failed to print string"); + ret = -1; + goto cleanup; + } + free(common_config->resolv_conf_path); + common_config->resolv_conf_path = util_strdup_s(file_path); + +cleanup: + return ret; +} + +int container_initialize_networking(const container_t *cont) +{ + int ret = 0; + size_t len = strlen(SHARE_NAMESPACE_PREFIX); + container_t *nc = NULL; + host_config *hc = cont->hostconfig; + + // is container mode + if (is_container(hc->network_mode)) { + nc = get_networked_container(cont->common_config->id, hc->network_mode + len, true); + if (nc == NULL) { + ERROR("Error to get networked container"); + return -1; + } + } + + container_unref(nc); + return ret; +} + diff --git a/src/services/execution/execute/execution_network.h b/src/services/execution/execute/execution_network.h new file mode 100644 index 0000000..512d51c --- /dev/null +++ b/src/services/execution/execute/execution_network.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + *******************************************************************************/ + +#ifndef __EXECUTION_NET_WORK_H_ +#define __EXECUTION_NET_WORK_H_ + +#include "callback.h" +#include "container_unix.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int merge_network(const host_config *host_spec, const char *rootfs, const char *hostname); + +int container_initialize_networking(const container_t *cont); + +int init_container_network_confs(const char *id, const char *rootpath, const host_config *hc, + container_config_v2_common_config *common_config); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/execution/execute/execution_stream.c b/src/services/execution/execute/execution_stream.c new file mode 100644 index 0000000..a7e763f --- /dev/null +++ b/src/services/execution/execute/execution_stream.c @@ -0,0 +1,1945 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container stream callback function definition + ********************************************************************************/ +#define _GNU_SOURCE +#include "execution_stream.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "engine.h" +#include "console.h" +#include "lcrd_config.h" +#include "config.h" +#include "image.h" +#include "path.h" +#include "lcrdtar.h" +#include "container_inspect.h" +#include "containers_store.h" +#include "container_state.h" +#include "containers_gc.h" +#include "error.h" +#include "logger_json_file.h" +#include "constants.h" + +static char *create_single_fifo(const char *statepath, const char *subpath, const char *stdflag) +{ + int nret = 0; + char *fifo_name = NULL; + char fifo_path[PATH_MAX] = { 0 }; + + fifo_name = util_common_calloc_s(PATH_MAX); + if (fifo_name == NULL) { + return NULL; + } + + nret = console_fifo_name(statepath, subpath, stdflag, fifo_name, PATH_MAX, + fifo_path, sizeof(fifo_path), true); + if (nret != 0) { + ERROR("Failed to get console fifo name."); + free(fifo_name); + fifo_name = NULL; + goto out; + } + if (console_fifo_create(fifo_name)) { + ERROR("Failed to create console fifo."); + free(fifo_name); + fifo_name = NULL; + goto out; + } +out: + return fifo_name; +} + +static int do_create_daemon_fifos(const char *statepath, const char *subpath, bool attach_stdin, + bool attach_stdout, bool attach_stderr, char *fifos[]) +{ + int ret = -1; + + if (attach_stdin) { + fifos[0] = create_single_fifo(statepath, subpath, "in"); + if (fifos[0] == NULL) { + goto cleanup; + } + } + + if (attach_stdout) { + fifos[1] = create_single_fifo(statepath, subpath, "out"); + if (fifos[1] == NULL) { + goto cleanup; + } + } + + if (attach_stderr) { + fifos[2] = create_single_fifo(statepath, subpath, "err"); + if (fifos[2] == NULL) { + goto cleanup; + } + } + + ret = 0; + +cleanup: + if (ret != 0) { + console_fifo_delete(fifos[0]); + free(fifos[0]); + fifos[0] = NULL; + console_fifo_delete(fifos[1]); + free(fifos[1]); + fifos[1] = NULL; + console_fifo_delete(fifos[2]); + free(fifos[2]); + fifos[2] = NULL; + } + return ret; +} + +int create_daemon_fifos(const char *id, const char *runtime, bool attach_stdin, bool attach_stdout, bool attach_stderr, + const char *operation, char *fifos[], char **fifopath) +{ + int nret; + int ret = -1; + char *statepath = NULL; + char subpath[PATH_MAX] = { 0 }; + char fifodir[PATH_MAX] = { 0 }; + struct timespec now; + pthread_t tid; + + nret = clock_gettime(CLOCK_REALTIME, &now); + if (nret != 0) { + ERROR("Failed to get time"); + goto cleanup; + } + + tid = pthread_self(); + + statepath = conf_get_routine_statedir(runtime); + if (statepath == NULL) { + ERROR("State path is NULL"); + goto cleanup; + } + + nret = sprintf_s(subpath, PATH_MAX, "%s/%s/%u_%u_%u", id, operation, + (unsigned int)tid, (unsigned int)now.tv_sec, (unsigned int)(now.tv_nsec)); + if (nret < 0) { + ERROR("Failed to print string"); + goto cleanup; + } + + nret = sprintf_s(fifodir, PATH_MAX, "%s/%s", statepath, subpath); + if (nret < 0) { + ERROR("Failed to print string"); + goto cleanup; + } + *fifopath = util_strdup_s(fifodir); + + if (do_create_daemon_fifos(statepath, subpath, attach_stdin, attach_stdout, attach_stderr, fifos) != 0) { + goto cleanup; + } + + ret = 0; +cleanup: + free(statepath); + return ret; +} + +void delete_daemon_fifos(const char *fifopath, const char *fifos[]) +{ + if (fifopath == NULL || fifos == NULL) { + return; + } + if (fifos[0] != NULL) { + console_fifo_delete(fifos[0]); + } + if (fifos[1] != NULL) { + console_fifo_delete(fifos[1]); + } + if (fifos[2] != NULL) { + console_fifo_delete(fifos[2]); + } + if (util_recursive_rmdir(fifopath, 0)) { + WARN("Failed to rmdir:%s", fifopath); + } +} + +int ready_copy_io_data(int sync_fd, bool detach, const char *fifoin, const char *fifoout, const char *fifoerr, + int stdin_fd, struct io_write_wrapper *stdout_handler, struct io_write_wrapper *stderr_handler, + const char *fifos[], pthread_t *tid) +{ + int ret = 0; + size_t len = 0; + struct io_copy_arg io_copy[6]; + + if (fifoin != NULL) { + io_copy[len].srctype = IO_FIFO; + io_copy[len].src = (void *)fifoin; + io_copy[len].dsttype = IO_FIFO; + io_copy[len].dst = (void *)fifos[0]; + len++; + } + if (fifoout != NULL) { + io_copy[len].srctype = IO_FIFO; + io_copy[len].src = (void *)fifos[1]; + io_copy[len].dsttype = IO_FIFO; + io_copy[len].dst = (void *)fifoout; + len++; + } + if (fifoerr != NULL) { + io_copy[len].srctype = IO_FIFO; + io_copy[len].src = (void *)fifos[2]; + io_copy[len].dsttype = IO_FIFO; + io_copy[len].dst = (void *)fifoerr; + len++; + } + + if (stdin_fd > 0) { + io_copy[len].srctype = IO_FD; + io_copy[len].src = &stdin_fd; + io_copy[len].dsttype = IO_FIFO; + io_copy[len].dst = (void *)fifos[0]; + len++; + } + + if (stdout_handler != NULL) { + io_copy[len].srctype = IO_FIFO; + io_copy[len].src = (void *)fifos[1]; + io_copy[len].dsttype = IO_FUNC; + io_copy[len].dst = stdout_handler; + len++; + } + + if (stderr_handler != NULL) { + io_copy[len].srctype = IO_FIFO; + io_copy[len].src = (void *)fifos[2]; + io_copy[len].dsttype = IO_FUNC; + io_copy[len].dst = stderr_handler; + len++; + } + + if (start_io_copy_thread(sync_fd, detach, io_copy, len, tid)) { + ret = -1; + goto out; + } +out: + return ret; +} + +static int runtime_exec(const char *id, const char *runtime, const char *rootpath, const char *engine_log_path, + const char *loglevel, const char *console_fifos[], char * const argv[], + char * const env[], int64_t timeout, pid_t *pid, int *exit_code) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_exec_op == NULL) { + DEBUG("Failed to get engine exec operations"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_exec_op(id, rootpath, engine_log_path, loglevel, + console_fifos, argv, env, timeout, pid, exit_code)) { + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Exec container error;%s", (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? + tmpmsg : DEF_ERR_RUNTIME_STR); + util_contain_errmsg(g_lcrd_errmsg, exit_code); + engine_ops->engine_clear_errmsg_op(); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int exec_container(container_t *cont, const char *runtime, char * const console_fifos[], + size_t argc, const char **argv, size_t env_len, const char **env, + int64_t timeout, pid_t *pid, int *exit_code) +{ + int ret = 0; + size_t i, tmp_env_len, tmp_argc; + char *engine_log_path = NULL; + char *loglevel = NULL; + char *logdriver = NULL; + const char **tmp_argv = NULL; + const char **tmp_env = NULL; + + // Append null pointer to end of argv + tmp_argc = argc + 1; + if (tmp_argc > SIZE_MAX / sizeof(char *)) { + ERROR("Too many parameters!"); + return -1; + } + tmp_argv = util_common_calloc_s(tmp_argc * sizeof(char *)); + if (tmp_argv == NULL) { + FATAL("out of memory"); + return -1; + } + + for (i = 0; i < tmp_argc - 1; i++) { + tmp_argv[i] = argv[i]; + } + + // Append null pointer to end of env + tmp_env_len = env_len + 1; + if (tmp_env_len > SIZE_MAX / sizeof(char *)) { + ERROR("The environment variable length is too long!"); + ret = -1; + goto out; + } + tmp_env = util_common_calloc_s(tmp_env_len * sizeof(char *)); + if (tmp_env == NULL) { + FATAL("out of memory"); + ret = -1; + goto out; + } + + for (i = 0; i < tmp_env_len - 1; i++) { + tmp_env[i] = env[i]; + } + + loglevel = conf_get_lcrd_loglevel(); + if (loglevel == NULL) { + ERROR("Exec: failed to get log level"); + ret = -1; + goto out; + } + logdriver = conf_get_lcrd_logdriver(); + if (logdriver == NULL) { + ERROR("Exec: Failed to get log driver"); + ret = -1; + goto out; + } + engine_log_path = conf_get_engine_log_file(); + if (strcmp(logdriver, "file") == 0 && engine_log_path == NULL) { + ERROR("Exec: Log driver is file, but engine log path is NULL"); + ret = -1; + goto out; + } + + if (runtime_exec(cont->common_config->id, runtime, cont->root_path, engine_log_path, loglevel, + (const char **)console_fifos, (char * const *)tmp_argv, + (char * const *)tmp_env, timeout, pid, exit_code)) { + ERROR("Runtime exec container failed"); + ret = -1; + goto out; + } + +out: + free(loglevel); + free(engine_log_path); + free(logdriver); + free(tmp_argv); + free(tmp_env); + + return ret; +} + +static int container_exec_cb_check(const container_exec_request *request, container_exec_response **response, + uint32_t *cc, container_t **cont) +{ + char *container_name = NULL; + + if (request == NULL) { + return -1; + } + *response = util_common_calloc_s(sizeof(container_exec_response)); + if (*response == NULL) { + ERROR("Out of memory"); + *cc = LCRD_ERR_MEMOUT; + return -1; + } + + container_name = request->container_id; + + if (container_name == NULL) { + ERROR("receive NULL Request id"); + *cc = LCRD_ERR_INPUT; + return -1; + } + + if (!util_valid_container_id_or_name(container_name)) { + ERROR("Invalid container name %s", container_name); + lcrd_set_error_message("Invalid container name %s", container_name); + *cc = LCRD_ERR_EXEC; + return -1; + } + + *cont = containers_store_get(container_name); + if (*cont == NULL) { + ERROR("No such container:%s", container_name); + lcrd_set_error_message("No such container:%s", container_name); + *cc = LCRD_ERR_EXEC; + return -1; + } + + return 0; +} + +static int exec_prepare_console(container_t *cont, const container_exec_request *request, int stdinfd, + struct io_write_wrapper *stdout_handler, char **fifos, + char **fifopath, int *sync_fd, pthread_t *thread_id) +{ + int ret = 0; + const char *id = cont->common_config->id; + + if (request->attach_stdin || request->attach_stdout || request->attach_stderr) { + if (create_daemon_fifos(id, cont->runtime, request->attach_stdin, + request->attach_stdout, request->attach_stderr, + "exec", fifos, fifopath)) { + ret = -1; + goto out; + } + + *sync_fd = eventfd(0, EFD_CLOEXEC); + if (*sync_fd < 0) { + ERROR("Failed to create eventfd: %s", strerror(errno)); + ret = -1; + goto out; + } + if (ready_copy_io_data(*sync_fd, false, request->stdin, request->stdout, request->stderr, + stdinfd, stdout_handler, NULL, (const char **)fifos, thread_id)) { + ret = -1; + goto out; + } + } +out: + return ret; +} + +static void container_exec_cb_end(container_exec_response *response, uint32_t cc, pid_t pid, int exit_code, int sync_fd, + pthread_t thread_id) +{ + if (response != NULL) { + response->cc = cc; + response->pid = pid; + response->exit_code = (uint32_t)exit_code; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + } + if (sync_fd >= 0 && cc != LCRD_SUCCESS) { + if (eventfd_write(sync_fd, 1) < 0) { + ERROR("Failed to write eventfd: %s", strerror(errno)); + } + } + if (thread_id > 0) { + if (pthread_join(thread_id, NULL) < 0) { + ERROR("Failed to join thread: %u", (unsigned int)thread_id); + } + } + if (sync_fd >= 0) { + close(sync_fd); + } +} + +static int container_exec_cb(const container_exec_request *request, container_exec_response **response, + int stdinfd, struct io_write_wrapper *stdout_handler) +{ + int exit_code = 0; + int sync_fd = -1; + pid_t pid = -1; + uint32_t cc = LCRD_SUCCESS; + char *id = NULL; + char *fifos[3] = { NULL, NULL, NULL }; + char *fifopath = NULL; + pthread_t thread_id = 0; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + if (container_exec_cb_check(request, response, &cc, &cont) < 0) { + goto pack_response; + } + id = cont->common_config->id; + set_log_prefix(id); + + EVENT("Event: {Object: %s, Type: execing}", id); + + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("You cannot exec container %s in garbage collector progress.", id); + ERROR("You cannot exec container %s in garbage collector progress.", id); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (exec_prepare_console(cont, request, stdinfd, stdout_handler, fifos, &fifopath, &sync_fd, &thread_id)) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (exec_container(cont, cont->runtime, (char * const *)fifos, request->argv_len, + (const char **)request->argv, request->env_len, + (const char **)request->env, request->timeout, &pid, &exit_code)) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + EVENT("Event: {Object: %s, Type: execed}", id); + +pack_response: + container_exec_cb_end(*response, cc, pid, exit_code, sync_fd, thread_id); + delete_daemon_fifos(fifopath, (const char **)fifos); + free(fifos[0]); + free(fifos[1]); + free(fifos[2]); + free(fifopath); + container_unref(cont); + + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int container_attach_cb_check(const container_attach_request *request, container_attach_response **response, + uint32_t *cc, container_t **cont) +{ + char *name = NULL; + + *response = util_common_calloc_s(sizeof(container_attach_response)); + if (*response == NULL) { + ERROR("Out of memory"); + *cc = LCRD_ERR_MEMOUT; + return -1; + } + + name = request->container_id; + + if (name == NULL) { + DEBUG("Receive NULL Request id"); + *cc = LCRD_ERR_INPUT; + return -1; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + *cc = LCRD_ERR_EXEC; + return -1; + } + + *cont = containers_store_get(name); + if (*cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + *cc = LCRD_ERR_EXEC; + return -1; + } + return 0; +} + +static int attach_check_container_state(const container_t *cont) +{ + int ret = 0; + const char *id = cont->common_config->id; + + if (!is_running(cont->state)) { + ERROR("Container is not running"); + lcrd_set_error_message("Container is is not running."); + ret = -1; + goto out; + } + + if (is_paused(cont->state)) { + ERROR("Container %s is paused, unpause the container before attach.", id); + lcrd_set_error_message("Container %s is paused, unpause the container before attach.", id); + ret = -1; + goto out; + } + + if (is_restarting(cont->state)) { + ERROR("Container %s is restarting, wait until the container is running.", id); + lcrd_set_error_message("Container %s is restarting, wait until the container is running.", id); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int attach_prepare_console(const container_t *cont, const container_attach_request *request, int stdinfd, + struct io_write_wrapper *stdout_handler, struct io_write_wrapper *stderr_handler, + char **fifos, char **fifopath, pthread_t *tid) +{ + int ret = 0; + const char *id = cont->common_config->id; + + if (request->attach_stdin || request->attach_stdout || request->attach_stderr) { + if (create_daemon_fifos(id, cont->runtime, request->attach_stdin, request->attach_stdout, + request->attach_stderr, "attach", fifos, fifopath)) { + ret = -1; + goto out; + } + + if (ready_copy_io_data(-1, true, request->stdin, request->stdout, request->stderr, + stdinfd, stdout_handler, stderr_handler, (const char **)fifos, tid)) { + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static void close_io_writer(const struct io_write_wrapper *stdout_handler, + const struct io_write_wrapper *stderr_handler) +{ + if (stdout_handler != NULL && stdout_handler->close_func != NULL) { + (void)stdout_handler->close_func(stdout_handler->context, NULL); + } + if (stderr_handler != NULL && stderr_handler->close_func != NULL) { + (void)stderr_handler->close_func(stderr_handler->context, NULL); + } +} + +static int container_attach_cb(const container_attach_request *request, container_attach_response **response, + int stdinfd, struct io_write_wrapper *stdout_handler, + struct io_write_wrapper *stderr_handler) +{ + char *id = NULL; + uint32_t cc = LCRD_SUCCESS; + char *fifos[3] = { NULL, NULL, NULL }; + char *fifopath = NULL; + pthread_t tid = 0; + container_t *cont = NULL; + struct engine_operation *engine_ops = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + if (container_attach_cb_check(request, response, &cc, &cont) < 0) { + close_io_writer(stdout_handler, stderr_handler); + goto pack_response; + } + id = cont->common_config->id; + set_log_prefix(id); + + if (attach_check_container_state(cont)) { + close_io_writer(stdout_handler, stderr_handler); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (attach_prepare_console(cont, request, stdinfd, stdout_handler, stderr_handler, fifos, &fifopath, &tid) != 0) { + cc = LCRD_ERR_EXEC; + close_io_writer(stdout_handler, stderr_handler); + goto pack_response; + } + + engine_ops = engines_get_handler(cont->runtime); + if (engine_ops == NULL || engine_ops->engine_console_op == NULL) { + DEBUG("Failed to get engine attach operations"); + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + if (!engine_ops->engine_console_op(id, cont->root_path, fifos[0], fifos[1], fifos[2])) { + ERROR("attach failed"); + cc = LCRD_ERR_EXEC; + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Attach container error;%s", (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? + tmpmsg : DEF_ERR_RUNTIME_STR); + engine_ops->engine_clear_errmsg_op(); + goto pack_response; + } + +pack_response: + if (*response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + } + + delete_daemon_fifos(fifopath, (const char **)fifos); + free(fifos[0]); + free(fifos[1]); + free(fifos[2]); + free(fifopath); + container_unref(cont); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +static int copy_from_container_cb_check(const struct lcrd_copy_from_container_request *request, + struct lcrd_copy_from_container_response **response, + container_t **cont) +{ + int ret = -1; + char *name = NULL; + + *response = util_common_calloc_s(sizeof(struct lcrd_copy_from_container_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + + name = request->id; + if (name == NULL) { + ERROR("receive NULL Request id"); + goto out; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + goto out; + } + + if (request->srcpath == NULL || request->srcpath[0] == '\0') { + ERROR("bad parameter: path cannot be empty"); + lcrd_set_error_message("bad parameter: path cannot be empty"); + goto out; + } + + *cont = containers_store_get(name); + if (*cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + goto out; + } + + ret = 0; +out: + return ret; +} + +static int archive_and_send_copy_data(const stream_func_wrapper *stream, + struct lcrd_copy_from_container_response *response, + const char *resolvedpath, const char *abspath) +{ + int ret = -1; + int nret; + size_t buf_len = ARCHIVE_BLOCK_SIZE; + ssize_t read_len; + char *srcdir = NULL; + char *srcbase = NULL; + char *absbase = NULL; + char *err = NULL; + char *buf = NULL; + char cleaned[PATH_MAX + 2] = { 0 }; + struct io_read_wrapper reader = { 0 }; + + buf = util_common_calloc_s(buf_len); + if (buf == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (cleanpath(resolvedpath, cleaned, sizeof(cleaned)) == NULL) { + ERROR("Can not clean path: %s", resolvedpath); + goto cleanup; + } + + nret = split_dir_and_base_name(cleaned, &srcdir, &srcbase); + if (nret != 0) { + ERROR("split %s failed", cleaned); + goto cleanup; + } + + nret = split_dir_and_base_name(abspath, NULL, &absbase); + if (nret != 0) { + ERROR("split %s failed", abspath); + goto cleanup; + } + nret = archive_path(srcdir, srcbase, absbase, false, &reader); + if (nret != 0) { + ERROR("Archive %s failed", resolvedpath); + goto cleanup; + } + + read_len = reader.read(reader.context, buf, buf_len); + while (read_len > 0) { + bool writed = true; + response->data = buf; + response->data_len = (size_t)read_len; + writed = stream->write_func(stream->writer, response); + response->data = NULL; + response->data_len = 0; + if (!writed) { + DEBUG("Write to client failed, client may be exited"); + break; + } + read_len = reader.read(reader.context, buf, buf_len); + } + + ret = 0; +cleanup: + free(buf); + free(srcdir); + free(srcbase); + free(absbase); + if (reader.close != NULL) { + int cret = reader.close(reader.context, &err); + if (err != NULL) { + lcrd_set_error_message("%s", err); + } + ret = (cret != 0) ? cret : ret; + } + free(err); + return ret; +} + +static container_path_stat *do_container_stat_path(const char *rootpath, const char *resolvedpath, const char *abspath) +{ + int nret; + char *hostpath = NULL; + char *target = NULL; + timestamp *mtime = NULL; + struct stat st; + container_path_stat *stat = NULL; + + nret = lstat(resolvedpath, &st); + if (nret < 0) { + ERROR("lstat %s: %s", resolvedpath, strerror(errno)); + lcrd_set_error_message("lstat %s: %s", resolvedpath, strerror(errno)); + goto cleanup; + } + + if (S_ISLNK(st.st_mode)) { + char *p = NULL; + hostpath = get_resource_path(rootpath, abspath); + if (hostpath == NULL) { + ERROR("Failed to get resource path"); + goto cleanup; + } + p = strstr(hostpath, rootpath); + if (p == NULL) { + ERROR("rootpath %s should be in scope of hostpath %s", rootpath, hostpath); + goto cleanup; + } + target = util_path_join("/", p + strlen(rootpath)); + if (target == NULL) { + ERROR("Can not join path"); + goto cleanup; + } + } + + mtime = util_common_calloc_s(sizeof(timestamp)); + if (mtime == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + + stat = util_common_calloc_s(sizeof(container_path_stat)); + if (stat == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + nret = split_dir_and_base_name(abspath, NULL, &stat->name); + if (nret != 0) { + ERROR("split %s failed", abspath); + goto cleanup; + } + stat->size = (int64_t)st.st_size; + stat->mode = (uint32_t)st.st_mode; + stat->mtime = mtime; + mtime = NULL; + stat->mtime->seconds = (int64_t)st.st_mtim.tv_sec; + stat->mtime->nanos = (int32_t)st.st_mtim.tv_nsec; + stat->link_target = target; + target = NULL; + +cleanup: + free_timestamp(mtime); + free(target); + free(hostpath); + return stat; +} + +static int copy_from_container_send_path_stat(const stream_func_wrapper *stream, + const container_path_stat *stat) +{ + int ret = -1; + char *json = NULL; + char *err = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY, 0 }; + + json = container_path_stat_generate_json(stat, &ctx, &err); + if (json == NULL) { + ERROR("Can not generate json: %s", err); + goto cleanup; + } + + if (!stream->add_initial_metadata(stream->context, "isulad-container-path-stat", json)) { + goto cleanup; + } + // send metadata, client should always ignore the first read + if (!stream->write_func(stream->writer, NULL)) { + goto cleanup; + } + + ret = 0; +cleanup: + free(json); + free(err); + return ret; +} + +static container_path_stat *resolve_and_stat_path(const char *rootpath, const char *srcpath, char **resolvedpath, + char **abspath) +{ + int nret; + char *resolved = NULL; + char *abs = NULL; + container_path_stat *stat = NULL; + + nret = resolve_path(rootpath, srcpath, &resolved, &abs); + if (nret < 0) { + ERROR("Can not resolve path: %s", srcpath); + return NULL; + } + + stat = do_container_stat_path(rootpath, resolved, abs); + if (resolvedpath != NULL) { + *resolvedpath = resolved; + resolved = NULL; + } + if (abspath != NULL) { + *abspath = abs; + abs = NULL; + } + free(resolved); + free(abs); + return stat; +} + +static int copy_from_container_cb(const struct lcrd_copy_from_container_request *request, + const stream_func_wrapper *stream, char **err) +{ + int ret = -1; + int nret; + char *resolvedpath = NULL; + char *abspath = NULL; + container_path_stat *stat = NULL; + container_t *cont = NULL; + struct lcrd_copy_from_container_response *response = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || stream == NULL || err == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + if (copy_from_container_cb_check(request, &response, &cont) < 0) { + goto pack_response; + } + + container_lock(cont); + + if (is_removal_in_progress(cont->state) || is_dead(cont->state)) { + ERROR("can't copy file from a container which is dead or marked for removal"); + lcrd_set_error_message("can't copy file from a container which is dead or marked for removal"); + goto unlock_container; + } + + nret = im_mount_container_rootfs(cont->common_config->image_type, cont->common_config->image, + cont->common_config->id); + if (nret != 0) { + goto unlock_container; + } + + stat = resolve_and_stat_path(cont->common_config->base_fs, request->srcpath, &resolvedpath, &abspath); + if (stat == NULL) { + goto cleanup_rootfs; + } + DEBUG("Got resolved path: %s, abspath: %s", resolvedpath, abspath); + + nret = copy_from_container_send_path_stat(stream, stat); + if (nret < 0) { + ERROR("Can not send metadata to client"); + goto cleanup_rootfs; + } + + nret = archive_and_send_copy_data(stream, response, resolvedpath, abspath); + if (nret < 0) { + ERROR("Failed to send archive data"); + goto cleanup_rootfs; + } + + ret = 0; +cleanup_rootfs: + if (im_umount_container_rootfs(cont->common_config->image_type, cont->common_config->image, + cont->common_config->id) != 0) { + WARN("Can not umount rootfs of container: %s", cont->common_config->id); + } +unlock_container: + container_unlock(cont); + container_unref(cont); +pack_response: + if (g_lcrd_errmsg != NULL) { + *err = util_strdup_s(g_lcrd_errmsg); + } + lcrd_copy_from_container_response_free(response); + free_container_path_stat(stat); + free(resolvedpath); + free(abspath); + DAEMON_CLEAR_ERRMSG(); + return ret; +} + +static int copy_to_container_cb_check(const container_copy_to_request *request, + container_t **cont) +{ + int ret = -1; + char *name = NULL; + + name = request->id; + if (name == NULL) { + ERROR("receive NULL Request id"); + goto out; + } + + if (!util_valid_container_id_or_name(name)) { + ERROR("Invalid container name %s", name); + lcrd_set_error_message("Invalid container name %s", name); + goto out; + } + + if (request->src_path == NULL || request->src_path[0] == '\0') { + ERROR("bad parameter: path cannot be empty"); + lcrd_set_error_message("bad parameter: path cannot be empty"); + goto out; + } + + *cont = containers_store_get(name); + if (*cont == NULL) { + ERROR("No such container:%s", name); + lcrd_set_error_message("No such container:%s", name); + goto out; + } + + ret = 0; +out: + return ret; +} + +static ssize_t extract_stream_to_io_read(void *content, void *buf, size_t buf_len) +{ + stream_func_wrapper *stream = (stream_func_wrapper *)content; + struct lcrd_copy_to_container_data copy = { 0 }; + + if (!stream->read_func(stream->reader, ©)) { + DEBUG("Client may exited"); + return -1; + } + if (memcpy_s(buf, buf_len, copy.data, copy.data_len) != EOK) { + free(copy.data); + return -1; + } + free(copy.data); + return (ssize_t)(copy.data_len); +} + +int read_and_extract_archive(stream_func_wrapper *stream, const char *resolved_path, const char *transform) +{ + int ret = -1; + char *err = NULL; + struct io_read_wrapper content = { 0 }; + + content.context = stream; + content.read = extract_stream_to_io_read; + ret = archive_untar(&content, false, resolved_path, transform, &err); + if (ret != 0) { + ERROR("Can not untar to container: %s", (err != NULL) ? err : "unknown"); + lcrd_set_error_message("Can not untar to container: %s", (err != NULL) ? err : "unknown"); + } + free(err); + return ret; +} + +static char *copy_to_container_get_dstdir(const container_t *cont, const container_copy_to_request *request, + char **transform) +{ + char *dstdir = NULL; + char *error = NULL; + container_path_stat *dststat = NULL; + struct archive_copy_info srcinfo = { 0 }; + struct archive_copy_info *dstinfo = NULL; + + if (cont == NULL) { + return NULL; + } + + dstinfo = util_common_calloc_s(sizeof(struct archive_copy_info)); + if (dstinfo == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + dstinfo->path = util_strdup_s(request->dst_path); + // stat once + dststat = resolve_and_stat_path(cont->common_config->base_fs, request->dst_path, NULL, NULL); + if (dststat != NULL) { + if (S_ISLNK(dststat->mode)) { + free(dstinfo->path); + dstinfo->path = util_strdup_s(dststat->link_target); + free_container_path_stat(dststat); + // stat twice + dststat = resolve_and_stat_path(cont->common_config->base_fs, dstinfo->path, NULL, NULL); + } + if (dststat != NULL) { + dstinfo->exists = true; + dstinfo->isdir = S_ISDIR(dststat->mode); + } + } + // ignore any error + DAEMON_CLEAR_ERRMSG(); + + srcinfo.exists = true; + srcinfo.isdir = request->src_isdir; + srcinfo.path = request->src_path; + srcinfo.rebase_name = request->src_rebase_name; + + dstdir = prepare_archive_copy(&srcinfo, dstinfo, transform, &error); + if (dstdir == NULL) { + if (error == NULL) { + ERROR("Can not prepare archive copy"); + } else { + ERROR("%s", error); + lcrd_set_error_message("%s", error); + } + goto cleanup; + } +cleanup: + free(error); + free_archive_copy_info(dstinfo); + free_container_path_stat(dststat); + return dstdir; +} + +static int copy_to_container_resolve_path(const container_t *cont, const char *dstdir, + char **resolvedpath, char **abspath) +{ + int ret = -1; + char *joined = NULL; + char cleaned[PATH_MAX] = { 0 }; + + if (cont == NULL) { + return -1; + } + + joined = util_path_join("/", dstdir); + if (joined == NULL) { + ERROR("Can not join path"); + return -1; + } + if (cleanpath(joined, cleaned, sizeof(cleaned)) == NULL) { + ERROR("Can not clean path: %s", dstdir); + goto cleanup; + } + *abspath = preserve_trailing_dot_or_separator(cleaned, dstdir); + if (*abspath == NULL) { + ERROR("Can not preserve path"); + goto cleanup; + } + + *resolvedpath = get_resource_path(cont->common_config->base_fs, *abspath); + if (*resolvedpath == NULL) { + ERROR("Can not get resource path"); + goto cleanup; + } + ret = 0; +cleanup: + free(joined); + return ret; +} + +static int copy_to_container_check_path_valid(const container_t *cont, const char *resolvedpath, const char *abspath) +{ + int ret = -1; + int nret; + struct stat st; + + if (cont == NULL) { + return -1; + } + + if (cont->hostconfig->readonly_rootfs) { + ERROR("container rootfs is marked read-only"); + lcrd_set_error_message("container rootfs is marked read-only"); + goto cleanup; + } + + nret = lstat(resolvedpath, &st); + if (nret < 0) { + ERROR("lstat %s: %s", resolvedpath, strerror(errno)); + lcrd_set_error_message("lstat %s: %s", resolvedpath, strerror(errno)); + goto cleanup; + } + + if (!S_ISDIR(st.st_mode)) { + ERROR("extraction point is not a directory"); + lcrd_set_error_message("extraction point is not a directory"); + goto cleanup; + } + ret = 0; +cleanup: + return ret; +} + +static int copy_to_container_cb(const container_copy_to_request *request, + stream_func_wrapper *stream, char **err) +{ + int ret = -1; + int nret; + char *resolvedpath = NULL; + char *abspath = NULL; + char *dstdir = NULL; + char *transform = NULL; + container_t *cont = NULL; + + DAEMON_CLEAR_ERRMSG(); + if (request == NULL || stream == NULL || err == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + if (copy_to_container_cb_check(request, &cont) < 0) { + goto pack_response; + } + + container_lock(cont); + + if (is_removal_in_progress(cont->state) || is_dead(cont->state)) { + ERROR("can't copy to a container which is dead or marked for removal"); + lcrd_set_error_message("can't copy to a container which is dead or marked for removal"); + goto unlock_container; + } + + nret = im_mount_container_rootfs(cont->common_config->image_type, cont->common_config->image, + cont->common_config->id); + if (nret != 0) { + goto unlock_container; + } + + dstdir = copy_to_container_get_dstdir(cont, request, &transform); + if (dstdir == NULL) { + goto cleanup_rootfs; + } + + nret = copy_to_container_resolve_path(cont, dstdir, &resolvedpath, &abspath); + if (nret < 0) { + goto cleanup_rootfs; + } + + nret = copy_to_container_check_path_valid(cont, resolvedpath, abspath); + if (nret < 0) { + goto cleanup_rootfs; + } + + nret = read_and_extract_archive(stream, resolvedpath, transform); + if (nret < 0) { + ERROR("Failed to send archive data"); + goto cleanup_rootfs; + } + + ret = 0; +cleanup_rootfs: + if (im_umount_container_rootfs(cont->common_config->image_type, cont->common_config->image, + cont->common_config->id) != 0) { + WARN("Can not umount rootfs of container: %s", cont->common_config->id); + } +unlock_container: + container_unlock(cont); + container_unref(cont); +pack_response: + if (g_lcrd_errmsg != NULL) { + *err = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + free(resolvedpath); + free(abspath); + free(dstdir); + free(transform); + return ret; +} + +static int container_logs_cb_check(const struct lcrd_logs_request *request, struct lcrd_logs_response *response) +{ + if (request == NULL || request->id == NULL) { + response->cc = LCRD_ERR_INPUT; + ERROR("Receive NULL request or id"); + return -1; + } + + if (!util_valid_container_id_or_name(request->id)) { + ERROR("Invalid container name %s", request->id); + response->cc = LCRD_ERR_INPUT; + if (asprintf(&(response->errmsg), "Invalid container name %s", request->id) < 0) { + response->errmsg = util_strdup_s("Out of memory"); + } + return -1; + } + + return 0; +} + +static int do_decode_write_log_entry(const char *json_str, const stream_func_wrapper *stream) +{ + bool write_ok = false; + int ret = -1; + parser_error jerr = NULL; + logger_json_file *logentry = NULL; + struct parser_context ctx = { OPT_GEN_SIMPLIFY | OPT_GEN_NO_VALIDATE_UTF8, stderr }; + + logentry = logger_json_file_parse_data(json_str, &ctx, &jerr); + if (logentry == NULL) { + ERROR("parse logentry: %s, failed: %s", json_str, jerr); + goto out; + } + + /* send to client */ + write_ok = stream->write_func(stream->writer, logentry); + if (!write_ok) { + ERROR("Send log to client failed"); + goto out; + } + + ret = 0; +out: + free_logger_json_file(logentry); + free(jerr); + return ret; +} + +/* + * return: + * < 0, mean read failed + * == 0, mean read zero line + * > 0, mean read many lines + * */ +static int64_t do_read_log_file(const char *path, int64_t require_line, long pos, const stream_func_wrapper *stream, + long *last_pos) +{ +#define MAX_JSON_DECODE_RETRY 20 + int retries = 0; + int decode_retries = 0; + int64_t read_lines = 0; + FILE *fp = NULL; + char buffer[MAXLINE + 1] = { 0 }; + + for (retries = 0; retries <= LOG_MAX_RETRIES; retries++) { + fp = util_fopen(path, "r"); + if (fp != NULL || errno != ENOENT) { + break; + } + /* fopen is too fast, need wait rename operator finish */ + usleep_nointerupt(1000); + } + if (fp == NULL) { + ERROR("open file: %s failed: %s", path, strerror(errno)); + return -1; + } + if (pos > 0 && fseek(fp, pos, SEEK_SET) != 0) { + ERROR("fseek to %ld failed: %s", pos, strerror(errno)); + read_lines = -1; + goto out; + } + *last_pos = pos; + + while (fgets(buffer, MAXLINE, fp) != NULL) { + (*last_pos) += (long)strlen(buffer); + + if (do_decode_write_log_entry(buffer, stream) != 0) { + /* read a incomplete json object, try agin */ + decode_retries++; + if (decode_retries < MAX_JSON_DECODE_RETRY) { + continue; + } + read_lines = -1; + goto out; + } + decode_retries = 0; + + read_lines++; + if (read_lines == require_line) { + break; + } + } + +out: + fclose(fp); + return read_lines; +} + +struct last_log_file_position { + /* read file position */ + long pos; + /* which log file */ + int file_index; +}; + +static int do_read_all_container_logs(int64_t require_line, const char *path, const stream_func_wrapper *stream, + struct last_log_file_position *position) +{ + int ret = -1; + int i = position->file_index; + int64_t read_lines = 0; + int64_t left_lines = require_line; + long pos = position->pos; + char log_path[PATH_MAX] = { 0 }; + + for (; i > 0; i--) { + if (sprintf_s(log_path, PATH_MAX, "%s.%d", path, i) < 0) { + ERROR("Sprintf failed"); + goto out; + } + read_lines = do_read_log_file(log_path, left_lines, pos, stream, &(position->pos)); + if (read_lines < 0) { + if (errno == ENOENT) { + continue; + } + goto out; + } + /* only last file need pos */ + pos = 0; + if (require_line < 0) { + continue; + } + left_lines -= read_lines; + if (left_lines <= 0) { + /* get enough lines */ + ret = 0; + goto out; + } + } + read_lines = do_read_log_file(path, left_lines, pos, stream, &(position->pos)); + ret = read_lines < 0 ? -1 : 0; +out: + position->file_index = i; + return ret; +} + +static int do_show_all_logs(const struct container_log_config *conf, const stream_func_wrapper *stream, + struct last_log_file_position *last_pos) +{ + int ret = 0; + int index = conf->rotate - 1; + char log_path[PATH_MAX] = { 0 }; + + while (index > 0) { + if (sprintf_s(log_path, PATH_MAX, "%s.%d", conf->path, index) < 0) { + ERROR("Sprintf failed"); + ret = -1; + goto out; + } + if (util_file_exists(log_path)) { + break; + } + index--; + } + last_pos->file_index = index; + last_pos->pos = 0; + ret = do_read_all_container_logs(-1, conf->path, stream, last_pos); +out: + return ret; +} + +static int do_tail_find(FILE *fp, int64_t require_line, int64_t *get_line, long *get_pos) +{ +#define SECTION_SIZE 4096 + char buffer[SECTION_SIZE] = { 0 }; + size_t read_size, i; + long len, pos, step_size; + int ret = -1; + + if (fseek(fp, 0L, SEEK_END) != 0) { + ERROR("Fseek failed: %s", strerror(errno)); + goto out; + } + len = ftell(fp); + if (len < 0) { + ERROR("Ftell failed: %s", strerror(errno)); + goto out; + } + if (len < SECTION_SIZE) { + pos = len; + step_size = len; + } else { + step_size = SECTION_SIZE; + pos = len - step_size; + } + while (true) { + if (fseek(fp, pos, SEEK_SET) != 0) { + ERROR("Fseek failed: %s", strerror(errno)); + goto out; + } + read_size = fread(buffer, sizeof(char), (size_t)step_size, fp); + for (i = read_size; i > 0; i--) { + if (buffer[i - 1] != '\n') { + continue; + } + (*get_line) += 1; + if ((*get_line) > require_line) { + (*get_pos) = pos + (long)i; + (*get_line) = require_line; + ret = 0; + goto out; + } + } + if (pos == 0) { + break; + } + if (pos < step_size) { + step_size = pos; + pos = 0; + } else { + pos -= step_size; + } + } + + ret = 0; +out: + return ret; +} + +static int util_find_tail_position(const char *file_name, int64_t require_line, int64_t *get_line, long *pos) +{ + FILE *fp = NULL; + int ret = -1; + + if (file_name == NULL) { + return 0; + } + if (get_line == NULL || pos == NULL) { + ERROR("Invalid Arguments"); + return -1; + } + + fp = util_fopen(file_name, "rb"); + if (fp == NULL) { + ERROR("open file: %s failed: %s", file_name, strerror(errno)); + return -1; + } + + ret = do_tail_find(fp, require_line, get_line, pos); + + fclose(fp); + return ret; +} + +static int do_tail_container_logs(int64_t require_line, const struct container_log_config *conf, + const stream_func_wrapper *stream, struct last_log_file_position *last_pos) +{ + int i, ret; + int64_t left = require_line; + int64_t get_line = 0; + long pos = 0; + char log_path[PATH_MAX] = { 0 }; + + if (require_line < 0) { + /* read all logs */ + return do_show_all_logs(conf, stream, last_pos); + } + if (require_line == 0) { + /* require empty logs */ + return 0; + } + ret = util_find_tail_position(conf->path, left, &get_line, &pos); + if (ret != 0) { + return -1; + } + if (pos != 0) { + /* first line in first log file */ + get_line = do_read_log_file(conf->path, require_line, pos, stream, &(last_pos->pos)); + last_pos->file_index = 0; + return get_line < 0 ? -1 : 0; + } + for (i = 1; i < conf->rotate; i++) { + if (left <= get_line) { + i--; + break; + } + left -= get_line; + get_line = 0; + if (sprintf_s(log_path, PATH_MAX, "%s.%d", conf->path, i) < 0) { + ERROR("Sprintf failed"); + goto out; + } + ret = util_find_tail_position(log_path, left, &get_line, &pos); + if (ret != 0) { + if (errno == ENOENT) { + i--; + break; + } + goto out; + } + if (pos != 0) { + break; + } + } + i = (i == conf->rotate ? i - 1 : i); + + last_pos->pos = pos; + last_pos->file_index = i; + ret = do_read_all_container_logs(require_line, conf->path, stream, last_pos); +out: + return ret; +} + +struct follow_args { + const char *path; + stream_func_wrapper *stream; + bool *finish; + long last_file_pos; + int last_file_index; +}; + +static int handle_rotate(int fd, int wd, const char *path) +{ + int watch_fd = -1; + int retries = 0; + + INFO("Do rotate..."); + if (inotify_rm_watch(fd, wd) < 0) { + WARN("Rm watch failed"); + } + + for (; retries < LOG_MAX_RETRIES; retries++) { + watch_fd = inotify_add_watch(fd, path, IN_MODIFY | IN_DELETE | IN_MOVED_FROM | IN_MOVE_SELF); + if (watch_fd >= 0) { + break; + } + usleep_nointerupt(1000); + } + if (watch_fd < 0) { + SYSERROR("Add watch %s failed", path); + } + return watch_fd; +} + +static int hanlde_events(int fd, const struct follow_args *farg) +{ + int write_cnt, rename_cnt; + int watch_fd = 0; + int ret = -1; + size_t i = 0; + ssize_t len = 0; + struct inotify_event *c_event = NULL; + char buf[MAXLINE] __attribute__((aligned(__alignof__(struct inotify_event)))) = { 0 }; + + struct last_log_file_position last_pos = { + .file_index = farg->last_file_index, + .pos = farg->last_file_pos, + }; + + watch_fd = inotify_add_watch(fd, farg->path, IN_MODIFY | IN_DELETE | IN_MOVED_FROM | IN_MOVE_SELF); + if (watch_fd < 0) { + SYSERROR("Add watch %s failed", farg->path); + goto out; + } + + for (;;) { + if (pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0) { + ERROR("set cancel state failed"); + } + len = util_read_nointr(fd, buf, sizeof(buf)); + if (len < 0) { + SYSERROR("Read inotify event failed"); + goto out; + } + if (pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL) != 0) { + ERROR("set cancel state failed"); + } + + write_cnt = 0; + rename_cnt = 0; + for (i = 0; i < (size_t)len; i += (sizeof(struct inotify_event) + c_event->len)) { + c_event = (struct inotify_event *)(&buf[i]); + if (c_event->mask & IN_MODIFY) { + write_cnt++; + } else if (c_event->mask & (IN_DELETE | IN_MOVED_FROM | IN_MOVE_SELF)) { + rename_cnt++; + } + } + if (rename_cnt == 0 && write_cnt == 0) { + continue; + } + + last_pos.file_index = rename_cnt; + if (do_read_all_container_logs(write_cnt, farg->path, farg->stream, &last_pos) != 0) { + ERROR("Read all new logs failed"); + goto out; + } + if (rename_cnt > 0) { + watch_fd = handle_rotate(fd, watch_fd, farg->path); + if (watch_fd < 0) { + goto out; + } + /* if terminal log file rotated and index of last_pos is not 0, + * this mean we reach end of console.log.1. We need change last_pos + * to begin of console.log. + * */ + if (last_pos.file_index > 0) { + last_pos.pos = 0; + last_pos.file_index = 0; + } + } + } + +out: + if (inotify_rm_watch(fd, watch_fd) < 0) { + SYSERROR("Rm watch failed"); + } + return ret; +} + +static void *follow_thread_func(void *arg) +{ + int inotify_fd = 0; + struct follow_args *farg = (struct follow_args *)arg; + + prctl(PR_SET_NAME, "logs-worker"); + + INFO("Get args, path: %s, last pos: %ld, last file: %d", farg->path, farg->last_file_pos, farg->last_file_index); + + inotify_fd = inotify_init(); + if (inotify_fd < 0) { + SYSERROR("Init inotify failed"); + goto set_flag; + } + + if (hanlde_events(inotify_fd, farg) != 0) { + ERROR("Handle inotify event failed"); + } + + close(inotify_fd); +set_flag: + *(farg->finish) = true; + return NULL; +} + +static int do_follow_log_file(const char *cid, stream_func_wrapper *stream, struct last_log_file_position *last_pos, + const char *path) +{ + int ret = 0; + bool finish = false; + bool *finish_pointer = &finish; + pthread_t thread = 0; + + struct follow_args arg = { + .path = path, + .last_file_pos = last_pos->pos, + .last_file_index = last_pos->file_index, + .stream = stream, + .finish = finish_pointer, + }; + container_t *cont = NULL; + + ret = pthread_create(&thread, NULL, follow_thread_func, &arg); + if (ret != 0) { + ERROR("Thread create failed"); + return -1; + } + + cont = containers_store_get(cid); + if (cont == NULL) { + ERROR("No such container:%s", cid); + ret = -1; + goto out; + } + + /* check whether need finish */ + while (true) { + if (finish) { + ret = -1; + break; + } + if (!is_running(cont->state)) { + break; + } + if (stream->is_cancelled(stream->context)) { + ret = -1; + break; + } + usleep_nointerupt(10000); + } + +out: + if (pthread_cancel(thread) != 0) { + ERROR("cancel log work thread failed"); + ret = -1; + } + if (pthread_join(thread, NULL) != 0) { + ERROR("Joint log work failed"); + ret = -1; + } + container_unref(cont); + return ret; +} + +static int check_log_config(const struct container_log_config *log_config) +{ + if (log_config == NULL) { + ERROR("Log config is NULL"); + return -1; + } + if (log_config->path == NULL) { + lcrd_set_error_message("Do not set log path"); + ERROR("Do not set log path"); + return -1; + } + if (strcmp(log_config->path, "none") == 0) { + ERROR("Disable console log"); + lcrd_set_error_message("disable console log"); + return -1; + } + return 0; +} + +static int container_get_container_log_config(const container_t *cont, struct container_log_config **log_config) +{ + *log_config = (struct container_log_config *)util_common_calloc_s(sizeof(struct container_log_config)); + if (*log_config == NULL) { + ERROR("Out of memory"); + return -1; + } + (*log_config)->path = util_strdup_s(cont->log_path); + (*log_config)->rotate = cont->log_rotate; + (*log_config)->size = cont->log_maxsize; + + return 0; +} + +static void pack_logs_response(struct lcrd_logs_response *response, uint32_t cc) +{ + if (response == NULL) { + return; + } + response->cc = cc; + if (g_lcrd_errmsg != NULL) { + response->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } +} + +static int container_logs_cb(const struct lcrd_logs_request *request, stream_func_wrapper *stream, + struct lcrd_logs_response **response) +{ + int nret = 0; + uint32_t cc = LCRD_SUCCESS; + char *id = NULL; + container_t *cont = NULL; + struct container_log_config *log_config = NULL; + struct last_log_file_position last_pos = {0}; + + *response = (struct lcrd_logs_response *)util_common_calloc_s(sizeof(struct lcrd_logs_response)); + if (*response == NULL) { + ERROR("Out of memory"); + return -1; + } + (*response)->cc = LCRD_SUCCESS; + + /* check request */ + if (container_logs_cb_check(request, *response) != 0) { + goto out; + } + + cont = containers_store_get(request->id); + if (cont == NULL) { + ERROR("No such container: %s", request->id); + cc = LCRD_ERR_EXEC; + lcrd_set_error_message("No such container: %s", request->id); + goto out; + } + id = cont->common_config->id; + set_log_prefix(id); + + /* check state of container */ + if (gc_is_gc_progress(id)) { + lcrd_set_error_message("can not get logs from container which is dead or marked for removal"); + cc = LCRD_ERR_EXEC; + ERROR("can not get logs from container which is dead or marked for removal"); + goto out; + } + if (container_get_container_log_config(cont, &log_config) != 0) { + cc = LCRD_ERR_EXEC; + goto out; + } + + EVENT("Event: {Object: %s, Content: path: %s, rotate: %d, size: %ld }", id, log_config->path, log_config->rotate, + log_config->size); + + nret = check_log_config(log_config); + if (nret != 0) { + cc = LCRD_ERR_EXEC; + goto out; + } + + /* tail of container log file */ + if (do_tail_container_logs(request->tail, log_config, stream, &last_pos) != 0) { + lcrd_set_error_message("do tail log file failed"); + cc = LCRD_ERR_EXEC; + goto out; + } + + if (!request->follow) { + goto out; + } + + if (!is_running(cont->state)) { + goto out; + } + + /* follow of container log file */ + if (do_follow_log_file(id, stream, &last_pos, log_config->path) != 0) { + lcrd_set_error_message("do follow log file failed"); + cc = LCRD_ERR_EXEC; + goto out; + } + +out: + pack_logs_response(*response, cc); + + container_unref(cont); + container_log_config_free(log_config); + free_log_prefix(); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +void container_stream_callback_init(service_container_callback_t *cb) +{ + cb->attach = container_attach_cb; + cb->exec = container_exec_cb; + cb->copy_from_container = copy_from_container_cb; + cb->copy_to_container = copy_to_container_cb; + cb->logs = container_logs_cb; +} + diff --git a/src/services/execution/execute/execution_stream.h b/src/services/execution/execute/execution_stream.h new file mode 100644 index 0000000..000ac5c --- /dev/null +++ b/src/services/execution/execute/execution_stream.h @@ -0,0 +1,42 @@ +#ifndef __EXECUTION_STREAM_H_ +#define __EXECUTION_STREAM_H_ + +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + *********************************************************************************/ + +#include "callback.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void container_stream_callback_init(service_container_callback_t *cb); + +int create_daemon_fifos(const char *id, const char *runtime, bool attach_stdin, bool attach_stdout, + bool attach_stderr, const char *operation, char *fifos[], char **fifopath); + +void delete_daemon_fifos(const char *fifopath, const char *fifos[]); + +int ready_copy_io_data(int sync_fd, bool detach, const char *fifoin, const char *fifoout, const char *fifoerr, + int stdin_fd, struct io_write_wrapper *stdout_handler, struct io_write_wrapper *stderr_handler, + const char *fifos[], pthread_t *tid); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/src/services/execution/execute/list.c b/src/services/execution/execute/list.c new file mode 100644 index 0000000..0f2b72f --- /dev/null +++ b/src/services/execution/execute/list.c @@ -0,0 +1,711 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + ********************************************************************************/ + +#include +#include + +#include "log.h" +#include "containers_store.h" +#include "timestamp.h" +#include "list.h" +#include "filters.h" +#include "utils.h" +#include "error.h" + +struct list_context { + struct filters_args *ps_filters; + container_list_request *list_config; +}; + +static int dup_container_list_request(const container_list_request *src, container_list_request **dest) +{ + int ret = -1; + char *json = NULL; + parser_error err = NULL; + + if (src == NULL) { + *dest = NULL; + return 0; + } + + json = container_list_request_generate_json(src, NULL, &err); + if (json == NULL) { + ERROR("Failed to generate json: %s", err); + goto out; + } + *dest = container_list_request_parse_data(json, NULL, &err); + if (*dest == NULL) { + ERROR("Failed to parse json: %s", err); + goto out; + } + ret = 0; + +out: + free(err); + free(json); + return ret; +} + +static void free_list_context(struct list_context *ctx) +{ + if (ctx == NULL) { + return; + } + filters_args_free(ctx->ps_filters); + ctx->ps_filters = NULL; + free_container_list_request(ctx->list_config); + ctx->list_config = NULL; + free(ctx); +} + +static struct list_context *list_context_new(const container_list_request *request) +{ + struct list_context *ctx = NULL; + + ctx = util_common_calloc_s(sizeof(struct list_context)); + if (ctx == NULL) { + ERROR("Out of memory"); + return NULL; + } + ctx->ps_filters = filters_args_new(); + if (ctx->ps_filters == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + if (dup_container_list_request(request, &ctx->list_config) != 0) { + ERROR("Failed to dup list request"); + goto cleanup; + } + return ctx; +cleanup: + free_list_context(ctx); + return NULL; +} + +static const char *accepted_ps_filter_tags[] = { + "id", + "label", + "name", + "status", + NULL +}; + +static int filter_by_name(const struct list_context *ctx, const map_t *map_id_name, const map_t *matches, bool idsearch) +{ + int ret = 0; + bool default_value = true; + + if (ctx == NULL || map_id_name == NULL) { + return -1; + } + + map_itor *itor = map_itor_new(map_id_name); + if (itor == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + for (; map_itor_valid(itor); map_itor_next(itor)) { + void *id = map_itor_key(itor); + const char *name = map_itor_value(itor); + if (idsearch && map_search(matches, id) == NULL) { + continue; + } + if (filters_args_match(ctx->ps_filters, "name", name)) { + if (!map_replace(matches, id, &default_value)) { + ERROR("Failed to insert"); + map_itor_free(itor); + ret = -1; + goto out; + } + } + } + map_itor_free(itor); + +out: + return ret; +} + + +static int append_ids(const map_t *matches, char ***filtered_ids) +{ + map_itor *itor = map_itor_new(matches); + if (itor == NULL) { + ERROR("Out of memory"); + return -1; + } + + for (; map_itor_valid(itor); map_itor_next(itor)) { + if (util_array_append(filtered_ids, map_itor_key(itor)) != 0) { + ERROR("Append failed"); + util_free_array(*filtered_ids); + *filtered_ids = NULL; + map_itor_free(itor); + return -1; + } + } + map_itor_free(itor); + return 0; +} + +static int insert_matched_id(char **ids, map_t *matches, void *value, size_t ids_len) +{ + size_t i; + + for (i = 0; i < ids_len; i++) { + container_t *cont = containers_store_get_by_prefix(ids[i]); + if (cont != NULL) { + bool inserted; + inserted = map_insert(matches, cont->common_config->id, value); + container_unref(cont); + if (!inserted) { + ERROR("Insert map failed: %s", ids[i]); + return -1; + } + } + } + return 0; +} + +static inline void set_idsearch(size_t ids_len, bool *value) +{ + if (ids_len > 0) { + *value = true; + } +} + +static char **filter_by_name_id_matches(const struct list_context *ctx, const map_t *map_id_name) +{ + int ret = 0; + size_t names_len, ids_len; + bool idsearch = false; + bool default_value = true; + char **names = NULL; + char **ids = NULL; + char **filtered_ids = NULL; + map_t *matches = NULL; + + names = filters_args_get(ctx->ps_filters, "name"); + names_len = util_array_len(names); + + ids = filters_args_get(ctx->ps_filters, "id"); + ids_len = util_array_len(ids); + if (names_len == 0 && ids_len == 0) { + if (append_ids(map_id_name, &filtered_ids) != 0) { + goto cleanup; + } + return filtered_ids; + } + + set_idsearch(ids_len, &idsearch); + + matches = map_new(MAP_STR_BOOL, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC); + if (matches == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + + + if (insert_matched_id(ids, matches, &default_value, ids_len) != 0) { + goto cleanup; + } + + if (names_len > 0) { + ret = filter_by_name(ctx, map_id_name, matches, idsearch); + if (ret != 0) { + goto cleanup; + } + } + + if (map_size(matches) > 0) { + if (append_ids(map_id_name, &filtered_ids) != 0) { + goto cleanup; + } + } + +cleanup: + util_free_array(ids); + util_free_array(names); + map_free(matches); + return filtered_ids; +} + + +int dup_json_map_string_string(const json_map_string_string *src, json_map_string_string *dest) +{ + int ret = 0; + size_t i; + + if (src->len == 0) { + return 0; + } + + if (src->len > SIZE_MAX / sizeof(char *)) { + ERROR("Container inspect container config is too much!"); + ret = -1; + goto out; + } + dest->keys = util_common_calloc_s(src->len * sizeof(char *)); + if (dest->keys == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + dest->values = util_common_calloc_s(src->len * sizeof(char *)); + if (dest->values == NULL) { + ERROR("Out of memory"); + free(dest->keys); + dest->keys = NULL; + ret = -1; + goto out; + } + for (i = 0; i < src->len; i++) { + dest->keys[i] = util_strdup_s(src->keys[i] ? src->keys[i] : ""); + dest->values[i] = util_strdup_s(src->values[i] ? src->values[i] : ""); + dest->len++; + } +out: + return ret; +} + +char *container_get_health_state(const container_config_v2_state *cont_state) +{ + if (cont_state == NULL || cont_state->health == NULL || cont_state->health->status == NULL) { + return NULL; + } + + if (strcmp(cont_state->health->status, HEALTH_STARTING) == 0) { + return util_strdup_s("health: starting"); + } + + return util_strdup_s(cont_state->health->status); +} + +static int replace_labels(container_container *lcrdinfo, json_map_string_string *labels, const map_t *map_labels) +{ + lcrdinfo->labels = util_common_calloc_s(sizeof(json_map_string_string)); + + if (lcrdinfo->labels == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (dup_json_map_string_string(labels, lcrdinfo->labels) != 0) { + ERROR("Failed to dup labels"); + return -1; + } + size_t i; + for (i = 0; i < labels->len; i++) { + if (!map_replace(map_labels, (void *)labels->keys[i], labels->values[i])) { + ERROR("Failed to insert labels to map"); + return -1; + } + } + return 0; +} + +static int replace_annotations(const container_config_v2_common_config *common_config, + container_container *lcrdinfo) +{ + if (common_config->config->annotations != NULL && + common_config->config->annotations->len != 0) { + lcrdinfo->annotations = util_common_calloc_s(sizeof(json_map_string_string)); + if (lcrdinfo->annotations == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (dup_json_map_string_string(common_config->config->annotations, + lcrdinfo->annotations) != 0) { + ERROR("Failed to dup annotations"); + return -1; + } + } + return 0; +} + +static void dup_id_name(const container_config_v2_common_config *common_config, container_container *lcrdinfo) +{ + if (common_config->id != NULL) { + lcrdinfo->id = util_strdup_s(common_config->id); + } + + if (common_config->name != NULL) { + lcrdinfo->name = util_strdup_s(common_config->name); + } +} + +static int convert_common_config_info(const map_t *map_labels, const container_config_v2_common_config *common_config, + container_container *lcrdinfo) +{ + int ret = 0; + bool args_err = false; + + if (map_labels == NULL || common_config == NULL || lcrdinfo == NULL) { + return -1; + } + + if (common_config->config == NULL) { + return 0; + } + args_err = (common_config->config->labels != NULL && common_config->config->labels->len != 0); + if (args_err) { + json_map_string_string *labels = common_config->config->labels; + + ret = replace_labels(lcrdinfo, labels, map_labels); + if (ret == -1) { + goto out; + } + } + + ret = replace_annotations(common_config, lcrdinfo); + if (ret == -1) { + goto out; + } + + dup_id_name(common_config, lcrdinfo); + args_err = (common_config->created != NULL && + to_unix_nanos_from_str(common_config->created, &lcrdinfo->created) != 0); + if (args_err) { + ret = -1; + goto out; + } + lcrdinfo->restartcount = (uint64_t)common_config->restart_count; +out: + return ret; +} + +static int container_info_match(const struct list_context *ctx, const map_t *map_labels, + const container_container *lcrdinfo, const container_config_v2_state *cont_state) +{ + int ret = 0; + Container_Status cs; + + if (ctx == NULL || map_labels == NULL || cont_state == NULL) { + return -1; + } + + if (!filters_args_match(ctx->ps_filters, "name", lcrdinfo->name)) { + ret = -1; + goto out; + } + + if (!filters_args_match(ctx->ps_filters, "id", lcrdinfo->id)) { + ret = -1; + goto out; + } + + cs = state_judge_status(cont_state); + if (cs == CONTAINER_STATUS_CREATED) { + if (!filters_args_match(ctx->ps_filters, "status", "created") && \ + !filters_args_match(ctx->ps_filters, "status", "inited")) { + ret = -1; + goto out; + } + } else if (!filters_args_match(ctx->ps_filters, "status", state_to_string(cs))) { + ret = -1; + goto out; + } + + // Do not include container if any of the labels don't match + if (!filters_args_match_kv_list(ctx->ps_filters, "label", map_labels)) { + ret = -1; + goto out; + } + +out: + return ret; +} + +static int get_cnt_state(const struct list_context *ctx, const container_config_v2_state *cont_state, + const char *name) +{ + if (cont_state == NULL) { + ERROR("Failed to read %s state", name); + return -1; + } + + if (!cont_state->running && !ctx->list_config->all) { + return -1; + } + return 0; +} + +static int fill_lcrdinfo(container_container *lcrdinfo, const container_config_v2_state *cont_state, + const map_t *map_labels, const container_t *cont) +{ + int ret = 0; + char *image = NULL; + char *timestr = NULL; + char *defvalue = "-"; + + if (cont->common_config != NULL) { + ret = convert_common_config_info(map_labels, cont->common_config, lcrdinfo); + if (ret != 0) { + goto out; + } + } + + lcrdinfo->pid = (int32_t)cont_state->pid; + + lcrdinfo->status = (int)state_judge_status(cont_state); + + lcrdinfo->command = container_get_command(cont); + image = container_get_image(cont); + lcrdinfo->image = image ? image : util_strdup_s("none"); + + lcrdinfo->exit_code = (uint32_t)(cont_state->exit_code); + timestr = cont_state->started_at ? cont_state->started_at : defvalue; + lcrdinfo->startat = util_strdup_s(timestr); + + timestr = cont_state->finished_at ? cont_state->finished_at : defvalue; + lcrdinfo->finishat = util_strdup_s(timestr); + + lcrdinfo->runtime = cont->runtime ? util_strdup_s(cont->runtime) : util_strdup_s("none"); + + lcrdinfo->health_state = container_get_health_state(cont_state); + +out: + return ret; +} + +static void free_lcrd_info(container_container **lcrdinfo, int ret) +{ + if (ret != 0) { + free_container_container(*lcrdinfo); + *lcrdinfo = NULL; + } + return; +} + +static void unref_cont(container_t *cont) +{ + if (cont != NULL) { + container_unref(cont); + } + return; +} + +static container_container *get_container_info(const char *name, const struct list_context *ctx) +{ + int ret = 0; + container_container *lcrdinfo = NULL; + container_t *cont = NULL; + container_config_v2_state *cont_state = NULL; + map_t *map_labels = NULL; + + cont = containers_store_get(name); + if (cont == NULL) { + ERROR("Container '%s' already removed", name); + return NULL; + } + cont_state = state_get_info(cont->state); + + if (get_cnt_state(ctx, cont_state, name) != 0) { + ret = -1; + goto cleanup; + } + + map_labels = map_new(MAP_STR_STR, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC); + if (map_labels == NULL) { + ERROR("Out of memory"); + ret = -1; + goto cleanup; + } + + lcrdinfo = util_common_calloc_s(sizeof(container_container)); + if (lcrdinfo == NULL) { + ERROR("Out of memory"); + ret = -1; + goto cleanup; + } + + ret = fill_lcrdinfo(lcrdinfo, cont_state, map_labels, cont); + if (ret != 0) { + goto cleanup; + } + + ret = container_info_match(ctx, map_labels, lcrdinfo, cont_state); + if (ret != 0) { + goto cleanup; + } + +cleanup: + unref_cont(cont); + map_free(map_labels); + free_container_config_v2_state(cont_state); + free_lcrd_info(&lcrdinfo, ret); + return lcrdinfo; +} + +static int do_add_filters(const char *filter_key, const json_map_string_bool *filter_value, struct list_context *ctx) +{ + int ret = 0; + size_t j; + bool bret = false; + + for (j = 0; j < filter_value->len; j++) { + if (strcmp(filter_key, "status") == 0) { + if (!is_valid_state_string(filter_value->keys[j])) { + ERROR("Unrecognised filter value for status: %s", filter_value->keys[j]); + lcrd_set_error_message("Unrecognised filter value for status: %s", + filter_value->keys[j]); + ret = -1; + goto out; + } + ctx->list_config->all = true; + } + bret = filters_args_add(ctx->ps_filters, filter_key, filter_value->keys[j]); + if (!bret) { + ERROR("Add filter args failed"); + ret = -1; + goto out; + } + } +out: + return ret; +} + +static struct list_context *fold_filter(const container_list_request *request) +{ + size_t i; + struct list_context *ctx = NULL; + + ctx = list_context_new(request); + if (ctx == NULL) { + ERROR("Out of memory"); + return NULL; + } + + if (request->filters == NULL) { + return ctx; + } + + for (i = 0; i < request->filters->len; i++) { + if (!filters_args_valid_key(accepted_ps_filter_tags, sizeof(accepted_ps_filter_tags) / sizeof(char *), + request->filters->keys[i])) { + ERROR("Invalid filter '%s'", request->filters->keys[i]); + lcrd_set_error_message("Invalid filter '%s'", request->filters->keys[i]); + goto error_out; + } + if (do_add_filters(request->filters->keys[i], request->filters->values[i], ctx) != 0) { + goto error_out; + } + } + + return ctx; +error_out: + free_list_context(ctx); + return NULL; +} + +static int pack_list_containers(char **idsarray, const struct list_context *ctx, container_list_response *response) +{ + int ret = 0; + int j = 0; + size_t container_nums = 0; + + container_nums = util_array_len(idsarray); + if (container_nums == 0) { + goto out; + } + + if (container_nums > (SIZE_MAX / sizeof(container_container *))) { + ERROR("Get too many containers:%d", container_nums); + ret = -1; + goto out; + } + + response->containers = util_common_calloc_s(container_nums * sizeof(container_container *)); + if (response->containers == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + j = 0; + while (idsarray != NULL && idsarray[j] != NULL) { + response->containers[response->containers_len] = get_container_info(idsarray[j], ctx); + if (response->containers[response->containers_len] == NULL) { + j++; + continue; + } + j++; + response->containers_len++; + } +out: + return ret; +} + +int container_list_cb(const container_list_request *request, container_list_response **response) +{ + char **idsarray = NULL; + map_t *map_id_name = NULL; + uint32_t cc = LCRD_SUCCESS; + struct list_context *ctx = NULL; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(container_list_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + ctx = fold_filter(request); + if (ctx == NULL) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + + map_id_name = name_index_get_all(); + if (map_id_name == NULL) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + if (map_size(map_id_name) == 0) { + goto pack_response; + } + // fastpath to only look at a subset of containers if specific name + // or ID matches were provided by the user--otherwise we potentially + // end up querying many more containers than intended + idsarray = filter_by_name_id_matches(ctx, map_id_name); + + if (pack_list_containers(idsarray, ctx, (*response)) != 0) { + cc = LCRD_ERR_EXEC; + goto pack_response; + } + +pack_response: + map_free(map_id_name); + if (*response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + } + util_free_array(idsarray); + free_list_context(ctx); + + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + diff --git a/src/services/execution/execute/list.h b/src/services/execution/execute/list.h new file mode 100644 index 0000000..40ef873 --- /dev/null +++ b/src/services/execution/execute/list.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + ******************************************************************************/ + +#ifndef __EXECUTION_CONTAINER_LIST_CB_H_ +#define __EXECUTION_CONTAINER_LIST_CB_H_ + +#include "callback.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int dup_json_map_string_string(const json_map_string_string *src, json_map_string_string *dest); + +int container_list_cb(const container_list_request *request, container_list_response **response); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/execution/execute/runtime_interface.c b/src/services/execution/execute/runtime_interface.c new file mode 100644 index 0000000..e090ae8 --- /dev/null +++ b/src/services/execution/execute/runtime_interface.c @@ -0,0 +1,248 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "engine.h" +#include "callback.h" +#include "runtime_interface.h" +#include "error.h" +#include "lcrd_config.h" + +int runtime_create(const char *name, const char *runtime, const char *rootfs, void *oci_config_data) +{ + int ret = 0; + char *runtime_root = NULL; + struct engine_operation *engine_ops = NULL; + + runtime_root = conf_get_routine_rootdir(runtime); + if (runtime_root == NULL) { + ERROR("Root path is NULL"); + ret = -1; + goto out; + } + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_create_op == NULL) { + ERROR("Failed to get engine create operations"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_create_op(name, runtime_root, rootfs, NULL, + (void *)oci_config_data)) { + ERROR("Failed to create container"); + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Create container error: %s", + (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg + : DEF_ERR_RUNTIME_STR); + engine_ops->engine_clear_errmsg_op(); + ret = -1; + goto out; + } + +out: + free(runtime_root); + return ret; +} + +int runtime_start(const char *name, const char *runtime, const char *rootpath, bool tty, bool interactive, + const char *engine_log_path, const char *loglevel, const char *console_fifos[], + const char *share_ns[], unsigned int start_timeout, const char *pidfile, const char *exit_fifo, + const oci_runtime_spec_process_user *puser) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + engine_start_request_t request = { 0 }; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_start_op == NULL) { + ERROR("Failed to get engine start operations"); + ret = -1; + goto out; + } + + request.name = name; + request.lcrpath = rootpath; + request.logpath = engine_log_path; + request.loglevel = loglevel; + request.daemonize = true; + request.tty = tty; + request.open_stdin = interactive; + request.pidfile = NULL; + request.console_fifos = console_fifos; + request.console_logpath = NULL; + request.share_ns = (const char **)share_ns; + request.start_timeout = start_timeout; + request.container_pidfile = pidfile; + request.exit_fifo = exit_fifo; + if (puser != NULL) { + request.uid = puser->uid; + request.gid = puser->gid; + request.additional_gids = puser->additional_gids; + request.additional_gids_len = puser->additional_gids_len; + } + if (!engine_ops->engine_start_op(&request)) { + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Start container error: %s", + (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg + : DEF_ERR_RUNTIME_STR); + ERROR("Start container error: %s", (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg + : DEF_ERR_RUNTIME_STR); + engine_ops->engine_clear_errmsg_op(); + ret = -1; + goto out; + } +out: + return ret; +} + +int runtime_restart(const char *name, const char *runtime, const char *rootpath) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_reset_op == NULL) { + ERROR("Get reset operation failed"); + ret = -2; + goto out; + } + + if (!engine_ops->engine_reset_op(name, rootpath)) { + ERROR("Reset operate failed"); + if (engine_ops->engine_get_errmsg_op != NULL) { + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Restart container error: %s", + (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg + : DEF_ERR_RUNTIME_STR); + if (engine_ops->engine_clear_errmsg_op != NULL) { + engine_ops->engine_clear_errmsg_op(); + } + } + ret = -1; + } +out: + return ret; +} + +int runtime_clean_resource(const char *name, const char *runtime, const char *rootpath, + const char *engine_log_path, const char *loglevel, pid_t pid) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_clean_op == NULL) { + ERROR("Failed to get engine clean operations"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_clean_op(name, rootpath, engine_log_path, loglevel, pid)) { + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_try_set_error_message("Clean resource container error;%s", + (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg + : DEF_ERR_RUNTIME_STR); + ret = -1; + goto out; + } + +out: + if (engine_ops != NULL) { + engine_ops->engine_clear_errmsg_op(); + } + return ret; +} + +int runtime_rm(const char *name, const char *runtime, const char *rootpath) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_delete_op == NULL) { + ERROR("Failed to get engine delete operations"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_delete_op(name, rootpath)) { + ERROR("Delete container %s failed", name); + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Runtime delete container error: %s", + (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? tmpmsg : DEF_ERR_RUNTIME_STR); + + ret = -1; + goto out; + } + +out: + if (engine_ops != NULL) { + engine_ops->engine_clear_errmsg_op(); + } + return ret; +} + +int runtime_get_console_config(const char *name, const char *runtime, const char *rootpath, + struct engine_console_config *config) +{ + int ret = 0; + struct engine_operation *engine_ops = NULL; + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || (engine_ops->engine_get_console_config_op) == NULL) { + ERROR("Failed to get engine get_console_config operation"); + ret = -1; + goto out; + } + + if (!engine_ops->engine_get_console_config_op(name, rootpath, config)) { + ERROR("Failed to get console config"); + const char *tmpmsg = NULL; + tmpmsg = engine_ops->engine_get_errmsg_op(); + lcrd_set_error_message("Get console config error;%s", (tmpmsg && strcmp(tmpmsg, DEF_SUCCESS_STR)) ? + tmpmsg : DEF_ERR_RUNTIME_STR); + ret = -1; + goto out; + } + + +out: + if (engine_ops != NULL) { + engine_ops->engine_clear_errmsg_op(); + } + return ret; +} + diff --git a/src/services/execution/execute/runtime_interface.h b/src/services/execution/execute/runtime_interface.h new file mode 100644 index 0000000..093ca68 --- /dev/null +++ b/src/services/execution/execute/runtime_interface.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + *******************************************************************************/ + +#ifndef __EXECUTION_RUNTIME_INTERFACE_H_ +#define __EXECUTION_RUNTIME_INTERFACE_H_ + +#include "engine.h" +#include "oci_runtime_spec.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int runtime_rm(const char *name, const char *runtime, const char *rootpath); +int runtime_clean_resource(const char *name, const char *runtime, const char *rootpath, + const char *engine_log_path, const char *loglevel, pid_t pid); +int runtime_restart(const char *name, const char *runtime, const char *rootpath); + +int runtime_start(const char *name, const char *runtime, const char *rootpath, bool tty, bool interactive, + const char *engine_log_path, const char *loglevel, const char *console_fifos[], + const char *share_ns[], unsigned int start_timeout, const char *pidfile, const char *exit_fifo, + const oci_runtime_spec_process_user *puser); +int runtime_create(const char *name, const char *runtime, const char *rootfs, void *oci_config_data); +int runtime_get_console_config(const char *name, const char *runtime, const char *rootpath, + struct engine_console_config *config); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/execution/log_gather.c b/src/services/execution/log_gather.c new file mode 100644 index 0000000..13faf39 --- /dev/null +++ b/src/services/execution/log_gather.c @@ -0,0 +1,406 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide log gather functions + ******************************************************************************/ +#define _GNU_SOURCE +#include "log_gather.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "lcrdtar.h" + +typedef int(*log_save_t)(const void *buf, size_t count); +static log_save_t g_save_log_op = NULL; + +static int g_fifo_fd = -1; +static char *g_fifo_path = NULL; +static int g_log_fd = -1; +static char *g_log_file = NULL; +static int64_t g_max_size = 4096; +static int g_max_file = 3; +static mode_t g_log_mode = S_IRUSR | S_IWUSR; + +static int log_file_open(); + +static int file_rotate_gz(const char *file_name, int i) +{ + char from_path[PATH_MAX] = { 0 }; + char to_path[PATH_MAX] = { 0 }; + + if (sprintf_s(from_path, PATH_MAX, "%s.%d.gz", file_name, (i - 1)) == -1) { + ERROR("sprint zip file name failed"); + return -1; + } + + if (sprintf_s(to_path, PATH_MAX, "%s.%d.gz", file_name, i) == -1) { + ERROR("sprint zip file name failed"); + return -1; + } + + if (rename(from_path, to_path) < 0 && errno != ENOENT) { + WARN("Rename file: %s error: %s", from_path, strerror(errno)); + return -1; + } + + return 0; +} + +static int file_rotate_me(const char *file_name) +{ + char tmp_path[PATH_MAX] = { 0 }; + + if (sprintf_s(tmp_path, PATH_MAX, "%s.1", file_name) == -1) { + ERROR("Out of memory"); + return -1; + } + + if (rename(file_name, tmp_path) < 0 && errno != ENOENT) { + WARN("Rename file: %s error: %s", file_name, strerror(errno)); + return -1; + } + + if (gzip(tmp_path, sizeof(tmp_path))) { + WARN("Gzip file failed"); + return -2; + } + + return 0; +} + +static int file_rotate(const char *file_name, int max_files) +{ + int i = 0; + + if (file_name == NULL || max_files < 2) { + return 0; + } + + for (i = max_files - 1; i > 1; i--) { + if (file_rotate_gz(file_name, i)) { + return -1; + } + } + + return file_rotate_me(file_name); +} + +/* get driver */ +static int get_driver(const char *driver) +{ + if (driver == NULL) { + return LOG_GATHER_DRIVER_NOSET; + } + if (strcasecmp(driver, "stdout") == 0) { + return LOG_GATHER_DRIVER_STDOUT; + } + if (strcasecmp(driver, "file") == 0) { + return LOG_GATHER_DRIVER_FILE; + } + + return -1; +} + +/* create fifo */ +static int create_fifo() +{ + int ret = -1; + + ret = mknod(g_fifo_path, S_IFIFO | S_IRUSR | S_IWUSR, (dev_t)0); + if (ret != 0 && errno != EEXIST) { + COMMAND_ERROR("mknod failed: %s", strerror(errno)); + } else { + ret = 0; + } + + return ret; +} + +/* open log */ +static int open_log(bool change_size) +{ + int fd = -1; + + fd = util_open(g_fifo_path, O_RDWR | O_CLOEXEC, 0); + if (fd == -1) { + COMMAND_ERROR("open fifo %s failed: %s", g_fifo_path, strerror(errno)); + return fd; + } + + if (change_size && fcntl(fd, F_SETPIPE_SZ, LOG_FIFO_SIZE) == -1) { + COMMAND_ERROR("set fifo buffer size failed: %s", strerror(errno)); + close(fd); + return -1; + } + + if (g_fifo_fd != -1 && g_fifo_fd != fd) { + close(g_fifo_fd); + } + + g_fifo_fd = fd; + return fd; +} + +/* write into file */ +static int write_into_file(const void *buf, size_t g_log_size) +{ + int ret = 0; + static int64_t write_size = 0; + + if (!util_file_exists(g_log_file)) { + COMMAND_ERROR("Log file: %s delete by someone.", g_log_file); + if (log_file_open()) { + COMMAND_ERROR("Reopen log file failed."); + return -1; + } + } + ret = (int)write(g_log_fd, buf, g_log_size); + if (ret <= 0) { + return ret; + } + + write_size += ret; + if (write_size <= g_max_size) { + return 0; + } + + /* Begin rotate log files */ + ret = file_rotate(g_log_file, g_max_file); + if (ret == -1) { + COMMAND_ERROR("Rotate failed"); + return ret; + } + + write_size = 0; + if (log_file_open()) { + COMMAND_ERROR("Rotate file: reopen log file failed"); + ret = -1; + } + + return ret; +} + +/* check log file */ +static int check_log_file() +{ + struct stat sbuf; + int ret = -1; + + if (stat(g_log_file, &sbuf)) { + return 0; + } + + if (sbuf.st_size > g_max_size) { + ret = file_rotate(g_log_file, g_max_file); + if (ret != 0) { + COMMAND_ERROR("Rotate log file %s failed.", g_log_file); + } else { + INFO("Log file large than %lu, rotate it.", g_max_size); + } + } else { + ret = 0; + } + + return ret; +} + +/* write into stdout */ +static int write_into_stdout(const void *buf, size_t g_log_size) +{ + int ret; + + ret = fprintf(stderr, "%s", (const char *)buf); + return ret; +} + +/* main loop */ +void main_loop() +{ + int ecount = 0; + char rev_buf[REV_BUF_SIZE + 1] = { 0 }; + + if (g_save_log_op == NULL) { + ERROR("Not supported g_save_log_op"); + return; + } + + for (;;) { + int len = (int)util_read_nointr(g_fifo_fd, rev_buf, REV_BUF_SIZE); + if (len < 0) { + if (ecount < 2) { + COMMAND_ERROR("%d: Read message failed: %s", ecount++, strerror(errno)); + } + continue; + } + ecount = 0; + + rev_buf[len] = '\0'; + if (g_save_log_op(rev_buf, (size_t)len) < 0) { + COMMAND_ERROR("write message failed: %s", strerror(errno)); + } + } +} + +/* log file open */ +static int log_file_open() +{ + int ret = 0; + int fd = -1; + + umask(0000); + + if (g_log_file == NULL) { + ret = -1; + goto out; + } + fd = util_open(g_log_file, O_CREAT | O_WRONLY | O_APPEND, g_log_mode); + if (fd == -1) { + COMMAND_ERROR("Open %s failed: %s", g_log_file, strerror(errno)); + ret = -1; + goto out; + } + + /* change log file mode to config, if log file exist and with different mode */ + if (fchmod(fd, g_log_mode) != 0) { + COMMAND_ERROR("Change mode of log file: %s failed: %s", g_log_file, strerror(errno)); + close(fd); + ret = -1; + goto out; + } + + if (g_log_fd != -1 && g_log_fd != fd) { + close(g_log_fd); + } + + g_log_fd = fd; + +out: + umask(0022); + return ret; +} + +/* init log */ +static int init_log(const struct log_gather_conf *lgconf) +{ + int driver = -1; + int ret = -1; + + driver = get_driver(lgconf->g_log_driver); + switch (driver) { + case LOG_GATHER_DRIVER_STDOUT: + g_save_log_op = write_into_stdout; + break; + case LOG_GATHER_DRIVER_FILE: + if (lgconf->log_path == NULL) { + COMMAND_ERROR("Driver is file, but file path is NULL"); + return ret; + } + g_log_mode = lgconf->log_file_mode; + g_max_size = lgconf->max_size; + g_max_file = lgconf->max_file; + g_log_file = util_strdup_s(lgconf->log_path); + if (check_log_file()) { + goto err_out; + } + if (util_build_dir(g_log_file)) { + COMMAND_ERROR("Build log file path failed."); + goto err_out; + } + if (log_file_open()) { + goto err_out; + } + g_save_log_op = write_into_file; + break; + case LOG_GATHER_DRIVER_NOSET: + g_save_log_op = write_into_stdout; + driver = LOG_GATHER_DRIVER_STDOUT; + COMMAND_ERROR("Unset log driver, use stderr to log."); + break; + default: + COMMAND_ERROR("Unsupported driver: %s", lgconf->g_log_driver); + return ret; + } + ret = 0; + +err_out: + if (ret != 0) { + free(g_log_file); + g_log_file = NULL; + } + + return ret; +} + +/* log gather */ +void *log_gather(void *arg) +{ + int ret = pthread_detach(pthread_self()); + struct log_gather_conf *lgconf = (struct log_gather_conf *)arg; + + if (ret != 0) { + CRIT("Set log monitor thread detach fail"); + goto err_out; + } + prctl(PR_SET_NAME, "Log_gather"); + INFO("Begin to gather logs..."); + + if (lgconf == NULL || lgconf->fifo_path == NULL) { + COMMAND_ERROR("Invalid arguments"); + goto err_out; + } + if (lgconf->g_log_driver == NULL) { + COMMAND_ERROR("Log driver is NULL"); + goto err_out; + } + + if (g_fifo_path != NULL) { + free(g_fifo_path); + } + + g_fifo_path = util_strdup_s(lgconf->fifo_path); + + ret = create_fifo(); + if (ret != 0) { + goto err_out; + } + ret = open_log(true); + if (ret < 0) { + goto err_out; + } + + if (init_log(arg)) { + goto err_out; + } + + *(lgconf->exitcode) = 0; + + main_loop(); + goto pexit; + +err_out: + if (lgconf != NULL) { + *(lgconf->exitcode) = 1; + } +pexit: + return NULL; +} + diff --git a/src/services/execution/log_gather.h b/src/services/execution/log_gather.h new file mode 100644 index 0000000..9ada9de --- /dev/null +++ b/src/services/execution/log_gather.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide log gather definition + ******************************************************************************/ +#ifndef __LCRD_LOG_GATHER_H_ +#define __LCRD_LOG_GATHER_H_ + +#include "utils.h" +#include "log.h" + +#define LOG_FIFO_SIZE (1024 * 1024) + +#define REV_BUF_SIZE 4096 + +struct log_gather_conf { + const char *fifo_path; + int *exitcode; + const char *g_log_driver; + const char *log_path; + unsigned int log_file_mode; + int64_t max_size; + int max_file; +}; + +enum log_gather_driver { + LOG_GATHER_DRIVER_STDOUT, + LOG_GATHER_DRIVER_FILE, + LOG_GATHER_DRIVER_NOSET +}; + +void *log_gather(void *arg); + +#endif diff --git a/src/services/execution/manager/CMakeLists.txt b/src/services/execution/manager/CMakeLists.txt new file mode 100644 index 0000000..dfa9557 --- /dev/null +++ b/src/services/execution/manager/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_manager_srcs) + +set(MANAGER_SRCS + ${local_manager_srcs} + PARENT_SCOPE + ) diff --git a/src/services/execution/manager/container_state.c b/src/services/execution/manager/container_state.c new file mode 100644 index 0000000..777272d --- /dev/null +++ b/src/services/execution/manager/container_state.c @@ -0,0 +1,694 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container state functions + ******************************************************************************/ +#include +#include + +#include "container_unix.h" +#include "container_state.h" +#include "log.h" +#include "utils.h" +#include "error.h" + +/* container state lock */ +void container_state_lock(container_state_t *state) +{ + if (pthread_mutex_lock(&state->mutex)) { + ERROR("Failed to lock state"); + } +} + +/* container state unlock */ +void container_state_unlock(container_state_t *state) +{ + if (pthread_mutex_unlock(&state->mutex)) { + ERROR("Failed to lock state"); + } +} + +/* container state new */ +container_state_t *container_state_new(void) +{ + int ret; + container_state_t *s = NULL; + + s = util_common_calloc_s(sizeof(container_state_t)); + if (s == NULL) { + ERROR("Out of memory"); + return NULL; + } + ret = pthread_mutex_init(&s->mutex, NULL); + if (ret != 0) { + ERROR("Failed to init mutex of state"); + goto error_out; + } + + s->state = util_common_calloc_s(sizeof(container_config_v2_state)); + if (s->state == NULL) { + ERROR("Out of memory"); + goto error_out; + } + + s->state->started_at = util_strdup_s(defaultContainerTime); + s->state->finished_at = util_strdup_s(defaultContainerTime); + + return s; +error_out: + container_state_free(s); + return NULL; +} + +/* container state free */ +void container_state_free(container_state_t *state) +{ + if (state == NULL) { + return; + } + + free_container_config_v2_state(state->state); + state->state = NULL; + + pthread_mutex_destroy(&state->mutex); + free(state); +} + +/* state set starting */ +void state_set_starting(container_state_t *s) +{ + if (s == NULL) { + return; + } + + container_state_lock(s); + + s->state->starting = true; + + container_state_unlock(s); +} + +/* state set dead */ +void state_set_dead(container_state_t *s) +{ + if (s == NULL) { + return; + } + + container_state_lock(s); + + s->state->dead = true; + + container_state_unlock(s); +} + +/* state reset starting */ +void state_reset_starting(container_state_t *s) +{ + if (s == NULL) { + return; + } + + container_state_lock(s); + + s->state->starting = false; + + container_state_unlock(s); +} + +/* state set running*/ +void state_set_running(container_state_t *s, const container_pid_t *pid_info, bool initial) +{ + container_config_v2_state *state = NULL; + char timebuffer[TIME_STR_SIZE] = { 0 }; + + if (s == NULL) { + return; + } + + container_state_lock(s); + + state = s->state; + + free(state->error); + state->error = NULL; + + state->running = true; + state->starting = false; + state->restarting = false; + if (initial) { + state->paused = false; + } + state->exit_code = 0; + + if (pid_info != NULL) { + state->pid = pid_info->pid; + state->start_time = pid_info->start_time; + state->p_pid = pid_info->ppid; + state->p_start_time = pid_info->pstart_time; + } else { + state->pid = 0; + state->start_time = 0; + state->p_pid = 0; + state->p_start_time = 0; + } + + (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); + free(state->started_at); + state->started_at = util_strdup_s(timebuffer); + + container_state_unlock(s); +} + +/* state reset stopped*/ +void state_set_stopped(container_state_t *s, int exit_code) +{ + container_config_v2_state *state = NULL; + char timebuffer[TIME_STR_SIZE] = { 0 }; + + if (s == NULL) { + return; + } + + container_state_lock(s); + + state = s->state; + + state->running = false; + state->starting = false; + state->restarting = false; + state->paused = false; + state->exit_code = exit_code; + state->pid = 0; + state->start_time = 0; + state->p_pid = 0; + state->p_start_time = 0; + + (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); + free(state->finished_at); + state->finished_at = util_strdup_s(timebuffer); + + container_state_unlock(s); +} + +/* state set paused*/ +void state_set_paused(container_state_t *s) +{ + container_config_v2_state *state = NULL; + + if (s == NULL) { + return; + } + + container_state_lock(s); + + state = s->state; + state->paused = true; + + container_state_unlock(s); +} + +/* state reset paused*/ +void state_reset_paused(container_state_t *s) +{ + container_config_v2_state *state = NULL; + + if (s == NULL) { + return; + } + + container_state_lock(s); + + state = s->state; + state->paused = false; + + container_state_unlock(s); +} + +/* update start and finish time*/ +void update_start_and_finish_time(container_state_t *s, const char *finish_at) +{ + container_config_v2_state *state = NULL; + char timebuffer[TIME_STR_SIZE] = { 0 }; + + if (s == NULL) { + return; + } + + container_state_lock(s); + + state = s->state; + + state->running = true; + state->starting = false; + state->restarting = false; + state->paused = false; + state->exit_code = 0; + + (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); + free(state->finished_at); + state->finished_at = util_strdup_s(finish_at); + free(state->started_at); + state->started_at = util_strdup_s(timebuffer); + + container_state_unlock(s); + + return; +} + +/* state set restarting*/ +void state_set_restarting(container_state_t *s, int exit_code) +{ + container_config_v2_state *state = NULL; + char timebuffer[TIME_STR_SIZE] = { 0 }; + + if (s == NULL) { + return; + } + + container_state_lock(s); + + state = s->state; + + state->running = true; + state->starting = false; + state->restarting = true; + state->paused = false; + state->pid = 0; + state->start_time = 0; + state->p_pid = 0; + state->p_start_time = 0; + state->exit_code = exit_code; + + (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); + free(state->finished_at); + state->finished_at = util_strdup_s(timebuffer); + + container_state_unlock(s); + + return; +} + +// state_set_removal_in_progress sets the container state as being removed. +// It returns true if the container was already in that state +bool state_set_removal_in_progress(container_state_t *s) +{ + bool ret = false; + + if (s == NULL) { + return false; + } + + container_state_lock(s); + + if (s->state->removal_inprogress) { + ret = true; + } else { + s->state->removal_inprogress = true; + } + + container_state_unlock(s); + + return ret; +} + +/* state reset removal in progress*/ +void state_reset_removal_in_progress(container_state_t *s) +{ + if (s == NULL) { + return; + } + + container_state_lock(s); + + s->state->removal_inprogress = false; + + container_state_unlock(s); + + return; +} + +/* container state set error*/ +void container_state_set_error(container_state_t *s, const char *err) +{ + container_state_lock(s); + if (err != NULL) { + free(s->state->error); + s->state->error = util_strdup_s(err); + } + container_state_unlock(s); +} + +/* state judge status*/ +Container_Status state_judge_status(const container_config_v2_state *state) +{ + if (state == NULL) { + return CONTAINER_STATUS_UNKNOWN; + } + + if (state->running) { + if (state->paused) { + return CONTAINER_STATUS_PAUSED; + } + if (state->restarting) { + return CONTAINER_STATUS_RESTARTING; + } + + return CONTAINER_STATUS_RUNNING; + } + + if (state->starting) { + return CONTAINER_STATUS_STARTING; + } + + if (state->started_at == NULL || state->finished_at == NULL) { + return CONTAINER_STATUS_UNKNOWN; + } + if (strcmp(state->started_at, defaultContainerTime) == 0 && + strcmp(state->finished_at, defaultContainerTime) == 0) { + return CONTAINER_STATUS_CREATED; + } + + return CONTAINER_STATUS_STOPPED; +} + +const char *state_to_string(Container_Status cs) +{ + const char *state_string[] = { + "unknown", "inited", "starting", "running", "exited", "paused", "restarting" + }; + + if (cs >= CONTAINER_STATUS_MAX_STATE) { + return "unknown"; + } + return state_string[cs]; +} + +/* state get status*/ +Container_Status state_get_status(container_state_t *s) +{ + Container_Status status = CONTAINER_STATUS_UNKNOWN; + + if (s == NULL) { + return status; + } + + container_state_lock(s); + + status = state_judge_status(s->state); + + container_state_unlock(s); + return status; +} + +int dup_health_check_status(defs_health **dst, const defs_health *src) +{ + int ret = 0; + size_t i = 0; + defs_health *result = NULL; + + if (src == NULL) { + return 0; + } + result = util_common_calloc_s(sizeof(defs_health)); + if (result == NULL) { + ERROR("Out of memory"); + return -1; + } + result->status = src->status ? util_strdup_s(src->status) : NULL; + result->failing_streak = src->failing_streak; + if (src->log_len != 0) { + if (src->log_len > SIZE_MAX / sizeof(defs_health_log_element *)) { + ERROR("Invalid log size"); + ret = -1; + goto error; + } + result->log = util_common_calloc_s(sizeof(defs_health_log_element *) * src->log_len); + if (result->log == NULL) { + ERROR("Out of memory"); + ret = -1; + goto error; + } + for (i = 0; i < src->log_len; i++) { + result->log[i] = util_common_calloc_s(sizeof(defs_health_log_element)); + if (result->log[i] == NULL) { + ERROR("Out of memory"); + ret = -1; + goto error; + } + result->log[i]->start = util_strdup_s(src->log[i]->start); + result->log[i]->end = util_strdup_s(src->log[i]->end); + result->log[i]->exit_code = src->log[i]->exit_code; + result->log[i]->output = util_strdup_s(src->log[i]->output); + result->log_len++; + } + } + *dst = result; + return ret; +error: + free_defs_health(result); + return ret; +} + +/* state get info*/ +container_config_v2_state *state_get_info(container_state_t *s) +{ + container_config_v2_state *state = NULL; + + if (s == NULL) { + return NULL; + } + + state = util_common_calloc_s(sizeof(container_config_v2_state)); + if (state == NULL) { + return NULL; + } + + container_state_lock(s); + + state->pid = s->state->pid; + state->oom_killed = s->state->oom_killed; + state->dead = s->state->dead; + state->paused = s->state->paused; + state->running = s->state->running; + state->restarting = s->state->restarting; + state->removal_inprogress = s->state->removal_inprogress; + state->starting = s->state->starting; + state->exit_code = s->state->exit_code; + + state->started_at = s->state->started_at ? + util_strdup_s(s->state->started_at) : util_strdup_s(defaultContainerTime); + state->finished_at = s->state->finished_at ? + util_strdup_s(s->state->finished_at) : util_strdup_s(defaultContainerTime); + state->error = s->state->error ? util_strdup_s(s->state->error) : NULL; + + if (dup_health_check_status(&state->health, s->state->health) != 0) { + ERROR("Failed to dup health check info"); + free_container_config_v2_state(state); + state = NULL; + } + + container_state_unlock(s); + + return state; +} + +/* state get exitcode*/ +uint32_t state_get_exitcode(container_state_t *s) +{ + uint32_t exit_code = 0; + container_config_v2_state *state = NULL; + + if (s == NULL) { + return exit_code; + } + + container_state_lock(s); + + state = s->state; + exit_code = (uint32_t)state->exit_code; + + container_state_unlock(s); + + return exit_code; +} + +/* is running*/ +bool is_running(container_state_t *s) +{ + bool ret = false; + + if (s == NULL) { + return false; + } + + container_state_lock(s); + + ret = s->state->running; + + container_state_unlock(s); + + return ret; +} + +/* is restarting*/ +bool is_restarting(container_state_t *s) +{ + bool ret = false; + + if (s == NULL) { + return false; + } + + container_state_lock(s); + + ret = s->state->restarting; + + container_state_unlock(s); + + return ret; +} + +/* is paused*/ +bool is_paused(container_state_t *s) +{ + bool ret = false; + + if (s == NULL) { + return false; + } + + container_state_lock(s); + + ret = s->state->paused; + + container_state_unlock(s); + + return ret; +} + +/* is removal in progress*/ +bool is_removal_in_progress(container_state_t *s) +{ + bool ret = false; + + if (s == NULL) { + return false; + } + + container_state_lock(s); + + ret = s->state->removal_inprogress; + + container_state_unlock(s); + + return ret; +} + +/* is dead */ +bool is_dead(container_state_t *s) +{ + bool ret = false; + + if (s == NULL) { + return false; + } + + container_state_lock(s); + + ret = s->state->dead; + + container_state_unlock(s); + + return ret; +} + + +// state_get_pid holds the process id of a container. +int state_get_pid(container_state_t *s) +{ + int pid = 0; + + if (s == NULL) { + return pid; + } + + container_state_lock(s); + + pid = s->state->pid; + + container_state_unlock(s); + + return pid; +} + +/* state get started at */ +char *state_get_started_at(container_state_t *s) +{ + char *ret = NULL; + + if (s == NULL) { + return NULL; + } + + container_state_lock(s); + + ret = util_strdup_s(s->state->started_at); + + container_state_unlock(s); + + return ret; +} + +static inline bool is_state_string_paused(const char *state) +{ + return strcmp(state, "paused") == 0; +} + +static inline bool is_state_string_restarting(const char *state) +{ + return strcmp(state, "restarting") == 0; +} + +static inline bool is_state_string_running(const char *state) +{ + return strcmp(state, "running") == 0; +} + +static inline bool is_state_string_dead(const char *state) +{ + return strcmp(state, "dead") == 0; +} + +static inline bool is_state_string_created(const char *state) +{ + return strcmp(state, "created") == 0; +} + +static inline bool is_state_string_exited(const char *state) +{ + return strcmp(state, "exited") == 0; +} + +bool is_valid_state_string(const char *state) +{ + if (state == NULL) { + return false; + } + if (!is_state_string_paused(state) && !is_state_string_restarting(state) && \ + !is_state_string_running(state) && !is_state_string_dead(state) && \ + !is_state_string_created(state) && !is_state_string_exited(state)) { + return false; + } + return true; +} + diff --git a/src/services/execution/manager/container_state.h b/src/services/execution/manager/container_state.h new file mode 100644 index 0000000..9110360 --- /dev/null +++ b/src/services/execution/manager/container_state.h @@ -0,0 +1,99 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container state definition + ******************************************************************************/ +#ifndef __LCRD_CONTAINER_STATE_H__ +#define __LCRD_CONTAINER_STATE_H__ + +#include + +#include "liblcrd.h" +#include "container_config_v2.h" +#include "engine.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +typedef struct _container_state_t_ { + pthread_mutex_t mutex; + container_config_v2_state *state; +} container_state_t; + + +container_state_t *container_state_new(void); + +void container_state_free(container_state_t *state); + +container_config_v2_state *state_get_info(container_state_t *s); + +void container_state_lock(container_state_t *state); + +void container_state_unlock(container_state_t *state); + +void update_start_and_finish_time(container_state_t *s, const char *finish_at); + +void state_set_starting(container_state_t *s); + +void state_reset_starting(container_state_t *s); + +void state_set_running(container_state_t *s, const container_pid_t *pid_info, bool initial); + +void state_set_stopped(container_state_t *s, int exit_code); + +void state_set_restarting(container_state_t *s, int exit_code); + +void state_set_paused(container_state_t *s); +void state_reset_paused(container_state_t *s); + +void state_set_dead(container_state_t *s); + +// state_set_removal_in_progress sets the container state as being removed. +// It returns true if the container was already in that state +bool state_set_removal_in_progress(container_state_t *s); + +void state_reset_removal_in_progress(container_state_t *s); + +const char *state_to_string(Container_Status cs); + +Container_Status state_judge_status(const container_config_v2_state *state); + +Container_Status state_get_status(container_state_t *s); + +bool is_running(container_state_t *s); + +bool is_restarting(container_state_t *s); + +bool is_removal_in_progress(container_state_t *s); + +bool is_paused(container_state_t *s); + +uint32_t state_get_exitcode(container_state_t *s); + +int state_get_pid(container_state_t *s); + +bool is_dead(container_state_t *s); + +void container_state_set_error(container_state_t *s, const char *err); + +char *state_get_started_at(container_state_t *s); + +bool is_valid_state_string(const char *state); + +int dup_health_check_status(defs_health **dst, const defs_health *src); + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* __LCRD_CONTAINER_STATE_H__ */ diff --git a/src/services/execution/manager/container_unix.c b/src/services/execution/manager/container_unix.c new file mode 100644 index 0000000..4532983 --- /dev/null +++ b/src/services/execution/manager/container_unix.c @@ -0,0 +1,1334 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2017-11-22 + * Description: provide container unix functions + ******************************************************************************/ +#include +#include +#include +#include + +#include "constants.h" +#include "container_unix.h" +#include "log.h" +#include "utils.h" +#include "container_custom_config.h" +#include "container_start_generate_config.h" + +static int parse_container_log_configs(container_t *cont); + +static int init_container_mutex(container_t *cont) +{ + int ret = 0; + + ret = pthread_mutex_init(&(cont->mutex), NULL); + if (ret != 0) { + ERROR("Failed to init mutex of container"); + ret = -1; + goto out; + } + cont->init_mutex = true; + + ret = pthread_cond_init(&(cont->wait_stop_con), NULL); + if (ret != 0) { + ERROR("Failed to init wait stop condition of container"); + ret = -1; + goto out; + } + cont->init_wait_stop_con = true; + + ret = pthread_cond_init(&(cont->wait_rm_con), NULL); + if (ret != 0) { + ERROR("Failed to init wait remove condition of container"); + ret = -1; + goto out; + } + cont->init_wait_rm_con = true; + +out: + return ret; +} + +/* notes: hostconfig and common_config will be free in this function on error */ +container_t *container_new(const char *runtime, const char *rootpath, const char *statepath, + host_config **hostconfig, container_config_v2_common_config **common_config) +{ + int ret = 0; + container_t *cont = NULL; + host_config *tmp_host_config = NULL; + container_config_v2_common_config *tmp_common_config = NULL; + + if (common_config == NULL || *common_config == NULL || rootpath == NULL || statepath == NULL || + hostconfig == NULL || *hostconfig == NULL || runtime == NULL) { + return NULL; + } + + tmp_host_config = *hostconfig; + tmp_common_config = *common_config; + + *hostconfig = NULL; + *common_config = NULL; + + cont = util_common_calloc_s(sizeof(container_t)); + if (cont == NULL) { + free_container_config_v2_common_config(tmp_common_config); + free_host_config(tmp_host_config); + ERROR("Out of memory"); + return NULL; + } + + atomic_int_set(&cont->refcnt, 1); + cont->common_config = tmp_common_config; + cont->hostconfig = tmp_host_config; + + ret = init_container_mutex(cont); + if (ret != 0) { + goto error_out; + } + + ret = parse_container_log_configs(cont); + if (ret != 0) { + goto error_out; + } + + cont->runtime = util_strdup_s(runtime); + cont->root_path = util_strdup_s(rootpath); + cont->state_path = util_strdup_s(statepath); + cont->state = container_state_new(); + if (cont->state == NULL) { + ERROR("Out of memory"); + goto error_out; + } + + cont->rm = restart_manager_new(tmp_host_config->restart_policy, + tmp_common_config->restart_count); + if (cont->rm == NULL) { + ERROR("Out of memory"); + goto error_out; + } + + cont->handler = events_handler_new(); + if (cont->handler == NULL) { + ERROR("Out of memory"); + goto error_out; + } + + return cont; + +error_out: + container_unref(cont); + return NULL; +} + +/* container free */ +void container_free(container_t *container) +{ + if (container == NULL) { + return; + } + + free_container_config_v2_common_config(container->common_config); + container->common_config = NULL; + + container_state_free(container->state); + container->state = NULL; + + free(container->runtime); + container->runtime = NULL; + free(container->root_path); + container->root_path = NULL; + free(container->state_path); + container->state_path = NULL; + + free(container->log_path); + container->log_path = NULL; + + free_host_config(container->hostconfig); + + restart_manager_unref(container->rm); + + events_handler_free(container->handler); + + health_check_manager_free(container->health_check); + + if (container->init_wait_stop_con) { + pthread_cond_destroy(&container->wait_stop_con); + } + + if (container->init_wait_rm_con) { + pthread_cond_destroy(&container->wait_rm_con); + } + + if (container->init_mutex) { + pthread_mutex_destroy(&container->mutex); + } + + free(container); +} + +/* container refinc */ +void container_refinc(container_t *cont) +{ + if (cont == NULL) { + return; + } + atomic_int_inc(&cont->refcnt); +} + +/* container unref */ +void container_unref(container_t *cont) +{ + bool is_zero = false; + + if (cont == NULL) { + return; + } + + is_zero = atomic_int_dec_test(&cont->refcnt); + if (!is_zero) { + return; + } + + container_free(cont); +} + +/* container lock */ +void container_lock(container_t *cont) +{ + if (cont == NULL) { + ERROR("Invalid input arguments"); + return; + } + + if (pthread_mutex_lock(&cont->mutex) != 0) { + ERROR("Failed to lock container '%s'", cont->common_config->id); + } +} + +/* container timedlock */ +int container_timedlock(container_t *cont, int timeout) +{ + struct timespec ts; + + if (cont == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + if (timeout <= 0) { + return pthread_mutex_lock(&cont->mutex); + } else { + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + ERROR("Failed to get real time"); + return -1; + } + ts.tv_sec += timeout; + + return pthread_mutex_timedlock(&cont->mutex, &ts); + } +} + + +/* container unlock */ +void container_unlock(container_t *cont) +{ + if (cont == NULL) { + ERROR("Invalid input arguments"); + return; + } + + if (pthread_mutex_unlock(&cont->mutex) != 0) { + ERROR("Failed to unlock container '%s'", cont->common_config->id); + } +} + +/* container wait stop cond broadcast */ +void container_wait_stop_cond_broadcast(container_t *cont) +{ + if (cont == NULL) { + ERROR("Invalid input arguments"); + return; + } + if (pthread_cond_broadcast(&cont->wait_stop_con) != 0) { + ERROR("Failed to broadcast wait stop condition container '%s'", cont->common_config->id); + } +} + +/* container wait stop cond wait */ +static int container_wait_stop_cond_wait(container_t *cont, int timeout) +{ + struct timespec ts; + + if (timeout < 0) { + return pthread_cond_wait(&cont->wait_stop_con, &cont->mutex); + } + + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + ERROR("Failed to get real time"); + return -1; + } + ts.tv_sec += timeout; + + return pthread_cond_timedwait(&cont->wait_stop_con, &cont->mutex, &ts); +} + +/* container wait remove cond broadcast */ +void container_wait_rm_cond_broadcast(container_t *cont) +{ + if (cont == NULL) { + ERROR("Invalid input arguments"); + return; + } + if (pthread_cond_broadcast(&cont->wait_rm_con)) { + ERROR("Failed to broadcast wait remove condition container '%s'", cont->common_config->id); + } +} + +/* container wait remove cond wait */ +static int container_wait_rm_cond_wait(container_t *cont, int timeout) +{ + struct timespec ts; + + if (timeout < 0) { + return pthread_cond_wait(&cont->wait_rm_con, &cont->mutex); + } + + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + ERROR("Failed to get real time"); + return -1; + } + ts.tv_sec += timeout; + + return pthread_cond_timedwait(&cont->wait_rm_con, &cont->mutex, &ts); +} + +/* container wait remove with locking */ +int container_wait_rm_locking(container_t *cont, int timeout) +{ + int ret = 0; + + if (cont == NULL) { + return -1; + } + + container_lock(cont); + + ret = container_wait_rm_cond_wait(cont, timeout); + + container_unlock(cont); + + return ret; +} + +static int pack_container_config_annotations_from_oci_spec(const oci_runtime_spec *oci_spec, + container_config_v2_common_config *v2_spec) +{ + int ret = 0; + size_t i = 0; + + if (oci_spec->annotations != NULL && oci_spec->annotations->len) { + if (v2_spec->config == NULL) { + v2_spec->config = util_common_calloc_s(sizeof(container_config)); + if (v2_spec->config == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + } + v2_spec->config->annotations = util_common_calloc_s(sizeof(json_map_string_string)); + if (v2_spec->config->annotations == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + if (oci_spec->annotations->len > SIZE_MAX / sizeof(char *)) { + ERROR("Annotations list is too long!"); + ret = -1; + goto out; + } + v2_spec->config->annotations->keys = + util_common_calloc_s(sizeof(char *) * oci_spec->annotations->len); + if (v2_spec->config->annotations->keys == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + v2_spec->config->annotations->values = + util_common_calloc_s(sizeof(char *) * oci_spec->annotations->len); + if (v2_spec->config->annotations->values == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + for (i = 0; i < oci_spec->annotations->len; i++) { + v2_spec->config->annotations->keys[i] = util_strdup_s(oci_spec->annotations->keys[i]); + v2_spec->config->annotations->values[i] = util_strdup_s(oci_spec->annotations->values[i]); + v2_spec->config->annotations->len++; + } + } + +out: + return ret; +} + +static int pack_container_config_labels(container_config_v2_common_config *config, + const container_custom_config *custom_spec) +{ + int ret = 0; + size_t i = 0; + + if (custom_spec->labels != NULL && custom_spec->labels->len) { + if (config->config == NULL) { + config->config = util_common_calloc_s(sizeof(container_config)); + if (config->config == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + } + config->config->labels = util_common_calloc_s(sizeof(json_map_string_string)); + if (config->config->labels == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + if (custom_spec->labels->len > LIST_SIZE_MAX) { + ERROR("Labels list is too long, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Labels list is too long, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto out; + } + config->config->labels->keys = util_common_calloc_s(sizeof(char *) * custom_spec->labels->len); + if (config->config->labels->keys == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + config->config->labels->values = util_common_calloc_s(sizeof(char *) * custom_spec->labels->len); + if (config->config->labels->values == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + for (i = 0; i < custom_spec->labels->len; i++) { + config->config->labels->keys[i] = util_strdup_s(custom_spec->labels->keys[i]); + config->config->labels->values[i] = util_strdup_s(custom_spec->labels->values[i]); + config->config->labels->len++; + } + } + +out: + return ret; +} + +static int pack_container_config_health_check(container_config_v2_common_config *config, + const container_custom_config *custom_spec) +{ + int ret = 0; + size_t i = 0; + + if (custom_spec != NULL && custom_spec->health_check != NULL) { + if (config->config == NULL) { + config->config = util_common_calloc_s(sizeof(container_config)); + if (config->config == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + } + config->config->health_check = util_common_calloc_s(sizeof(defs_health_check)); + if (config->config->health_check == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (custom_spec->health_check->test != NULL && custom_spec->health_check->test_len != 0) { + if (custom_spec->health_check->test_len > SIZE_MAX / sizeof(char *)) { + ERROR("test list is too long!"); + ret = -1; + goto out; + } + config->config->health_check->test = + util_common_calloc_s(sizeof(char *) * custom_spec->health_check->test_len); + if (config->config->health_check->test == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + for (i = 0; i < custom_spec->health_check->test_len; i++) { + config->config->health_check->test[i] = util_strdup_s(custom_spec->health_check->test[i]); + config->config->health_check->test_len++; + } + + config->config->health_check->interval = custom_spec->health_check->interval; + config->config->health_check->timeout = custom_spec->health_check->timeout; + config->config->health_check->start_period = custom_spec->health_check->start_period; + config->config->health_check->retries = custom_spec->health_check->retries; + config->config->health_check->exit_on_unhealthy = custom_spec->health_check->exit_on_unhealthy; + } + } +out: + return ret; +} + +static inline void add_to_config_v2_args(const char *str, char **args, size_t *args_len) +{ + args[*args_len] = str ? util_strdup_s(str) : NULL; + (*args_len)++; +} + +static int pack_path_and_args_from_custom_spec(const container_custom_config *custom_spec, + container_config_v2_common_config *v2_spec) +{ + int ret = 0; + size_t i, total; + + if (custom_spec->entrypoint != NULL && custom_spec->entrypoint_len > 0) { + v2_spec->path = util_strdup_s(custom_spec->entrypoint[0]); + total = custom_spec->entrypoint_len + custom_spec->cmd_len - 1; + + if (total > SIZE_MAX / sizeof(char *)) { + ERROR("Container oci spec process args elements is too much!"); + ret = -1; + goto out; + } + if (total == 0) { + goto out; + } + + v2_spec->args = util_common_calloc_s(total * sizeof(char *)); + if (v2_spec->args == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + for (i = 1; i < custom_spec->entrypoint_len; i++) { + add_to_config_v2_args(custom_spec->entrypoint[i], v2_spec->args, &(v2_spec->args_len)); + } + for (i = 0; i < custom_spec->cmd_len; i++) { + add_to_config_v2_args(custom_spec->cmd[i], v2_spec->args, &(v2_spec->args_len)); + } + goto out; + } + + if (custom_spec->cmd != NULL && custom_spec->cmd_len > 0) { + v2_spec->path = util_strdup_s(custom_spec->cmd[0]); + total = custom_spec->cmd_len - 1; + + if (total > SIZE_MAX / sizeof(char *)) { + ERROR("Container oci spec process args elements is too much!"); + ret = -1; + goto out; + } + if (total == 0) { + goto out; + } + + v2_spec->args = util_common_calloc_s(total * sizeof(char *)); + if (v2_spec->args == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + for (i = 1; i < custom_spec->cmd_len; i++) { + add_to_config_v2_args(custom_spec->cmd[i], v2_spec->args, &(v2_spec->args_len)); + } + } + +out: + return ret; +} + +/* container make basic v2 spec info */ +int v2_spec_make_basic_info(const char *id, const char *name, const char *image_name, const char *image_type, + container_config_v2_common_config *v2_spec) +{ + char timebuffer[TIME_STR_SIZE] = { 0 }; + + if (v2_spec == NULL) { + return -1; + } + + v2_spec->id = id ? util_strdup_s(id) : NULL; + v2_spec->name = name ? util_strdup_s(name) : NULL; + v2_spec->image = image_name ? util_strdup_s(image_name) : util_strdup_s("none"); + v2_spec->image_type = image_type ? util_strdup_s(image_type) : NULL; + (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); + free(v2_spec->created); + v2_spec->created = util_strdup_s(timebuffer); + + return 0; +} + +/* container merge basic v2 spec info */ +int v2_spec_merge_custom_spec(const container_custom_config *custom_spec, container_config_v2_common_config *v2_spec) +{ + int ret = 0; + + if (v2_spec == NULL || custom_spec == NULL) { + return -1; + } + + if (custom_spec->log_config != NULL && custom_spec->log_config->log_file != NULL) { + v2_spec->log_path = util_strdup_s(custom_spec->log_config->log_file); + } + + if (v2_spec->config == NULL) { + v2_spec->config = util_common_calloc_s(sizeof(container_config)); + if (v2_spec->config == NULL) { + ERROR("Failed to malloc container_config_v2_common_config_config"); + ret = -1; + goto out; + } + } + + v2_spec->config->attach_stdin = custom_spec->attach_stdin; + v2_spec->config->attach_stdout = custom_spec->attach_stdout; + v2_spec->config->attach_stderr = custom_spec->attach_stderr; + v2_spec->config->tty = custom_spec->tty; + v2_spec->config->open_stdin = custom_spec->open_stdin; + + if (custom_spec->user != NULL) { + v2_spec->config->user = util_strdup_s(custom_spec->user); + } + + if (pack_path_and_args_from_custom_spec(custom_spec, v2_spec) != 0) { + ret = -1; + goto out; + } + + + ret = dup_array_of_strings((const char **)(custom_spec->cmd), custom_spec->cmd_len, + &(v2_spec->config->cmd), &(v2_spec->config->cmd_len)); + if (ret != 0) { + goto out; + } + + ret = dup_array_of_strings((const char **)(custom_spec->entrypoint), custom_spec->entrypoint_len, + &(v2_spec->config->entrypoint), &(v2_spec->config->entrypoint_len)); + if (ret != 0) { + goto out; + } + + ret = pack_container_config_labels(v2_spec, custom_spec); + if (ret != 0) { + ERROR("Failed to pack labels config"); + ret = -1; + goto out; + } + + ret = pack_container_config_health_check(v2_spec, custom_spec); + if (ret != 0) { + ERROR("Failed to pack health check config"); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int pack_envs_from_oci_spec(const oci_runtime_spec *oci_spec, const container_config_v2_common_config *v2_spec) +{ + int ret = 0; + + if (oci_spec->process != NULL && oci_spec->process->env != NULL) { + ret = dup_array_of_strings((const char **)(oci_spec->process->env), oci_spec->process->env_len, + &(v2_spec->config->env), &(v2_spec->config->env_len)); + if (ret != 0) { + goto out; + } + } + +out: + return ret; +} + +static void pack_hostname_from_oci_spec(const oci_runtime_spec *oci_spec, + const container_config_v2_common_config *v2_spec) +{ + if (oci_spec->hostname != NULL) { + free(v2_spec->config->hostname); + v2_spec->config->hostname = util_strdup_s(oci_spec->hostname); + } +} + +/* container pack common config */ +int v2_spec_merge_oci_spec(const oci_runtime_spec *oci_spec, container_config_v2_common_config *v2_spec) +{ + if (oci_spec == NULL || v2_spec == NULL) { + ERROR("Invalid inputs for pack container common config"); + return -1; + } + + if (pack_envs_from_oci_spec(oci_spec, v2_spec) != 0) { + return -1; + } + + pack_hostname_from_oci_spec(oci_spec, v2_spec); + + if (pack_container_config_annotations_from_oci_spec(oci_spec, v2_spec) != 0) { + ERROR("Failed to pack annotations config"); + return -1; + } + + return 0; +} + +/* save json config file */ +static int save_json_config_file(const char *id, const char *rootpath, + const char *json_data, const char *fname) +{ + int ret = 0; + int nret; + int fd = -1; + ssize_t len = 0; + char filename[PATH_MAX] = { 0 }; + + if (json_data == NULL || strlen(json_data) == 0) { + return 0; + } + nret = sprintf_s(filename, sizeof(filename), "%s/%s/%s", rootpath, id, fname); + if (nret < 0) { + ERROR("Failed to print string"); + ret = -1; + goto out; + } + + fd = util_open(filename, O_CREAT | O_TRUNC | O_CLOEXEC | O_WRONLY, CONFIG_FILE_MODE); + if (fd == -1) { + ERROR("Create file %s failed: %s", filename, strerror(errno)); + lcrd_set_error_message("Create file '%s' failed: %s", filename, strerror(errno)); + ret = -1; + goto out; + } + + len = util_write_nointr(fd, json_data, strlen(json_data)); + if (len < 0 || ((size_t)len) != strlen(json_data)) { + ERROR("Write file %s failed: %s", filename, strerror(errno)); + lcrd_set_error_message("Write file '%s' failed: %s", filename, strerror(errno)); + ret = -1; + } + close(fd); + +out: + return ret; +} + +#define CONFIG_V2_JSON "config.v2.json" + +/* save config v2 json */ +int save_config_v2_json(const char *id, const char *rootpath, const char *v2configstr) +{ + if (rootpath == NULL || id == NULL || v2configstr == NULL) { + return -1; + } + + return save_json_config_file(id, rootpath, v2configstr, CONFIG_V2_JSON); +} + +/* read config v2 */ +container_config_v2 *read_config_v2(const char *rootpath, const char *id) +{ + int nret; + char filename[PATH_MAX] = { 0x00 }; + parser_error err = NULL; + container_config_v2 *v2config = NULL; + + nret = sprintf_s(filename, sizeof(filename), "%s/%s/%s", rootpath, id, CONFIG_V2_JSON); + if (nret < 0) { + ERROR("Failed to print string"); + goto out; + } + + v2config = container_config_v2_parse_file(filename, NULL, &err); + if (v2config == NULL) { + ERROR("Failed to parse v2 config file:%s", err); + goto out; + } +out: + free(err); + + return v2config; +} + +#define HOSTCONFIGJSON "hostconfig.json" +/* save host config */ +int save_host_config(const char *id, const char *rootpath, const char *hostconfigstr) +{ + if (rootpath == NULL || id == NULL || hostconfigstr == NULL) { + return -1; + } + return save_json_config_file(id, rootpath, hostconfigstr, HOSTCONFIGJSON); +} + +static host_config *read_host_config(const char *rootpath, const char *id) +{ + int nret; + char filename[PATH_MAX] = { 0x00 }; + parser_error err = NULL; + host_config *hostconfig = NULL; + + nret = sprintf_s(filename, sizeof(filename), "%s/%s/%s", rootpath, id, HOSTCONFIGJSON); + if (nret < 0) { + ERROR("Failed to print string"); + goto out; + } + + hostconfig = host_config_parse_file(filename, NULL, &err); + if (hostconfig == NULL) { + ERROR("Failed to parse host config file:%s", err); + goto out; + } +out: + free(err); + return hostconfig; +} + +static bool check_start_generate_config(const char *rootpath, const char *id) +{ +#define START_GENERATE_CONFIG "start_generate_config.json" + int nret; + bool ret = false; + char filename[PATH_MAX] = { 0x00 }; + parser_error err = NULL; + container_start_generate_config *config = NULL; + + nret = sprintf_s(filename, sizeof(filename), "%s/%s/%s", rootpath, id, START_GENERATE_CONFIG); + if (nret < 0) { + ERROR("Failed to print string"); + goto out; + } + + if (!util_file_exists(filename)) { + return true; + } + + config = container_start_generate_config_parse_file(filename, NULL, &err); + if (config == NULL) { + ERROR("Failed to parse start generate config file:%s", err); + goto out; + } + ret = true; +out: + free(err); + free_container_start_generate_config(config); + return ret; +} + +/* container save host config */ +static int container_save_host_config(const container_t *cont) +{ + int ret = 0; + parser_error err = NULL; + char *json_host_config = NULL; + + if (cont == NULL) { + return -1; + } + + json_host_config = host_config_generate_json(cont->hostconfig, NULL, &err); + if (json_host_config == NULL) { + ERROR("Failed to generate container host config json string:%s", err ? err : " "); + ret = -1; + goto out; + } + + ret = save_host_config(cont->common_config->id, cont->root_path, json_host_config); + if (ret != 0) { + ERROR("Failed to save container host config json to file"); + ret = -1; + goto out; + } + +out: + free(json_host_config); + free(err); + + return ret; +} + +/* container save config v2 */ +static int container_save_config_v2(const container_t *cont) +{ + int ret = 0; + char *json_v2 = NULL; + parser_error err = NULL; + container_config_v2 config_v2; + + if (cont == NULL) { + return -1; + } + + container_state_lock(cont->state); + + config_v2.common_config = cont->common_config; + + config_v2.state = cont->state->state; + + json_v2 = container_config_v2_generate_json(&config_v2, NULL, &err); + if (json_v2 == NULL) { + ERROR("Failed to generate container config V2 json string:%s", err ? err : " "); + ret = -1; + goto out; + } + + ret = save_config_v2_json(cont->common_config->id, cont->root_path, json_v2); + if (ret != 0) { + ERROR("Failed to save container config V2 json to file"); + ret = -1; + goto out; + } + +out: + free(json_v2); + free(err); + container_state_unlock(cont->state); + return ret; +} + +/* container to disk */ +int container_to_disk(const container_t *cont) +{ + int ret = 0; + + if (cont == NULL) { + return -1; + } + + ret = container_save_config_v2(cont); + if (ret != 0) { + return ret; + } + + ret = container_save_host_config(cont); + if (ret != 0) { + return ret; + } + + return ret; +} + +/* container to disk locking */ +int container_to_disk_locking(container_t *cont) +{ + int ret = 0; + + if (cont == NULL) { + return -1; + } + + container_lock(cont); + + ret = container_to_disk(cont); + + container_unlock(cont); + return ret; +} + +static int do_parse_container_log_config(const char *key, const char *value, container_t *cont) +{ + if (strcmp(key, CONTAINER_LOG_CONFIG_KEY_FILE) == 0) { + cont->log_path = util_strdup_s(value); + } else if (strcmp(key, CONTAINER_LOG_CONFIG_KEY_ROTATE) == 0) { + return util_safe_int(value, &(cont->log_rotate)); + } else if (strcmp(key, CONTAINER_LOG_CONFIG_KEY_SIZE) == 0) { + return util_parse_byte_size_string(value, &(cont->log_maxsize)); + } + return 0; +} + +/* get log config of container */ +static int parse_container_log_configs(container_t *cont) +{ + int ret = -1; + size_t i = 0; + json_map_string_string *tmp_annos = NULL; + + if (cont == NULL) { + return -1; + } + + if (cont->common_config == NULL || cont->common_config->config == NULL || + cont->common_config->config->annotations == NULL) { + return 0; + } + + tmp_annos = cont->common_config->config->annotations; + for (i = 0; i < tmp_annos->len; i++) { + if (do_parse_container_log_config(tmp_annos->keys[i], tmp_annos->values[i], cont) != 0) { + ERROR("parse key: %s, value: %s failed", tmp_annos->keys[i], tmp_annos->values[i]); + goto out; + } + } + + ret = 0; +out: + return ret; +} + +/* container load */ +container_t *container_load(const char *runtime, const char *rootpath, const char *statepath, const char *id) +{ + container_config_v2 *v2config = NULL; + container_config_v2_common_config *common_config = NULL; + host_config *hostconfig = NULL; + container_t *cont = NULL; + + if (rootpath == NULL || statepath == NULL || id == NULL || runtime == NULL) { + return NULL; + } + + if (!check_start_generate_config(rootpath, id)) { + return NULL; + } + v2config = read_config_v2(rootpath, id); + if (v2config == NULL) { + ERROR("Failed to read config v2 file:%s", id); + return NULL; + } + + hostconfig = read_host_config(rootpath, id); + if (hostconfig == NULL) { + ERROR("Failed to host config file for container: %s", id); + goto error_out; + } + + common_config = v2config->common_config; + v2config->common_config = NULL; + + cont = container_new(runtime, rootpath, statepath, &hostconfig, &common_config); + if (cont == NULL) { + ERROR("Failed to create container '%s'", id); + goto error_out; + } + + /* replace cont->state->state with v2config->state */ + free_container_config_v2_state(cont->state->state); + + cont->state->state = v2config->state; + v2config->state = NULL; + + free_container_config_v2(v2config); + + return cont; + +error_out: + free_container_config_v2_common_config(common_config); + free_host_config(hostconfig); + free_container_config_v2(v2config); + container_unref(cont); + return NULL; +} + +static char *append_quote_to_arg(const char *arg) +{ + size_t arg_len, total; + char *new_arg = NULL; + const char *part = ""; + + arg_len = strlen(arg); + if (arg_len > SIZE_MAX - 3) { + ERROR("Arg is too long"); + return NULL; + } + + total = arg_len + 1; + if (strchr(arg, ' ') != NULL) { + total += 2; + part = "'"; + } + new_arg = util_common_calloc_s(total); + if (new_arg == NULL) { + ERROR("Out of memory"); + return NULL; + } + if (sprintf_s(new_arg, total, "%s%s%s", part, arg, part) < 0) { + free(new_arg); + ERROR("Sprintf failed"); + return NULL; + } + return new_arg; +} + +/* container get command */ +char *container_get_command(const container_t *cont) +{ + int nret; + size_t i; + char *cmd = NULL; + char **args = NULL; + + if (cont == NULL || cont->common_config == NULL) { + return NULL; + } + + if (cont->common_config->path != NULL) { + nret = util_array_append(&args, cont->common_config->path); + if (nret < 0) { + ERROR("Appned string failed"); + goto cleanup; + } + } + + for (i = 0; cont->common_config->args != NULL && i < cont->common_config->args_len; i++) { + char *arg = NULL; + + arg = append_quote_to_arg(cont->common_config->args[i]); + if (arg == NULL) { + goto cleanup; + } + nret = util_array_append(&args, arg); + free(arg); + if (nret < 0) { + ERROR("Appned string failed"); + goto cleanup; + } + } + + cmd = util_string_join(" ", (const char **)args, util_array_len(args)); + +cleanup: + util_free_array(args); + return cmd; +} + +/* container get image */ +char *container_get_image(const container_t *cont) +{ + char *tmp = NULL; + + if (cont == NULL) { + return NULL; + } + + if (cont->common_config != NULL && cont->common_config->image != NULL) { + tmp = util_strdup_s(cont->common_config->image); + } + + return tmp; +} + +/* container reset manually stopped */ +void container_reset_manually_stopped(container_t *cont) +{ + if (cont == NULL) { + return; + } + + container_lock(cont); + + cont->common_config->has_been_manually_stopped = false; + + container_unlock(cont); + return; +} + +/* reset restart manager */ +bool reset_restart_manager(container_t *cont, bool reset_count) +{ + if (cont == NULL) { + ERROR("Invalid input arguments"); + return false; + } + + if (cont->rm != NULL) { + if (restart_manager_cancel(cont->rm)) { + ERROR("Failed to cancel restart manager"); + return false; + } + restart_manager_unref(cont->rm); + } + if (reset_count) { + cont->common_config->restart_count = 0; + } + cont->rm = NULL; + return true; +} + +/* get restart manager */ +restart_manager_t *get_restart_manager(container_t *cont) +{ + if (cont == NULL) { + ERROR("Invalid input arguments"); + return NULL; + } + + if (cont->rm == NULL) { + cont->rm = restart_manager_new(cont->hostconfig->restart_policy, cont->common_config->restart_count); + } + restart_manager_refinc(cont->rm); + return cont->rm; +} + +/* container update restart manager */ +void container_update_restart_manager(container_t *cont, const host_config_restart_policy *policy) +{ + restart_manager_t *rm = NULL; + + if (cont == NULL || policy == NULL) { + ERROR("Invalid input arguments"); + return; + } + + rm = get_restart_manager(cont); + (void)restart_manager_set_policy(rm, policy); + restart_manager_unref(rm); +} + +/* container exit on next */ +int container_exit_on_next(container_t *cont) +{ + int ret = 0; + restart_manager_t *rm = NULL; + + if (cont == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + rm = get_restart_manager(cont); + if (rm == NULL) { + return -1; + } + ret = restart_manager_cancel(rm); + restart_manager_unref(rm); + return ret; +} + +/* this function should be called in container_lock*/ +int container_wait_stop(container_t *cont, int timeout) +{ + int ret = 0; + + if (cont == NULL) { + return -1; + } + + if (!is_running(cont->state)) { + goto unlock; + } + + ret = container_wait_stop_cond_wait(cont, timeout); +unlock: + return ret; +} + +/* container wait stop locking */ +int container_wait_stop_locking(container_t *cont, int timeout) +{ + int ret = 0; + + if (cont == NULL) { + return -1; + } + + container_lock(cont); + + if (!is_running(cont->state)) { + goto unlock; + } + + ret = container_wait_stop_cond_wait(cont, timeout); +unlock: + container_unlock(cont); + return ret; +} + +static container_pid_t *parse_container_pid(const char *S) +{ + int num; + container_pid_t *P = NULL; + + if (S == NULL) { + return NULL; + } + + P = util_common_calloc_s(sizeof(container_pid_t)); + if (P == NULL) { + return NULL; + } + + num = sscanf_s(S, "%d %Lu %d %Lu", &P->pid, &P->start_time, &P->ppid, &P->pstart_time); + if (num != 4) { // args num to read is 4 + ERROR("Call sscanf error: %s", errno ? strerror(errno) : ""); + free(P); + return NULL; + } + + return P; +} + +container_pid_t *container_read_pidfile(const char *pidfile) +{ + if (pidfile == NULL) { + ERROR("Invalid input arguments"); + return NULL; + } + + char sbuf[1024] = { 0 }; /* bufs for stat */ + + if ((util_file2str(pidfile, sbuf, sizeof(sbuf))) == -1) { + return NULL; + } + + return parse_container_pid(sbuf); +} + +char *container_get_env_nolock(const container_t *cont, const char *key) +{ + size_t i = 0; + size_t key_len = 0; + char *val = NULL; + const char *env = NULL; + const container_config_v2_common_config *cc = NULL; + const container_config *ccc = NULL; + + if (cont == NULL) { + ERROR("nil container_t"); + return val; + } + + if (key == NULL) { + ERROR("nil key"); + return val; + } + + key_len = strlen(key); + + cc = cont->common_config; + if (cc == NULL) { + ERROR("nil container common_config"); + return val; + } + + ccc = cc->config; + if (ccc == NULL) { + ERROR("nil container common_config config"); + return val; + } + + for (i = 0; i < ccc->env_len; i++) { + env = ccc->env[i]; + size_t env_len = strlen(env); + if (key_len < env_len && !strncmp(key, env, key_len) && env[key_len] == '=') { + val = util_strdup_s(env + key_len + 1); + break; + } + } + + return val; +} + diff --git a/src/services/execution/manager/container_unix.h b/src/services/execution/manager/container_unix.h new file mode 100644 index 0000000..2b5d99d --- /dev/null +++ b/src/services/execution/manager/container_unix.h @@ -0,0 +1,117 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide container unix definition + ******************************************************************************/ +#ifndef __LCRD_CONTAINER_UNIX_H__ +#define __LCRD_CONTAINER_UNIX_H__ + +#include + +#include "liblcrd.h" +#include "util_atomic.h" +#include "container_custom_config.h" +#include "container_config_v2.h" +#include "host_config.h" +#include "container_state.h" +#include "oci_runtime_spec.h" +#include "restartmanager.h" +#include "events_handler.h" +#include "health_check.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +typedef struct _container_t_ { + pthread_mutex_t mutex; + bool init_mutex; + pthread_cond_t wait_stop_con; + bool init_wait_stop_con; + pthread_cond_t wait_rm_con; + bool init_wait_rm_con; + uint64_t refcnt; + char *runtime; + char *root_path; + char *state_path; + container_config_v2_common_config *common_config; + container_state_t *state; + host_config *hostconfig; + restart_manager_t *rm; + events_handler_t *handler; + health_check_manager_t *health_check; + + /* log configs of container */ + char *log_path; + int log_rotate; + int64_t log_maxsize; +} container_t; + +void container_refinc(container_t *cont); + +void container_unref(container_t *cont); + +container_t *container_new(const char *runtime, const char *rootpath, const char *statepath, host_config **hostconfig, + container_config_v2_common_config **common_config); + +container_t *container_load(const char *runtime, const char *rootpath, const char *statepath, const char *id); + +int container_to_disk(const container_t *cont); + +int container_to_disk_locking(container_t *cont); + +void container_lock(container_t *cont); + +int container_timedlock(container_t *cont, int timeout); + +void container_unlock(container_t *cont); + +char *container_get_env_nolock(const container_t *cont, const char *key); + +int v2_spec_make_basic_info(const char *id, const char *name, const char *image_name, const char *image_type, + container_config_v2_common_config *v2_spec); + +int v2_spec_merge_custom_spec(const container_custom_config *custom_spec, container_config_v2_common_config *v2_spec); + +int v2_spec_merge_oci_spec(const oci_runtime_spec *oci_spec, container_config_v2_common_config *v2_spec); + +char *container_get_command(const container_t *cont); + +char *container_get_image(const container_t *cont); + +int container_exit_on_next(container_t *cont); + +restart_manager_t *get_restart_manager(container_t *cont); + +bool reset_restart_manager(container_t *cont, bool reset_count); + +void container_update_restart_manager(container_t *cont, const host_config_restart_policy *policy); + +void container_reset_manually_stopped(container_t *cont); + +void container_wait_stop_cond_broadcast(container_t *cont); +int container_wait_stop(container_t *cont, int timeout); +int container_wait_stop_locking(container_t *cont, int timeout); + +void container_wait_rm_cond_broadcast(container_t *cont); +int container_wait_rm_locking(container_t *cont, int timeout); + +container_pid_t *container_read_pidfile(const char *pidfile); + +int save_host_config(const char *id, const char *rootpath, const char *hostconfigstr); +int save_config_v2_json(const char *id, const char *rootpath, const char *v2configstr); + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* __LCRD_CONTAINER_UNIX_H__ */ diff --git a/src/services/execution/manager/containers_gc.c b/src/services/execution/manager/containers_gc.c new file mode 100644 index 0000000..69e4b34 --- /dev/null +++ b/src/services/execution/manager/containers_gc.c @@ -0,0 +1,519 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container gc functions + ******************************************************************************/ +#include +#include +#include +#include + +#include "constants.h" +#include "containers_gc.h" +#include "lcrd_config.h" +#include "container_garbage_config.h" +#include "log.h" +#include "utils.h" +#include "execution.h" +#include "containers_store.h" + +static containers_gc_t g_gc_containers; + +/* gc containers lock */ +static void gc_containers_lock() +{ + if (pthread_mutex_lock(&(g_gc_containers.mutex)) != 0) { + ERROR("Failed to lock garbage containers list"); + } +} + +/* gc containers unlock */ +static void gc_containers_unlock() +{ + if (pthread_mutex_unlock(&(g_gc_containers.mutex)) != 0) { + ERROR("Failed to unlock garbage containers list"); + } +} + +#define GCCONFIGJSON "garbage.json" +/* save gc config */ +static int save_gc_config(const char *json_gc_config) +{ + int ret = 0; + int nret; + char filename[PATH_MAX] = { 0 }; + char *rootpath = NULL; + int fd = -1; + + rootpath = conf_get_lcrd_rootdir(); + if (rootpath == NULL) { + ERROR("Root path is NULL"); + ret = -1; + goto out; + } + + nret = sprintf_s(filename, sizeof(filename), "%s/%s", rootpath, GCCONFIGJSON); + if (nret < 0) { + ERROR("Failed to print string"); + ret = -1; + goto out; + } + + fd = util_open(filename, O_CREAT | O_TRUNC | O_CLOEXEC | O_WRONLY, CONFIG_FILE_MODE); + if (fd == -1) { + ERROR("Create file %s failed: %s", filename, strerror(errno)); + ret = -1; + goto out; + } + + if (write(fd, json_gc_config, strlen(json_gc_config)) == -1) { + ERROR("write %s failed: %s", filename, strerror(errno)); + ret = -1; + } + close(fd); + +out: + free(rootpath); + return ret; +} + +/* gc save containers config */ +static int gc_save_containers_config(const container_garbage_config *saves) +{ + int ret = 0; + parser_error err = NULL; + char *json_gc_config = NULL; + + if (saves == NULL) { + return -1; + } + + json_gc_config = container_garbage_config_generate_json(saves, NULL, &err); + if (json_gc_config == NULL) { + ERROR("Failed to generate container gc config json string:%s", err ? err : " "); + ret = -1; + goto out; + } + + ret = save_gc_config(json_gc_config); + if (ret != 0) { + ERROR("Failed to save container gc config json to file"); + ret = -1; + goto out; + } + +out: + free(json_gc_config); + free(err); + return ret; +} + +/* notes: this funciton must be called with gc_containers_lock */ +static int gc_containers_to_disk() +{ + int ret = 0; + size_t size = 0; + struct linked_list *it = NULL; + struct linked_list *next = NULL; + container_garbage_config_gc_containers_element **conts = NULL; + container_garbage_config saves = { 0 }; + + size = linked_list_len(&g_gc_containers.containers_list); + + if (size != 0) { + size_t i = 0; + if (size > SIZE_MAX / sizeof(container_garbage_config_gc_containers_element *)) { + ERROR("Garbage collection list is too long!"); + return -1; + } + conts = util_common_calloc_s(sizeof(container_garbage_config_gc_containers_element *) * size); + if (conts == NULL) { + ERROR("Out of memory"); + return -1; + } + linked_list_for_each_safe(it, &g_gc_containers.containers_list, next) { + conts[i] = (container_garbage_config_gc_containers_element *)it->elem; + i++; + } + } + + saves.gc_containers_len = size; + saves.gc_containers = conts; + + + ret = gc_save_containers_config(&saves); + + free(conts); + + return ret; +} + +/* gc is gc progress */ +bool gc_is_gc_progress(const char *id) +{ + bool ret = false; + struct linked_list *it = NULL; + struct linked_list *next = NULL; + container_garbage_config_gc_containers_element *cont = NULL; + + gc_containers_lock(); + + linked_list_for_each_safe(it, &g_gc_containers.containers_list, next) { + cont = (container_garbage_config_gc_containers_element *)it->elem; + if (strcmp(id, cont->id) == 0) { + ret = true; + break; + } + } + + gc_containers_unlock(); + + return ret; +} + +/* gc add container */ +int gc_add_container(const char *id, const char *runtime, const container_pid_t *pid_info) +{ + struct linked_list *newnode = NULL; + container_garbage_config_gc_containers_element *gc_cont = NULL; + + if (pid_info == NULL) { + CRIT("Invalid inputs for add garbage collector."); + return -1; + } + + newnode = util_common_calloc_s(sizeof(struct linked_list)); + if (newnode == NULL) { + CRIT("Memory allocation error."); + return -1; + } + + gc_cont = util_common_calloc_s(sizeof(container_garbage_config_gc_containers_element)); + if (gc_cont == NULL) { + CRIT("Memory allocation error."); + free(newnode); + return -1; + } + + gc_cont->id = util_strdup_s(id); + gc_cont->runtime = util_strdup_s(runtime); + gc_cont->pid = pid_info->pid; + gc_cont->start_time = pid_info->start_time; + gc_cont->ppid = pid_info->ppid; + gc_cont->p_start_time = pid_info->pstart_time; + + gc_containers_lock(); + + linked_list_add_elem(newnode, gc_cont); + linked_list_add_tail(&g_gc_containers.containers_list, newnode); + (void)gc_containers_to_disk(); + + gc_containers_unlock(); + + return 0; +} + +/* read gc config */ +container_garbage_config *read_gc_config() +{ + int nret; + char filename[PATH_MAX] = { 0x00 }; + parser_error err = NULL; + container_garbage_config *gcconfig = NULL; + char *rootpath = NULL; + + rootpath = conf_get_lcrd_rootdir(); + if (rootpath == NULL) { + ERROR("Root path is NULL"); + goto out; + } + + nret = sprintf_s(filename, sizeof(filename), "%s/%s", rootpath, GCCONFIGJSON); + if (nret < 0) { + ERROR("Failed to print string"); + goto out; + } + + gcconfig = container_garbage_config_parse_file(filename, NULL, &err); + if (gcconfig == NULL) { + INFO("Failed to parse gc config file:%s", err); + goto out; + } +out: + free(err); + free(rootpath); + return gcconfig; +} + +/* gc restore */ +int gc_restore() +{ + int ret = 0; + size_t i = 0; + container_garbage_config *gcconfig = NULL; + struct linked_list *newnode = NULL; + + gcconfig = read_gc_config(); + if (gcconfig == NULL) { + goto out; + } + + gc_containers_lock(); + + for (i = 0; i < gcconfig->gc_containers_len; i++) { + newnode = util_common_calloc_s(sizeof(struct linked_list)); + if (newnode == NULL) { + gc_containers_unlock(); + CRIT("Memory allocation error, failed to restore garbage collector."); + ret = -1; + goto out; + } + + linked_list_add_elem(newnode, gcconfig->gc_containers[i]); + linked_list_add_tail(&g_gc_containers.containers_list, newnode); + gcconfig->gc_containers[i] = NULL; + } + gcconfig->gc_containers_len = 0; + + (void)gc_containers_to_disk(); + gc_containers_unlock(); + +out: + free_container_garbage_config(gcconfig); + return ret; +} + +/* apply restart policy after gc */ +static void apply_auto_remove_after_gc(const char *id) +{ + container_t *cont = NULL; + + cont = containers_store_get(id); + if (cont == NULL) { + INFO("Container '%s' already removed", id); + goto out; + } + + if (is_running(cont->state)) { + INFO("container %s already running, skip apply auto remove after gc", id); + goto out; + } + + if (cont->hostconfig != NULL && cont->hostconfig->auto_remove_bak) { + (void)set_container_to_removal(cont); + (void)cleanup_container(cont, true); + } + +out: + container_unref(cont); + return; +} + +/* apply restart policy after gc */ +static void apply_restart_policy_after_gc(const char *id) +{ + container_t *cont = NULL; + char *started_at = NULL; + bool should_restart = false; + uint64_t timeout; + uint32_t exit_code; + + cont = containers_store_get(id); + if (cont == NULL) { + INFO("Container '%s' already removed", id); + goto out; + } + + container_lock(cont); + + if (is_running(cont->state)) { + INFO("container %s already running, skip apply restart policy after gc", id); + goto unlock_out; + } + + started_at = state_get_started_at(cont->state); + exit_code = state_get_exitcode(cont->state); + + should_restart = restart_manager_should_restart(id, exit_code, cont->common_config->has_been_manually_stopped, + time_seconds_since(started_at), &timeout); + free(started_at); + + if (should_restart) { + cont->common_config->restart_count++; + state_set_restarting(cont->state, (int)exit_code); + INFO("Try to restart container %s after %.2fs", id, (double)timeout / Time_Second); + (void)container_restart_in_thread(id, timeout, (int)exit_code); + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", id); + goto unlock_out; + } + } + +unlock_out: + container_unlock(cont); +out: + container_unref(cont); + return; +} + +static void gc_monitor_process(const char *id, pid_t pid, unsigned long long start_time) +{ + INFO("Received garbage collector monitor of %s with pid %d", id, pid); + + if (util_process_alive(pid, start_time)) { + int ret = kill(pid, SIGKILL); + if (ret < 0 && errno != ESRCH) { + ERROR("Can not kill monitor process (pid=%d) with SIGKILL", pid); + } + } +} + +static void gc_container_process(struct linked_list *it) +{ + int ret = 0; + int pid = 0; + unsigned long long start_time = 0; + char *runtime = NULL; + char *id = NULL; + container_garbage_config_gc_containers_element *gc_cont = NULL; + + gc_cont = (container_garbage_config_gc_containers_element *)it->elem; + id = gc_cont->id; + runtime = gc_cont->runtime; + pid = gc_cont->pid; + start_time = gc_cont->start_time; + + if (util_process_alive(pid, start_time) == false) { + ret = clean_container_resource(id, runtime, pid); + if (ret != 0) { + WARN("Failed to clean resources of container %s", id); + } + + /* remove container from gc list */ + gc_containers_lock(); + + linked_list_del(it); + (void)gc_containers_to_disk(); + + gc_containers_unlock(); + + /* apply restart policy for the container after gc */ + apply_restart_policy_after_gc(id); + + apply_auto_remove_after_gc(id); + + free_container_garbage_config_gc_containers_element(gc_cont); + free(it); + } else { + ret = kill(pid, SIGKILL); + if (ret < 0 && errno != ESRCH) { + ERROR("Can not kill process (pid=%d) with SIGKILL for container %s", pid, id); + } + + gc_containers_lock(); + + linked_list_del(it); + linked_list_add_tail(&g_gc_containers.containers_list, it); + + gc_containers_unlock(); + } +} +static void do_gc_container(struct linked_list *it) +{ + container_garbage_config_gc_containers_element *gc_cont = NULL; + + gc_cont = (container_garbage_config_gc_containers_element *)it->elem; + + gc_monitor_process(gc_cont->id, gc_cont->ppid, gc_cont->p_start_time); + + gc_container_process(it); + + return; +} + +static void *gchandler(void *arg) +{ + int ret = 0; + struct linked_list *it = NULL; + + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Set thread detach fail"); + goto error; + } + + prctl(PR_SET_NAME, "Garbage_collector"); + + for (;;) { + gc_containers_lock(); + + if (linked_list_empty(&g_gc_containers.containers_list)) { + gc_containers_unlock(); + goto wait_continue; + } + it = linked_list_first_node(&g_gc_containers.containers_list); + + gc_containers_unlock(); + + do_gc_container(it); + +wait_continue: + usleep_nointerupt(100 * 1000); /* wait 100 millisecond to check next gc container*/ + } +error: + return NULL; +} + +/* new gchandler */ +int new_gchandler() +{ + int ret = -1; + + linked_list_init(&(g_gc_containers.containers_list)); + + ret = pthread_mutex_init(&(g_gc_containers.mutex), NULL); + if (ret != 0) { + CRIT("Mutex initialization failed"); + goto out; + } + + INFO("Restoring garbage collector..."); + + if (gc_restore()) { + ERROR("Failed to restore garbage collector"); + pthread_mutex_destroy(&(g_gc_containers.mutex)); + goto out; + } + + ret = 0; +out: + return ret; +} + +/* start gchandler */ +int start_gchandler() +{ + int ret = -1; + pthread_t a_thread; + + INFO("Starting garbage collector..."); + + ret = pthread_create(&a_thread, NULL, gchandler, NULL); + if (ret != 0) { + CRIT("Thread creation failed"); + goto out; + } + + ret = 0; +out: + return ret; +} diff --git a/src/services/execution/manager/containers_gc.h b/src/services/execution/manager/containers_gc.h new file mode 100644 index 0000000..6c7130f --- /dev/null +++ b/src/services/execution/manager/containers_gc.h @@ -0,0 +1,48 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container gc definition + ******************************************************************************/ +#ifndef __LCRD_CONTAINER_GC_H__ +#define __LCRD_CONTAINER_GC_H__ + +#include + +#include "liblcrd.h" +#include "linked_list.h" +#include "container_unix.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +typedef struct _containers_gc_t_ { + pthread_mutex_t mutex; + struct linked_list containers_list; +} containers_gc_t; + +int new_gchandler(); + +int gc_add_container(const char *id, const char *runtime, const container_pid_t *pid_info); + +int gc_restore(); + +int start_gchandler(); + +bool gc_is_gc_progress(const char *id); + + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* __LCRD_CONTAINER_GC_H__ */ diff --git a/src/services/execution/manager/containers_store.c b/src/services/execution/manager/containers_store.c new file mode 100644 index 0000000..734a653 --- /dev/null +++ b/src/services/execution/manager/containers_store.c @@ -0,0 +1,534 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container store functions + ******************************************************************************/ +#include +#include + +#include "containers_store.h" +#include "log.h" +#include "utils.h" + +typedef struct memory_store_t { + map_t *map; // map string container_t + pthread_rwlock_t rwlock; +} memory_store; + +typedef struct name_index_t { + map_t *map; + pthread_rwlock_t rwlock; +} name_index; + +static memory_store *g_containers_store = NULL; + +static name_index *g_indexs = NULL; + +/* memory store map kvfree */ +static void memory_store_map_kvfree(void *key, void *value) +{ + free(key); + + container_unref((container_t *)value); +} + +/* memory store free */ +static void memory_store_free(memory_store *store) +{ + if (store == NULL) { + return; + } + map_free(store->map); + store->map = NULL; + pthread_rwlock_destroy(&(store->rwlock)); + free(store); +} + +/* memory store new */ +static memory_store *memory_store_new(void) +{ + int ret; + memory_store *store = NULL; + + store = util_common_calloc_s(sizeof(memory_store)); + if (store == NULL) { + ERROR("Out of memory"); + return NULL; + } + ret = pthread_rwlock_init(&(store->rwlock), NULL); + if (ret != 0) { + ERROR("Failed to init memory store rwlock"); + free(store); + return NULL; + } + store->map = map_new(MAP_STR_PTR, MAP_DEFAULT_CMP_FUNC, memory_store_map_kvfree); + if (store->map == NULL) { + ERROR("Out of memory"); + goto error_out; + } + return store; +error_out: + memory_store_free(store); + return NULL; +} + +/* containers store add */ +bool containers_store_add(const char *id, container_t *cont) +{ + bool ret = false; + + if (pthread_rwlock_wrlock(&g_containers_store->rwlock)) { + ERROR("lock memory store failed"); + return false; + } + ret = map_replace(g_containers_store->map, (void *)id, (void *)cont); + if (pthread_rwlock_unlock(&g_containers_store->rwlock)) { + ERROR("unlock memory store failed"); + return false; + } + return ret; +} + +/* containers store get */ +static container_t *containers_store_get_by_id(const char *id) +{ + container_t *cont = NULL; + + if (id == NULL) { + return NULL; + } + if (pthread_rwlock_rdlock(&g_containers_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return cont; + } + cont = map_search(g_containers_store->map, (void *)id); + container_refinc(cont); + if (pthread_rwlock_unlock(&g_containers_store->rwlock) != 0) { + ERROR("unlock memory store failed"); + return cont; + } + return cont; +} + +/* containers store get container by container name*/ +static container_t *containers_store_get_by_name(const char *name) +{ + char *id = NULL; + + if (name == NULL) { + ERROR("No container name supplied"); + return NULL; + } + + id = name_index_get(name); + if (id == NULL) { + WARN("Could not find entity for %s", name); + return NULL; + } + + return containers_store_get_by_id(id); +} + +/* containers store get container by prefix*/ +container_t *containers_store_get_by_prefix(const char *prefix) +{ + bool ret = false; + char *container_id = NULL; + container_t *cont = NULL; + map_itor *itor = NULL; + + if (prefix == NULL) { + return NULL; + } + if (pthread_rwlock_rdlock(&g_containers_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return NULL; + } + + itor = map_itor_new(g_containers_store->map); + if (itor == NULL) { + ERROR("Out of memory"); + ret = false; + goto unlock; + } + + for (; map_itor_valid(itor); map_itor_next(itor)) { + container_id = map_itor_key(itor); + if (container_id == NULL) { + ERROR("Out of memory"); + ret = false; + goto unlock; + } + if (strncmp(container_id, prefix, strlen(prefix)) == 0) { + if (cont != NULL) { + ERROR("Multiple IDs found with provided prefix: %s", prefix); + ret = false; + goto unlock; + } else { + cont = map_itor_value(itor); + } + } + } + + ret = true; + container_refinc(cont); + +unlock: + if (pthread_rwlock_unlock(&g_containers_store->rwlock) != 0) { + ERROR("unlock memory store failed"); + } + map_itor_free(itor); + if (!ret) { + cont = NULL; + } + return cont; +} + +// containers_store_get looks for a container using the provided information, which could be +// one of the following inputs from the caller: +// - A full container ID, which will exact match a container in daemon's list +// - A container name, which will only exact match via the containers_store_get_by_name() function +// - A partial container ID prefix (e.g. short ID) of any length that is +// unique enough to only return a single container object +// If none of these searches succeed, an error is returned +container_t *containers_store_get(const char *id_or_name) +{ + container_t *cont = NULL; + + if (id_or_name == NULL) { + ERROR("No container name or ID supplied"); + return NULL; + } + + // A full container ID, which will exact match a container in daemon's list + cont = containers_store_get_by_id(id_or_name); + if (cont != NULL) { + return cont; + } + + // A container name, which will only exact match via the containers_store_get_by_name() function + cont = containers_store_get_by_name(id_or_name); + if (cont != NULL) { + return cont; + } + + // A partial container ID prefix + cont = containers_store_get_by_prefix(id_or_name); + if (cont != NULL) { + return cont; + } + + return NULL; +} + +/* containers store list */ +int containers_store_list(container_t ***out, size_t *size) +{ + int ret = -1; + size_t i; + container_t **conts = NULL; + map_itor *itor = NULL; + + if (pthread_rwlock_rdlock(&g_containers_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return -1; + } + + *size = map_size(g_containers_store->map); + if (*size == 0) { + ret = 0; + goto unlock; + } + if (*size > SIZE_MAX / sizeof(container_t *)) { + ERROR("Containers store list is too long!"); + goto unlock; + } + conts = util_common_calloc_s(sizeof(container_t *) * (*size)); + if (conts == NULL) { + ERROR("Out of memory"); + goto unlock; + } + + itor = map_itor_new(g_containers_store->map); + if (itor == NULL) { + ERROR("Out of memory"); + goto unlock; + } + + for (i = 0; map_itor_valid(itor) && + i < *size; map_itor_next(itor), i++) { + conts[i] = map_itor_value(itor); + container_refinc(conts[i]); + } + ret = 0; +unlock: + if (pthread_rwlock_unlock(&g_containers_store->rwlock)) { + ERROR("unlock memory store failed"); + } + map_itor_free(itor); + if (ret != 0) { + free(conts); + *size = 0; + conts = NULL; + } + *out = conts; + return ret; +} + +/* containers store list names */ +char **containers_store_list_ids(void) +{ + bool ret = false; + char **idsarray = NULL; + map_itor *itor = NULL; + + if (pthread_rwlock_rdlock(&g_containers_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return NULL; + } + + if (map_size(g_containers_store->map) == 0) { + ret = true; + goto unlock; + } + + itor = map_itor_new(g_containers_store->map); + if (itor == NULL) { + ERROR("Out of memory"); + goto unlock; + } + + for (; map_itor_valid(itor); map_itor_next(itor)) { + char *id = map_itor_key(itor); + if (util_array_append(&idsarray, id ? id : "-")) { + ERROR("Out of memory"); + goto unlock; + } + } + ret = true; +unlock: + if (pthread_rwlock_unlock(&g_containers_store->rwlock)) { + ERROR("unlock memory store failed"); + } + map_itor_free(itor); + if (!ret) { + util_free_array(idsarray); + idsarray = NULL; + } + return idsarray; +} + +/* containers store remove */ +bool containers_store_remove(const char *id) +{ + bool ret = false; + + if (pthread_rwlock_wrlock(&g_containers_store->rwlock) != 0) { + ERROR("lock memory store failed"); + return false; + } + ret = map_remove(g_containers_store->map, (void *)id); + if (pthread_rwlock_unlock(&g_containers_store->rwlock) != 0) { + ERROR("unlock memory store failed"); + return false; + } + return ret; +} + +/* containers store init */ +int containers_store_init(void) +{ + g_containers_store = memory_store_new(); + if (g_containers_store == NULL) { + return -1; + } + return 0; +} + +/* name index free */ +static void name_index_free(name_index *indexs) +{ + if (indexs == NULL) { + return; + } + map_free(indexs->map); + indexs->map = NULL; + pthread_rwlock_destroy(&(indexs->rwlock)); + free(indexs); +} + +/* name index new */ +static name_index *name_index_new(void) +{ + int ret; + name_index *indexs = NULL; + + indexs = util_common_calloc_s(sizeof(name_index)); + if (indexs == NULL) { + ERROR("Out of memory"); + return NULL; + } + ret = pthread_rwlock_init(&(indexs->rwlock), NULL); + if (ret != 0) { + ERROR("Failed to init name g_indexs rwlock"); + free(indexs); + return NULL; + } + indexs->map = map_new(MAP_STR_STR, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC); + if (indexs->map == NULL) { + ERROR("Out of memory"); + goto error_out; + } + return indexs; +error_out: + name_index_free(indexs); + return NULL; +} + +/* name index add */ +bool name_index_add(const char *name, const char *id) +{ + bool ret = false; + + if (pthread_rwlock_wrlock(&g_indexs->rwlock) != 0) { + ERROR("lock name index failed"); + return false; + } + ret = map_insert(g_indexs->map, (void *)name, (void *)id); + if (pthread_rwlock_unlock(&g_indexs->rwlock) != 0) { + ERROR("unlock name index failed"); + return false; + } + return ret; +} + +/* name index rename */ +bool name_index_rename(const char *new_name, const char *old_name, const char *id) +{ + bool ret = false; + + if (pthread_rwlock_wrlock(&g_indexs->rwlock) != 0) { + ERROR("lock name index failed"); + return false; + } + ret = map_insert(g_indexs->map, (void *)new_name, (void *)id); + if (!ret) { + goto unlock_out; + } + + ret = map_remove(g_indexs->map, (void *)old_name); + +unlock_out: + if (pthread_rwlock_unlock(&g_indexs->rwlock) != 0) { + ERROR("unlock name index failed"); + return false; + } + return ret; +} + + +/* name index get */ +char *name_index_get(const char *name) +{ + char *id = NULL; + + if (name == NULL) { + return id; + } + if (pthread_rwlock_rdlock(&g_indexs->rwlock) != 0) { + ERROR("lock name index failed"); + return id; + } + id = map_search(g_indexs->map, (void *)name); + if (pthread_rwlock_unlock(&g_indexs->rwlock) != 0) { + ERROR("unlock name index failed"); + } + return id; +} + +/* name index remove */ +bool name_index_remove(const char *name) +{ + bool ret = false; + + if (pthread_rwlock_wrlock(&g_indexs->rwlock) != 0) { + ERROR("lock name index failed"); + return false; + } + ret = map_remove(g_indexs->map, (void *)name); + if (pthread_rwlock_unlock(&g_indexs->rwlock) != 0) { + ERROR("unlock name index failed"); + return false; + } + return ret; +} + +/* name index get all */ +map_t *name_index_get_all(void) +{ + bool ret = false; + map_t *map_id_name = NULL; + map_itor *itor = NULL; + + map_id_name = map_new(MAP_STR_STR, MAP_DEFAULT_CMP_FUNC, MAP_DEFAULT_FREE_FUNC); + if (map_id_name == NULL) { + ERROR("Out of memory"); + return NULL; + } + + if (pthread_rwlock_rdlock(&g_indexs->rwlock) != 0) { + ERROR("lock memory store failed"); + goto out; + } + + if (map_size(g_indexs->map) == 0) { + ret = true; + goto unlock; + } + + itor = map_itor_new(g_indexs->map); + if (itor == NULL) { + ERROR("Out of memory"); + goto unlock; + } + + for (; map_itor_valid(itor); map_itor_next(itor)) { + if (!map_insert(map_id_name, map_itor_value(itor), map_itor_key(itor))) { + ERROR("Insert failed"); + goto unlock; + } + } + ret = true; +unlock: + if (pthread_rwlock_unlock(&g_indexs->rwlock)) { + ERROR("unlock memory store failed"); + } +out: + map_itor_free(itor); + if (!ret) { + map_free(map_id_name); + map_id_name = NULL; + } + return map_id_name; +} + + +/* name index init */ +int name_index_init(void) +{ + g_indexs = name_index_new(); + if (g_indexs == NULL) { + return -1; + } + return 0; +} + diff --git a/src/services/execution/manager/containers_store.h b/src/services/execution/manager/containers_store.h new file mode 100644 index 0000000..b4ef36a --- /dev/null +++ b/src/services/execution/manager/containers_store.h @@ -0,0 +1,56 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide containers store definition + ******************************************************************************/ +#ifndef __LCRD_MEMORY_STORE_H__ +#define __LCRD_MEMORY_STORE_H__ + +#include "container_unix.h" +#include "map.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +int containers_store_init(void); + +bool containers_store_add(const char *id, container_t *cont); + +container_t *containers_store_get(const char *id_or_name); + +container_t *containers_store_get_by_prefix(const char *prefix); + +bool containers_store_remove(const char *id); + +int containers_store_list(container_t ***out, size_t *size); + +char **containers_store_list_ids(void); + +/* name indexs */ +int name_index_init(void); + +bool name_index_remove(const char *name); + +char *name_index_get(const char *name); + +bool name_index_add(const char *name, const char *id); + +map_t *name_index_get_all(void); + +bool name_index_rename(const char *new_name, const char *old_name, const char *id); + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* __LCRD_MEMORY_STORE_H__ */ diff --git a/src/services/execution/manager/health_check.c b/src/services/execution/manager/health_check.c new file mode 100644 index 0000000..3ce42a7 --- /dev/null +++ b/src/services/execution/manager/health_check.c @@ -0,0 +1,858 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wujing + * Create: 2018-11-1 + * Description: provide health check functions + *********************************************************************************/ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "health_check.h" +#include "callback.h" +#include "execution.h" +#include "container_exec_request.h" +#include "container_exec_response.h" +#include "containers_store.h" +#include "log_gather.h" + + +/* container state lock */ +static void container_health_check_lock(health_check_manager_t *health) +{ + if (health == NULL) { + return; + } + if (pthread_mutex_lock(&health->mutex)) { + ERROR("Failed to lock health check manager"); + } +} + +/* container state unlock */ +static void container_health_check_unlock(health_check_manager_t *health) +{ + if (health == NULL) { + return; + } + if (pthread_mutex_unlock(&health->mutex)) { + ERROR("Failed to unlock health check manager"); + } +} + +static char *get_health_status(container_state_t *s) +{ + char *status = NULL; + + if (s->state->health->status == NULL || strlen(s->state->health->status) == 0) { + return util_strdup_s(UNHEALTHY); + } + + container_state_lock(s); + status = util_strdup_s(s->state->health->status); + container_state_unlock(s); + + return status; +} + +static void set_health_status(container_state_t *s, const char *new) +{ + if (s == NULL || new == NULL) { + return; + } + container_state_lock(s); + free(s->state->health->status); + s->state->health->status = util_strdup_s(new); + container_state_unlock(s); +} + +static void set_monitor_idle_status(health_check_manager_t *health) +{ + container_health_check_lock(health); + health->monitor_status = MONITOR_IDLE; + container_health_check_unlock(health); +} + +static void set_monitor_stop_status(health_check_manager_t *health) +{ + container_health_check_lock(health); + health->monitor_status = MONITOR_STOP; + container_health_check_unlock(health); +} + +static void set_monitor_interval_timeout_status(health_check_manager_t *health) +{ + container_health_check_lock(health); + health->monitor_status = MONITOR_INTERVAL; + container_health_check_unlock(health); +} + +static health_check_monitor_status_t get_health_check_monitor_state(health_check_manager_t *health) +{ + health_check_monitor_status_t ret; + + container_health_check_lock(health); + ret = health->monitor_status; + container_health_check_unlock(health); + + return ret; +} + +static void close_health_check_monitor(const container_t *cont) +{ + if (cont == NULL || cont->health_check == NULL) { + return; + } + set_monitor_stop_status(cont->health_check); + set_health_status(cont->state, UNHEALTHY); +} + +static void open_health_check_monitor(health_check_manager_t *health) +{ + set_monitor_interval_timeout_status(health); +} + +// Called when the container is being stopped (whether because the health check is +// failing or for any other reason). +void stop_health_checks(const char *container_id) +{ + container_t *cont = NULL; + + if (container_id == NULL) { + return; + } + + cont = containers_store_get(container_id); + if (cont == NULL) { + ERROR("Failed to get container info"); + return; + } + if (cont->state != NULL && cont->state->state != NULL && cont->state->state->health != NULL) { + close_health_check_monitor(cont); + } + container_unref(cont); +} + +// If configuredValue is zero, use defaultValue instead. +int64_t timeout_with_default(int64_t configured_value, int64_t default_value) +{ + if (configured_value == 0) { + return default_value; + } + return configured_value; +} + +/* health check manager free */ +void health_check_manager_free(health_check_manager_t *health_check) +{ + if (health_check == NULL) { + return; + } + if (health_check->init_mutex) { + pthread_mutex_destroy(&health_check->mutex); + } + free(health_check); +} + +/* health check manager new */ +static health_check_manager_t *health_check_manager_new() +{ + int ret; + health_check_manager_t *health_check = NULL; + + health_check = util_common_calloc_s(sizeof(health_check_manager_t)); + if (health_check == NULL) { + ERROR("Out of memory"); + return NULL; + } + ret = pthread_mutex_init(&health_check->mutex, NULL); + if (ret != 0) { + ERROR("Failed to init mutex of health check manager"); + goto cleanup; + } + health_check->init_mutex = true; + + health_check->monitor_status = MONITOR_IDLE; + + return health_check; +cleanup: + health_check_manager_free(health_check); + return NULL; +} + +static ssize_t write_to_string(void *context, const void *data, size_t len) +{ + char *dst = (char *)context; + + if (len == 0) { + return 0; + } + + if (len >= REV_BUF_SIZE) { + if (strncpy_s(dst, REV_BUF_SIZE, data, REV_BUF_SIZE - 4) != EOK) { + ERROR("Failed to set output"); + len = 0; + goto out; + } + if (strcpy_s(dst + REV_BUF_SIZE - 4, strlen("...") + 1, "...") != EOK) { + ERROR("Failed to append string to output"); + len = 0; + goto out; + } + } else { + if (strncpy_s(dst, REV_BUF_SIZE, data, len) != EOK) { + len = 0; + goto out; + } + } +out: + return (ssize_t)len; +} + + +static char **get_shell() +{ + char **shell = NULL; + + if (util_array_append(&shell, "/bin/sh") || util_array_append(&shell, "-c")) { + ERROR("Failed to add shell, out of memory"); + util_free_array(shell); + return NULL; + } + return shell; +} + +static char **health_check_cmds(const container_config *config) +{ + size_t i = 0; + size_t shell_len = 0; + char **shell = NULL; + char **cmd_slice = NULL; + + if (config == NULL) { + return NULL; + } + shell = get_shell(); + if (shell == NULL) { + ERROR("Failed to get shell"); + goto out; + } + + shell_len = util_array_len(shell); + if (shell_len > (SIZE_MAX / sizeof(char *)) - config->health_check->test_len) { + ERROR("Invalid shell length"); + goto out; + } + cmd_slice = util_common_calloc_s((shell_len + config->health_check->test_len) * sizeof(char *)); + if (cmd_slice == NULL) { + ERROR("out of memory"); + goto out; + } + for (i = 0; i < shell_len; i++) { + cmd_slice[i] = util_strdup_s(shell[i]); + } + + for (i = shell_len; i < (shell_len + config->health_check->test_len) - 1; i++) { + cmd_slice[i] = util_strdup_s(config->health_check->test[(i - shell_len) + 1]); + } + +out: + util_free_array(shell); + return cmd_slice; +} + +static int shift_and_store_log_result(defs_health *health, + const defs_health_log_element *result) +{ + int ret = 0; + size_t i = 0; + + for (i = 0; i < MAX_LOG_ENTRIES; i++) { + free(health->log[i]->start); + free(health->log[i]->end); + free(health->log[i]->output); + + if (i != MAX_LOG_ENTRIES - 1) { + health->log[i]->start = util_strdup_s(health->log[i + 1]->start); + health->log[i]->end = util_strdup_s(health->log[i + 1]->end); + health->log[i]->exit_code = health->log[i + 1]->exit_code; + health->log[i]->output = health->log[i + 1]->output != NULL ? + util_strdup_s(health->log[i + 1]->output) : NULL; + } else { + health->log[i]->start = util_strdup_s(result->start); + health->log[i]->end = util_strdup_s(result->end); + health->log[i]->exit_code = result->exit_code; + health->log[i]->output = result->output != NULL ? util_strdup_s(result->output) : NULL; + } + } + health->log_len = MAX_LOG_ENTRIES; + + return ret; +} + +static int append_last_log_result(defs_health *health, + const defs_health_log_element *result) +{ + int ret = 0; + defs_health_log_element **tmp_log = NULL; + defs_health_log_element *log = NULL; + + if (health->log_len > (SIZE_MAX / sizeof(defs_health_log_element *)) - 1) { + ERROR("failed to realloc memory"); + return -1; + } + + ret = mem_realloc((void **)(&tmp_log), (health->log_len + 1) * sizeof(defs_health_log_element *), + health->log, health->log_len * sizeof(defs_health_log_element *)); + if (ret != 0) { + ERROR("failed to realloc memory"); + return -1; + } + health->log = tmp_log; + log = util_common_calloc_s(sizeof(defs_health_log_element)); + if (log == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + log->start = util_strdup_s(result->start); + log->end = util_strdup_s(result->end); + log->exit_code = result->exit_code; + log->output = result->output != NULL ? util_strdup_s(result->output) : NULL; + health->log[health->log_len++] = log; + +out: + return ret; +} + +static int handle_increment_streak(container_t *cont, int retries) +{ + int ret = 0; + defs_health *health = NULL; + + health = cont->state->state->health; + health->failing_streak++; + if (health->failing_streak >= retries) { + set_health_status(cont->state, UNHEALTHY); + if (cont->common_config->config->health_check->exit_on_unhealthy) { + // kill container when exit on unhealthy flag is set + ret = stop_container(cont, 3, true, false); + if (ret != 0) { + lcrd_try_set_error_message("Could not stop running container %s, cannot remove", + cont->common_config->id); + ERROR("Could not stop running container %s, cannot remove", cont->common_config->id); + ret = -1; + } + } + } + return ret; +} + +static int handle_unhealthy_case(container_t *cont, const defs_health_log_element *result, + int retries) +{ + int ret = 0; + bool should_increment_streak = true; + char *health_status = NULL; + + health_status = get_health_status(cont->state); + + if (strcmp(health_status, HEALTH_STARTING) == 0) { + int64_t start_period = timeout_with_default(cont->common_config->config->health_check->start_period, + DEFAULT_START_PERIOD); + int64_t first, last; + if (to_unix_nanos_from_str(cont->state->state->started_at, &first)) { + ERROR("Parse container started time failed: %s", cont->state->state->started_at); + ret = -1; + goto out; + } + if (to_unix_nanos_from_str(result->start, &last)) { + ERROR("Parse last health check start time failed: %s", result->start); + ret = -1; + goto out; + } + if (last - first < start_period) { + should_increment_streak = false; + } + } + if (should_increment_streak) { + ret = handle_increment_streak(cont, retries); + } +out: + free(health_status); + return ret; +} + +static int append_health_log(container_state_t *s, const defs_health_log_element *result) +{ + int ret = 0; + defs_health *health = NULL; + + container_state_lock(s); + + health = s->state->health; + + if (health->log_len >= MAX_LOG_ENTRIES) { + if (shift_and_store_log_result(health, result)) { + ERROR("failed to append last log result"); + ret = -1; + goto out; + } + } else { + if (append_last_log_result(health, result) != 0) { + ERROR("failed to append last log result"); + ret = -1; + goto out; + } + } + +out: + container_state_unlock(s); + + return ret; +} + +// Update the container's Status.Health struct based on the latest probe's result. +static int handle_probe_result(const char *container_id, const defs_health_log_element *result) +{ + int ret = 0; + int retries = 0; + char *current = NULL; + char *old_state = NULL; + defs_health *health = NULL; + container_t *cont = NULL; + + cont = containers_store_get(container_id); + if (cont == NULL) { + ERROR("Failed to get container info"); + return -1; + } + DEBUG("health check result: \n start: %s\n end: %s\n output: %s\n exit_code: %d\n", + result->start, result->end, result->output, result->exit_code); + // probe may have been cancelled while waiting on lock. Ignore result then + if (get_health_check_monitor_state(cont->health_check) == MONITOR_STOP) { + goto out; + } + retries = cont->common_config->config->health_check->retries; + if (retries <= 0) { + retries = DEFAULT_PROBE_RETRIES; + } + health = cont->state->state->health; + old_state = get_health_status(cont->state); + + ret = append_health_log(cont->state, result); + if (ret != 0) { + goto out; + } + + if (result->exit_code == EXIT_STATUS_HEALTHY) { + health->failing_streak = 0; + set_health_status(cont->state, HEALTHY); + } else { + if (handle_unhealthy_case(cont, result, retries)) { + ERROR("failed to handle unhealthy case"); + ret = -1; + goto out; + } + // else we're starting or healthy. Stay in that state. + } + // note: replicate Health status changes + current = get_health_status(cont->state); + if (strcmp(old_state, current) != 0) { + // note: event + EVENT("EVENT: {Object: %s, health_status: %s}", cont->common_config->id, current); + } + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", cont->common_config->id); + ret = -1; + } +out: + free(old_state); + free(current); + container_unref(cont); + + return ret; +} +static void health_check_exec_failed_handle(const container_exec_response *container_res, + defs_health_log_element *result) +{ + if (container_res != NULL) { + if (container_res->errmsg != NULL) { + ERROR("%s, Exit code: %d", container_res->errmsg, (int)container_res->exit_code); + result->output = util_strdup_s(container_res->errmsg); + } else { + ERROR("Execution of exec failed, Exit code: %d", (int)container_res->exit_code); + result->output = util_strdup_s("Execution of exec failed"); + } + } else { + ERROR("Failed to call exec container callback"); + result->output = util_strdup_s("Failed to call exec container callback"); + } + result->exit_code = -1; +} + +static void health_check_exec_success_handle(const container_exec_response *container_res, + defs_health_log_element *result, + const char *output) +{ + result->output = util_strdup_s(output); + if (container_res != NULL) { + result->exit_code = (int)container_res->exit_code; + } else { + result->exit_code = -1; + } +} + +// exec the healthcheck command in the container. +// Returns the exit code and probe output (if any) +void *health_check_run(void *arg) +{ + int ret = 0; + char *container_id = NULL; + char **cmd_slice = NULL; + char output[REV_BUF_SIZE] = { 0 }; + char timebuffer[TIME_STR_SIZE] = { 0 }; + struct io_write_wrapper ctx = { 0 }; + container_t *cont = NULL; + service_callback_t *cb = NULL; + container_exec_request *container_req = NULL; + container_exec_response *container_res = NULL; + defs_health_log_element *result = NULL; + container_config *config = NULL; + + if (arg == NULL) { + ERROR("Invalid input arguments"); + return NULL; + } + + container_id = util_strdup_s((char *)arg); + + cont = containers_store_get(container_id); + if (cont == NULL) { + ERROR("Failed to get container info"); + goto out; + } + + config = cont->common_config->config; + + cmd_slice = health_check_cmds(config); + if (cmd_slice == NULL) { + ERROR("Failed to get health check cmds"); + goto out; + } + + cb = get_service_callback(); + if (cb == NULL || cb->container.exec == NULL) { + ERROR("Failed to get service callback function"); + goto out; + } + + container_req = (container_exec_request *)util_common_calloc_s(sizeof(container_exec_request)); + if (container_req == NULL) { + ERROR("Out of memory"); + goto out; + } + container_req->tty = false; + container_req->attach_stdin = false; + container_req->attach_stdout = true; + container_req->attach_stderr = true; + container_req->timeout = timeout_with_default(config->health_check->timeout, DEFAULT_PROBE_TIMEOUT) / Time_Second; + container_req->container_id = util_strdup_s(cont->common_config->id); + container_req->argv = cmd_slice; + container_req->argv_len = util_array_len(cmd_slice); + cmd_slice = NULL; + EVENT("EVENT: {Object: %s, Type: Health checking}", cont->common_config->id); + + (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); + result = util_common_calloc_s(sizeof(defs_health_log_element)); + if (result == NULL) { + ERROR("Out of memory"); + goto out; + } + result->start = util_strdup_s(timebuffer); + + ctx.context = (void *)output; + ctx.write_func = write_to_string; + ctx.close_func = NULL; + ret = cb->container.exec(container_req, &container_res, -1, &ctx); + if (ret != 0) { + health_check_exec_failed_handle(container_res, result); + } else { + health_check_exec_success_handle(container_res, result, output); + } + + (void)get_now_time_buffer(timebuffer, sizeof(timebuffer)); + result->end = util_strdup_s(timebuffer); + + if (handle_probe_result(cont->common_config->id, result) != 0) { + ERROR("Failed to handle probe result"); + } + +out: + util_free_array(cmd_slice); + free(container_id); + container_id = NULL; + free_defs_health_log_element(result); + free_container_exec_request(container_req); + free_container_exec_response(container_res); + container_unref(cont); + return NULL; +} + +// Get a suitable probe implementation for the container's healthcheck configuration. +// Nil will be returned if no healthcheck was configured or NONE was set. +static health_probe_t get_probe(const container_t *cont) +{ + defs_health_check *config = cont->common_config->config->health_check; + + if (config == NULL || config->test_len == 0) { + return HEALTH_NONE; + } + + if (strcmp(config->test[0], "CMD") == 0) { + return CMD; + } else if (strcmp(config->test[0], "CMD-SHELL") == 0) { + return CMD_SHELL; + } else if (strcmp(config->test[0], "NONE") == 0) { + return HEALTH_NONE; + } else { + WARN("Unknown healthcheck type '%s' (expected 'CMD') in container %s", + config->test[0], cont->common_config->id); + return HEALTH_NONE; + } +} + +static int do_monitor_interval(const char *container_id, + health_check_manager_t *health_check, + types_timestamp_t *start_timestamp) +{ + int ret = 0; + pthread_t exec_tid = { 0 }; + + if (pthread_create(&exec_tid, NULL, health_check_run, (void *)container_id)) { + ERROR("Failed to create thread to exec health check"); + ret = -1; + goto out; + } + if (pthread_join(exec_tid, NULL) < 0) { + ERROR("Failed to run health check thread"); + ret = -1; + goto out; + } + if (get_health_check_monitor_state(health_check) == MONITOR_STOP) { + ret = 0; + goto out; + } + set_monitor_idle_status(health_check); + if (get_now_time_stamp(start_timestamp) == false) { + ERROR("Failed to get time stamp"); + ret = -1; + goto out; + } +out: + return ret; +} + +static int do_monitor_default(int64_t probe_interval, + health_check_manager_t *health_check, + const types_timestamp_t *start_timestamp, + types_timestamp_t *last_timestamp) +{ + int64_t time_interval = 0; + + if (get_now_time_stamp(last_timestamp) == false) { + ERROR("Failed to get time stamp"); + return -1; + } + + if (get_time_interval(*start_timestamp, *last_timestamp, &time_interval)) { + ERROR("Failed to get time interval"); + return -1; + } + + if (time_interval >= probe_interval) { + set_monitor_interval_timeout_status(health_check); + } + usleep_nointerupt(500); + + return 0; +} +// Run the container's monitoring thread until notified via "stop". +// There is never more than one monitor thread running per container at a time. +static void *health_check_monitor(void *arg) +{ + char *container_id = NULL; + int64_t probe_interval = 0; + container_t *cont = NULL; + types_timestamp_t start_timestamp = { 0 }; + types_timestamp_t last_timestamp = { 0 }; + + if ((char *)arg == NULL) { + ERROR("Container id is empty"); + return NULL; + } + container_id = util_strdup_s((char *)arg); + + cont = containers_store_get(container_id); + if (cont == NULL) { + ERROR("Failed to get container info"); + goto out; + } + + if (get_now_time_stamp(&start_timestamp) == false) { + ERROR("Failed to monitor start time stamp"); + goto out; + } + probe_interval = timeout_with_default(cont->common_config->config->health_check->interval, + DEFAULT_PROBE_INTERVAL); + set_monitor_idle_status(cont->health_check); + while (true) { + switch (get_health_check_monitor_state(cont->health_check)) { + case MONITOR_STOP: + DEBUG("Stop healthcheck monitoring for container %s (received while idle)", + cont->common_config->id); + goto out; + /* fall-through */ + case MONITOR_INTERVAL: + if (do_monitor_interval(container_id, cont->health_check, &start_timestamp)) { + goto out; + } + break; + case MONITOR_IDLE: + /* fall-through */ + default: + if (do_monitor_default(probe_interval, cont->health_check, &start_timestamp, &last_timestamp)) { + goto out; + } + break; + } + } +out: + free(container_id); + container_id = NULL; + container_unref(cont); + return NULL; +} + +// Ensure the health-check monitor is running or not, depending on the current +// state of the container. +// Called from monitor.go, with c locked. +void update_health_monitor(const char *container_id) +{ + bool want_running = false; + container_t *cont = NULL; + defs_health *health = NULL; + health_probe_t probe; + + if (container_id == NULL) { + return; + } + cont = containers_store_get(container_id); + if (cont == NULL) { + ERROR("Failed to get container info"); + return; + } + + health = cont->state->state->health; + if (health == NULL) { + goto out; + } + probe = get_probe(cont); + want_running = cont->state->state->running && !cont->state->state->paused && probe != HEALTH_NONE; + + if (want_running) { + open_health_check_monitor(cont->health_check); + pthread_t monitor_tid = { 0 }; + if (pthread_create(&monitor_tid, NULL, health_check_monitor, (void *)container_id)) { + ERROR("Failed to create thread to monitor health check..."); + goto out; + } + if (pthread_detach(monitor_tid)) { + ERROR("Failed to detach the health check monitor thread"); + goto out; + } + } else { + close_health_check_monitor(cont); + } + +out: + container_unref(cont); +} + + +// Reset the health state for a newly-started, restarted or restored container. +// initHealthMonitor is called from monitor.go and we should never be running +// two instances at once. +// Note: Called with container locked. +void init_health_monitor(const char *id) +{ + container_t *cont = NULL; + + cont = containers_store_get(id); + if (cont == NULL) { + ERROR("Failed to get container info"); + return; + } + + if (cont->common_config->config->health_check == NULL || + cont->common_config->config->health_check->test == NULL) { + goto out; + } + + if (cont->health_check == NULL) { + cont->health_check = health_check_manager_new(); + if (cont->health_check == NULL) { + ERROR("Out of memory"); + goto out; + } + } + + // If no healthcheck is setup then don't init the monitor + if (get_probe(cont) == HEALTH_NONE) { + goto out; + } + // This is needed in case we're auto-restarting + stop_health_checks(cont->common_config->id); + if (cont->state == NULL || cont->state->state == NULL) { + goto out; + } + + if (cont->state->state->health != NULL) { + set_health_status(cont->state, HEALTH_STARTING); + cont->state->state->health->failing_streak = 0; + } else { + cont->state->state->health = util_common_calloc_s(sizeof(defs_health)); + if (cont->state->state->health == NULL) { + ERROR("out of memory"); + goto out; + } + set_health_status(cont->state, HEALTH_STARTING); + } + + if (container_to_disk(cont)) { + ERROR("Failed to save container \"%s\" to disk", id); + goto out; + } + + update_health_monitor(id); + +out: + container_unref(cont); + return; +} + diff --git a/src/services/execution/manager/health_check.h b/src/services/execution/manager/health_check.h new file mode 100644 index 0000000..e31be62 --- /dev/null +++ b/src/services/execution/manager/health_check.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: wujing + * Create: 2018-11-1 + * Description: provide health check definition + *********************************************************************************/ +#ifndef __LCRD_HEALTH_CHECK_H_ +#define __LCRD_HEALTH_CHECK_H_ + +#include "types_def.h" +#include "container_config_v2.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_OUTPUT_LEN 4096 +#define DEFAULT_PROBE_INTERVAL (30 * Time_Second) +#define DEFAULT_PROBE_TIMEOUT (30 * Time_Second) +#define DEFAULT_START_PERIOD (0 * Time_Second) +#define DEFAULT_PROBE_RETRIES 3 +#define MAX_LOG_ENTRIES 5 +#define EXIT_STATUS_HEALTHY 0 + +#define NO_HEALTH_CHECK "none" +#define HEALTH_STARTING "starting" +#define HEALTHY "healthy" +#define UNHEALTHY "unhealthy" + +typedef enum { + CMD, + CMD_SHELL, + HEALTH_NONE, + HEALTH_UNKNOWN +} health_probe_t; + +typedef enum { + MONITOR_IDLE = 0, + MONITOR_INTERVAL = 1, + MONITOR_STOP = 2 +} health_check_monitor_status_t; + +typedef struct health_check_manager { + pthread_mutex_t mutex; + bool init_mutex; + health_check_monitor_status_t monitor_status; +} health_check_manager_t; + +void init_health_monitor(const char *id); +void stop_health_checks(const char *container_id); +void update_health_monitor(const char *container_id); +void health_check_manager_free(health_check_manager_t *health_check); +int64_t timeout_with_default(int64_t configured_value, int64_t default_value); + +#ifdef __cplusplus +} +#endif + +#endif /* __LCRD_HEALTH_CHECK_H_ */ + + diff --git a/src/services/execution/manager/monitord.c b/src/services/execution/manager/monitord.c new file mode 100644 index 0000000..4d90493 --- /dev/null +++ b/src/services/execution/manager/monitord.c @@ -0,0 +1,229 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container monitord functions + ******************************************************************************/ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "monitord.h" +#include "mainloop.h" +#include "securec.h" +#include "lcrd_config.h" +#include "collector.h" +#include "utils.h" + +struct monitord_handler { + struct epoll_descr *pdescr; + int fifo_fd; + char *fifo_path; +}; + +/* lcrd monitor fifo name */ +char *lcrd_monitor_fifo_name(const char *rootpath) +{ + int ret; + char fifo_file_path[PATH_MAX] = { 0 }; + + if (rootpath == NULL) { + ERROR("Invalid parameter"); + goto err; + } + ret = sprintf_s(fifo_file_path, PATH_MAX, "%s/monitord_fifo", rootpath); + if (ret < 0 || ret >= PATH_MAX) { + ERROR("Create monitord fifo path failed"); + goto err; + } + return util_strdup_s(fifo_file_path); + +err: + return NULL; +} + +/* monitor event cb */ +static int monitor_event_cb(int fd, uint32_t events, void *cbdata, struct epoll_descr *descr) +{ + ssize_t len; + struct monitord_msg mmsg = { 0 }; + + /* first, read message from container monitor. */ + len = util_read_nointr(fd, &mmsg, sizeof(mmsg)); + if ((unsigned int)len != sizeof(mmsg)) { + ERROR("Invalid message"); + goto out; + } + + /* second, handle events*/ + events_handler(&mmsg); + if (malloc_trim(0) == 0) { + DEBUG("Malloc trim failed"); + } +out: + return 0; +} + +/* free monitord */ +static void free_monitord(struct monitord_handler *mhandler) +{ + if (mhandler->fifo_fd != -1) { + epoll_loop_del_handler(mhandler->pdescr, mhandler->fifo_fd); + close(mhandler->fifo_fd); + } + if (mhandler->fifo_path != NULL) { + if (unlink(mhandler->fifo_path) < 0) { + WARN("Failed to unlink fifo_path"); + } + free(mhandler->fifo_path); + mhandler->fifo_path = NULL; + } + + DEBUG("Clean monitord data..."); +} + +#define EVENTS_FIFO_SIZE (1024 * 1024) +/* monitord */ +static void *monitord(void *arg) +{ + int ret = 0; + char *statedir = NULL; + char *fifo_file_path = NULL; + struct monitord_handler mhandler = { 0 }; + struct flock mlock; + struct monitord_sync_data *msync = arg; + struct epoll_descr descr; + + mhandler.fifo_fd = -1; + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Set thread detach fail"); + goto pexit; + } + + prctl(PR_SET_NAME, "Monitord"); + + ret = epoll_loop_open(&descr); + if (ret != 0) { + ERROR("Failed to create epoll_loop"); + goto pexit; + } + mhandler.pdescr = &descr; + + statedir = conf_get_lcrd_statedir(); + if (statedir == NULL) { + CRIT("Can not get lcrd root path"); + goto err; + } + + /* 1. monitor fifo: to wait container monitor message */ + fifo_file_path = lcrd_monitor_fifo_name(statedir); + if (fifo_file_path == NULL) { + goto err; + } + mhandler.fifo_path = fifo_file_path; + + if (mknod(fifo_file_path, S_IFIFO | S_IRUSR | S_IWUSR, (dev_t)0) && errno != EEXIST) { + ERROR("Create monitord fifo file failed: %s", strerror(errno)); + goto err; + } + + mhandler.fifo_fd = util_open(fifo_file_path, O_RDWR | O_NONBLOCK | O_CLOEXEC, 0); + if (mhandler.fifo_fd == -1) { + ERROR("Open monitord fifo file failed: %s", strerror(errno)); + goto err; + } + + if (fcntl(mhandler.fifo_fd, F_SETPIPE_SZ, EVENTS_FIFO_SIZE) == -1) { + ERROR("Set events fifo buffer size failed: %s\n", strerror(errno)); + goto err; + } + + mlock.l_type = F_WRLCK; + mlock.l_whence = SEEK_SET; + mlock.l_start = 0; + mlock.l_len = 0; + if (fcntl(mhandler.fifo_fd, F_SETLK, &mlock)) { + INFO("Monitord already running on path: %s", fifo_file_path); + goto err; + } + + ret = epoll_loop_add_handler(&descr, mhandler.fifo_fd, monitor_event_cb, NULL); + if (ret != 0) { + ERROR("Failed to add handler for fifo"); + goto err; + } + + sem_post(msync->monitord_sem); + + /* loop forever except error occured */ + do { + ret = epoll_loop(&descr, -1); + } while (ret == 0); + + ERROR("Mainloop returned an error: %s", strerror(errno)); + goto err2; + +err: + *(msync->exit_code) = -1; + sem_post(msync->monitord_sem); +err2: + free(statedir); + free_monitord(&mhandler); + epoll_loop_close(&descr); + +pexit: + return NULL; +} + +/* new monitord */ +int new_monitord(struct monitord_sync_data *msync) +{ + int ret = 0; + char *statedir = NULL; + pthread_t monitord_thread; + + if (msync == NULL || msync->monitord_sem == NULL) { + ERROR("Monitord sem is NULL"); + ret = -1; + goto out; + } + + statedir = conf_get_lcrd_statedir(); + if (statedir == NULL) { + ERROR("LCRD root path is NULL"); + ret = -1; + goto out; + } + + if (setenv("LCRD_MONITORD_PATH", statedir, 1)) { + ERROR("Setenv monitord path failed"); + ret = -1; + goto out; + } + + INFO("Starting monitord..."); + if (pthread_create(&monitord_thread, NULL, monitord, msync) != 0) { + ERROR("Create monitord thread failed"); + ret = -1; + } + +out: + free(statedir); + return ret; +} diff --git a/src/services/execution/manager/monitord.h b/src/services/execution/manager/monitord.h new file mode 100644 index 0000000..d230ff7 --- /dev/null +++ b/src/services/execution/manager/monitord.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide monitord definition + ******************************************************************************/ +#ifndef __LCRD_MONITORD_H +#define __LCRD_MONITORD_H +#include +#include +#include +#include "engine.h" + +typedef enum { + monitord_msg_state, + monitord_msg_priority, + monitord_msg_exit_code +} msg_type_t; + +struct monitord_msg { + msg_type_t type; + char name[NAME_MAX + 1]; + int value; + int exit_code; + int pid; +}; + +struct monitord_sync_data { + sem_t *monitord_sem; + int *exit_code; +}; + +char *lcrd_monitor_fifo_name(const char *rootpath); + +int connect_monitord(const char *rootpath); + +int read_monitord_message_timeout(int fd, struct monitord_msg *msg, int timeout); + +int new_monitord(struct monitord_sync_data *msync); + +#endif diff --git a/src/services/execution/manager/restartmanager.c b/src/services/execution/manager/restartmanager.c new file mode 100644 index 0000000..f05ea59 --- /dev/null +++ b/src/services/execution/manager/restartmanager.c @@ -0,0 +1,465 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container restart manager functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "log.h" +#include "lcrd_config.h" +#include "restartmanager.h" +#include "utils.h" +#include "securec.h" +#include "containers_store.h" +#include "execution.h" +#include "containers_gc.h" + +#define backoffMultipulier 2U +// unit nanos +#define defaultTimeout (100LL * Time_Milli) +#define maxRestartTimeout Time_Minute + +struct restart_args { + char *id; + uint64_t timeout; + int exit_code; +}; + +/* free restart args */ +static void free_restart_args(struct restart_args *args) +{ + if (args == NULL) { + return; + } + if (args->id != NULL) { + free(args->id); + args->id = NULL; + } + free(args); +} + +/* container restart */ +static void *container_restart(void *args) +{ + int ret = 0; + struct restart_args *arg = args; + char *id = arg->id; + uint64_t timeout = arg->timeout; + int exit_code = arg->exit_code; + container_t *cont = NULL; + const char *console_fifos[3] = { NULL, NULL, NULL }; + restart_manager_t *rm = NULL; + + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Set thread detach fail"); + goto out; + } + + cont = containers_store_get(id); + if (cont == NULL) { + INFO("Container '%s' already removed", id); + goto out; + } + + if (gc_is_gc_progress(id)) { + ERROR("Cannot restart container %s in garbage collector progress.", id); + goto set_stopped; + } + + container_lock(cont); + rm = get_restart_manager(cont); + container_unlock(cont); + if (rm == NULL) { + ERROR("Failed to get restart manager for container '%s'", id); + goto set_stopped; + } + + ret = restart_manager_wait_cancel(rm, timeout); + if (ret == 0) { + INFO("Canceled to restart container '%s' cased %d", id, ret); + goto set_stopped; + } + + (void)start_container(cont, console_fifos, false); + goto out; + +set_stopped: + container_lock(cont); + state_set_stopped(cont->state, exit_code); + container_wait_stop_cond_broadcast(cont); + container_unlock(cont); + +out: + container_unref(cont); + restart_manager_unref(rm); + free_restart_args(arg); + DAEMON_CLEAR_ERRMSG(); + return NULL; +} + +/* container restart in thread */ +int container_restart_in_thread(const char *id, uint64_t timeout, int exit_code) +{ + int ret = -1; + pthread_t td; + struct restart_args *arg = NULL; + + if (id == NULL) { + ERROR("Invalid input arguments"); + goto error; + } + + arg = util_common_calloc_s(sizeof(struct restart_args)); + if (arg == NULL) { + ERROR("Out of memory"); + goto error; + } + arg->id = util_strdup_s(id); + arg->timeout = timeout; + arg->exit_code = exit_code; + + ret = pthread_create(&td, NULL, container_restart, arg); + if (ret != 0) { + CRIT("Thread create failed"); + goto error; + } + + return 0; +error: + free_restart_args(arg); + return -1; +} + +/* restart manager lock */ +static void restart_manager_lock(restart_manager_t *rm) +{ + if (pthread_mutex_lock(&rm->mutex)) { + ERROR("Failed to lock restart manager"); + } +} + +/* restart manager unlock */ +static void restart_manager_unlock(restart_manager_t *rm) +{ + if (pthread_mutex_unlock(&rm->mutex)) { + ERROR("Failed to unlock restart manager"); + } +} + +/* restart manager wait cancel cond broadcast */ +static void restart_manager_wait_cancel_cond_broadcast(restart_manager_t *rm) +{ + if (pthread_cond_broadcast(&rm->wait_cancel_con)) { + ERROR("Failed to broadcast wait cancel condition container"); + } +} + +/* restart manager wait cancel cond wait */ +static int restart_manager_wait_cancel_cond_wait(restart_manager_t *rm, uint64_t timeout) +{ + time_t sec = 0; + long nsec = 0; + struct timespec ts; + + sec = (time_t)(timeout / Time_Second); + nsec = (long)(timeout - (uint64_t)sec * Time_Second); + + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) { + ERROR("Failed to get real time"); + return -1; + } + + nsec += ts.tv_nsec; + + ts.tv_sec += sec + (time_t)nsec / Time_Second; + ts.tv_nsec = nsec % Time_Second; + + return pthread_cond_timedwait(&rm->wait_cancel_con, &rm->mutex, &ts); +} + +/* restart manager wait cancel */ +int restart_manager_wait_cancel(restart_manager_t *rm, uint64_t timeout) +{ + int ret = 0; + + if (rm == NULL) { + return -1; + } + + restart_manager_lock(rm); + + if (rm->canceled) { + goto unlock; + } + + ret = restart_manager_wait_cancel_cond_wait(rm, timeout); +unlock: + restart_manager_unlock(rm); + return ret; +} + +/* restart policy free */ +void restart_policy_free(host_config_restart_policy *policy) +{ + if (policy == NULL) { + return; + } + + free(policy->name); + policy->name = NULL; + + free(policy); +} + +/* restart manager refinc */ +void restart_manager_refinc(restart_manager_t *rm) +{ + if (rm == NULL) { + return; + } + atomic_int_inc(&rm->refcnt); +} + +/* restart manager unref */ +void restart_manager_unref(restart_manager_t *rm) +{ + bool is_zero = false; + + if (rm == NULL) { + return; + } + + is_zero = atomic_int_dec_test(&rm->refcnt); + if (!is_zero) { + return; + } + + restart_manager_free(rm); +} + +/* restart manager free */ +void restart_manager_free(restart_manager_t *rm) +{ + if (rm == NULL) { + return; + } + + restart_policy_free(rm->policy); + rm->policy = NULL; + + if (rm->init_wait_cancel_con) { + pthread_cond_destroy(&rm->wait_cancel_con); + } + if (rm->init_mutex) { + pthread_mutex_destroy(&rm->mutex); + } + free(rm); +} + +/* restart manager new */ +restart_manager_t *restart_manager_new(const host_config_restart_policy *policy, int failure_count) +{ + int ret; + restart_manager_t *rm = NULL; + + rm = util_common_calloc_s(sizeof(restart_manager_t)); + if (rm == NULL) { + ERROR("Out of memory"); + return NULL; + } + + ret = pthread_mutex_init(&rm->mutex, NULL); + if (ret != 0) { + ERROR("Failed to init mutex of restart manager"); + goto cleanup; + } + rm->init_mutex = true; + + ret = pthread_cond_init(&rm->wait_cancel_con, NULL); + if (ret != 0) { + ERROR("Failed to init wait cancel condition of restart manager"); + goto cleanup; + } + rm->init_wait_cancel_con = true; + + atomic_int_set(&rm->refcnt, 1); + rm->policy = util_common_calloc_s(sizeof(host_config_restart_policy)); + if (rm->policy == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + + if (policy != NULL) { + rm->policy->name = util_strdup_s(policy->name); + rm->policy->maximum_retry_count = policy->maximum_retry_count; + } + + rm->failure_count = failure_count; + + return rm; +cleanup: + restart_manager_free(rm); + return NULL; +} + +/* restart manager set policy */ +int restart_manager_set_policy(restart_manager_t *rm, const host_config_restart_policy *policy) +{ + host_config_restart_policy *newpolicy = NULL; + + if (rm == NULL || policy == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + newpolicy = util_common_calloc_s(sizeof(host_config_restart_policy)); + if (newpolicy == NULL) { + ERROR("Out of memory"); + return -1; + } + newpolicy->name = util_strdup_s(policy->name); + newpolicy->maximum_retry_count = policy->maximum_retry_count; + + restart_manager_lock(rm); + restart_policy_free(rm->policy); + rm->policy = newpolicy; + restart_manager_unlock(rm); + + return 0; +} + +static void restart_manager_set_items(restart_manager_t *rm, uint32_t exit_code, int64_t exec_duration) +{ + if (exit_code != 0) { + rm->failure_count++; + } else { + rm->failure_count = 0; + } + + // if the container ran for more than 10s, regardless of status and policy reset the + // the timeout back to the default. + if (exec_duration >= 10) { + rm->timeout = 0; + } + + if (rm->timeout == 0) { + rm->timeout = defaultTimeout; + } else if (rm->timeout < maxRestartTimeout) { + rm->timeout *= backoffMultipulier; + } + if (rm->timeout > maxRestartTimeout) { + rm->timeout = maxRestartTimeout; + } + + return; +} + +static bool should_be_restart(const restart_manager_t *rm, uint32_t exit_code, bool has_been_manually_stopped) +{ + bool restart = false; + + if (strcmp(rm->policy->name, "always") == 0) { + restart = true; + } else if (strcmp(rm->policy->name, "unless-stopped") == 0 && !has_been_manually_stopped) { + restart = true; + } else if (strcmp(rm->policy->name, "on-failure") == 0) { + // the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count + if (rm->policy->maximum_retry_count == 0 || + rm->failure_count <= rm->policy->maximum_retry_count) { + restart = (exit_code != 0); + } + } else if (strcmp(rm->policy->name, "on-reboot") == 0) { + restart = (exit_code == 129); + } + + return restart; +} + +/* restart manager should restart */ +bool restart_manager_should_restart(const char *id, uint32_t exit_code, bool has_been_manually_stopped, + int64_t exec_duration, uint64_t *timeout) +{ + bool restart = false; + restart_manager_t *rm = NULL; + container_t *cont = NULL; + + if (id == NULL) { + return false; + } + + cont = containers_store_get(id); + if (cont == NULL) { + ERROR("No such container:%s", id); + restart = false; + goto out; + } + + rm = get_restart_manager(cont); + if (rm == NULL) { + ERROR("Failed to get restart manager"); + restart = false; + goto unref; + } + + if (rm->policy == NULL || rm->policy->name == NULL) { + restart = false; + goto unref; + } + + restart_manager_lock(rm); + + if (rm->canceled) { + INFO("Restart canceled"); + restart = false; + goto unlock; + } + + restart_manager_set_items(rm, exit_code, exec_duration); + + restart = should_be_restart(rm, exit_code, has_been_manually_stopped); + if (restart) { + *timeout = (uint64_t)rm->timeout; + } + +unlock: + restart_manager_unlock(rm); +unref: + restart_manager_unref(rm); + container_unref(cont); +out: + return restart; +} + +/* restart manager cancel */ +int restart_manager_cancel(restart_manager_t *rm) +{ + if (rm == NULL) { + ERROR("Invalid input arguments"); + return -1; + } + + // need atomic lock ? + restart_manager_lock(rm); + rm->canceled = true; + restart_manager_wait_cancel_cond_broadcast(rm); + restart_manager_unlock(rm); + return 0; +} diff --git a/src/services/execution/manager/restartmanager.h b/src/services/execution/manager/restartmanager.h new file mode 100644 index 0000000..866c99a --- /dev/null +++ b/src/services/execution/manager/restartmanager.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container restart manager definition + ******************************************************************************/ +#ifndef __RESTARTMANAGER_H +#define __RESTARTMANAGER_H + +#include +#include +#include "engine.h" +#include "host_config.h" + +struct restart_policy { + char *name; + uint64_t max_retry_count; +}; + +typedef struct _restart_manager_t { + pthread_mutex_t mutex; + bool init_mutex; + pthread_cond_t wait_cancel_con; + bool init_wait_cancel_con; + uint64_t refcnt; + host_config_restart_policy *policy; + int failure_count; + int64_t timeout; + bool active; + bool canceled; +} restart_manager_t; + +void restart_policy_free(host_config_restart_policy *policy); + +restart_manager_t *restart_manager_new(const host_config_restart_policy *policy, int failure_count); + +void restart_manager_refinc(restart_manager_t *rm); + +void restart_manager_unref(restart_manager_t *rm); + +void restart_manager_free(restart_manager_t *rm); + +int restart_manager_set_policy(restart_manager_t *rm, const host_config_restart_policy *policy); + +bool restart_manager_should_restart(const char *id, uint32_t exit_code, bool has_been_manually_stopped, + int64_t exec_duration, uint64_t *timeout); + +int restart_manager_cancel(restart_manager_t *rm); + +int restart_manager_wait_cancel(restart_manager_t *rm, uint64_t timeout); + +int container_restart_in_thread(const char *id, uint64_t timeout, int exit_code); + + +#endif /* __RESTARTMANAGER_H */ diff --git a/src/services/execution/manager/restore.c b/src/services/execution/manager/restore.c new file mode 100644 index 0000000..53ebd07 --- /dev/null +++ b/src/services/execution/manager/restore.c @@ -0,0 +1,726 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container list callback function definition + ********************************************************************************/ + +#include +#include +#include +#include +#include + +#include "lcrd_config.h" +#include "securec.h" +#include "log.h" +#include "restore.h" +#include "containers_store.h" +#include "supervisor.h" +#include "containers_gc.h" +#include "container_unix.h" +#include "error.h" +#include "image.h" + +#ifdef ENABLE_OCI_IMAGE +#include "oci_image.h" +#include "oci_images_store.h" +#endif + +#include "execution.h" + +/* restore supervisor */ +static int restore_supervisor(const char *id, const char *runtime, const char *statepath) +{ + int ret = 0; + int nret = 0; + int exit_fifo_fd = -1; + char container_state[PATH_MAX] = { 0 }; + char pidfile[PATH_MAX] = { 0 }; + char *exit_fifo = NULL; + container_pid_t *pid_info = NULL; + + nret = sprintf_s(container_state, sizeof(container_state), "%s/%s", statepath, id); + if (nret < 0 || (unsigned int)nret >= sizeof(container_state)) { + ERROR("Failed to sprintf container state %s/%s", statepath, id); + ret = -1; + goto out; + } + + exit_fifo = exit_fifo_name(container_state); + if (exit_fifo == NULL) { + ERROR("Failed to get exit fifo name %s/%s", statepath, id); + ret = -1; + goto out; + } + + exit_fifo_fd = exit_fifo_open(exit_fifo); + if (exit_fifo_fd < 0) { + ERROR("Failed to open exit FIFO %s", exit_fifo); + ret = -1; + goto out; + } + + nret = sprintf_s(pidfile, sizeof(pidfile), "%s/pid.file", container_state); + if (nret < 0 || (unsigned int)nret >= sizeof(pidfile)) { + close(exit_fifo_fd); + ERROR("Failed to sprintf pidfile"); + ret = -1; + goto out; + } + + pid_info = container_read_pidfile(pidfile); + if (pid_info == NULL) { + close(exit_fifo_fd); + ERROR("Failed to get started container's pid info"); + ret = -1; + goto out; + } + + if (supervisor_add_exit_monitor(exit_fifo_fd, pid_info, id, runtime)) { + ERROR("Failed to add exit monitor to supervisor"); + ret = -1; + goto out; + } + +out: + free(exit_fifo); + free(pid_info); + + return ret; +} + +static container_pid_t *container_read_proc(uint32_t pid) +{ + container_pid_t *pid_info = NULL; + proc_t *proc_info = NULL; + + if (pid == 0) { + goto out; + } + + proc_info = util_get_process_proc_info((pid_t)pid); + if (proc_info == NULL) { + goto out; + } + + pid_info = util_common_calloc_s(sizeof(container_pid_t)); + if (pid_info == NULL) { + goto out; + } + + pid_info->pid = proc_info->pid; + pid_info->start_time = proc_info->start_time; + +out: + free(proc_info); + return pid_info; +} + +/* post stopped container to gc */ +static int post_stopped_container_to_gc(const char *id, const char *runtime, const char *statepath, uint32_t pid) +{ + int ret = 0; + int nret = 0; + char container_state[PATH_MAX] = { 0 }; + char pidfile[PATH_MAX] = { 0 }; + container_pid_t *pid_info = NULL; + + nret = sprintf_s(container_state, sizeof(container_state), "%s/%s", statepath, id); + if (nret < 0 || (unsigned int)nret >= sizeof(container_state)) { + ERROR("Failed to sprintf container state %s/%s", statepath, id); + ret = -1; + goto out; + } + + nret = sprintf_s(pidfile, sizeof(pidfile), "%s/pid.file", container_state); + if (nret < 0 || (unsigned int)nret >= sizeof(pidfile)) { + ERROR("Failed to sprintf pidfile"); + ret = -1; + goto out; + } + + pid_info = container_read_pidfile(pidfile); + if (pid_info == NULL) { + WARN("Failed to get started container's pid info, try to read proc filesystem"); + pid_info = container_read_proc(pid); + if (pid_info == NULL) { + ERROR("Failed to get started container's pid info"); + ret = -1; + goto out; + } + } + + if (gc_add_container(id, runtime, pid_info)) { + ERROR("Failed to post container %s to garbage collector", id); + ret = -1; + goto out; + } + +out: + free(pid_info); + return ret; +} + +static container_pid_t *load_running_container_pid_info(const container_t *cont) +{ + int nret = 0; + const char *id = cont->common_config->id; + char pidfile[PATH_MAX] = { 0 }; + char container_state[PATH_MAX] = { 0 }; + container_pid_t *pid_info = NULL; + + nret = sprintf_s(container_state, sizeof(container_state), "%s/%s", cont->state_path, id); + if (nret < 0 || (unsigned int)nret >= sizeof(container_state)) { + ERROR("Failed to sprintf container_state for container %s", id); + goto out; + } + + nret = sprintf_s(pidfile, sizeof(pidfile), "%s/pid.file", container_state); + if (nret < 0 || (unsigned int)nret >= sizeof(pidfile)) { + ERROR("Failed to sprintf pidfile"); + goto out; + } + + pid_info = container_read_pidfile(pidfile); + if (pid_info == NULL) { + goto out; + } + +out: + return pid_info; +} + +#ifdef ENABLE_OCI_IMAGE +static void post_nonexist_image_containers(const container_t *cont, Container_Status status, + const struct engine_container_summary_info *info) +{ + int nret; + const char *id = cont->common_config->id; + + if (info->status == ENGINE_CONTAINER_STATUS_STOPPED) { + if (status != CONTAINER_STATUS_STOPPED && \ + status != CONTAINER_STATUS_CREATED) { + nret = post_stopped_container_to_gc(id, cont->runtime, cont->state_path, 0); + if (nret != 0) { + ERROR("Failed to post container %s to garbage" + "collector, that may lost some resources" + "used with container!", id); + } + state_set_stopped(cont->state, 255); + } + } else if (info->status == ENGINE_CONTAINER_STATUS_RUNNING) { + nret = post_stopped_container_to_gc(id, cont->runtime, cont->state_path, info->pid); + if (nret != 0) { + ERROR("Failed to post container %s to garbage" + "collector, that may lost some resources" + "used with container!", id); + } + state_set_stopped(cont->state, 255); + } else { + ERROR("Container %s get invalid status %d", id, info->status); + } + + return; +} + +static int check_container_image_exist(const container_t *cont) +{ + int ret = 0; + char *tmp = NULL; + const char *id = cont->common_config->id; + const char *image_name = cont->common_config->image; + const char *image_type = cont->common_config->image_type; + oci_image_t *image = NULL; + + if (image_type == NULL || image_name == NULL) { + ERROR("Failed to get image type for container %s", id); + ret = -1; + goto out; + } + + /* only check exist for oci image */ + if (strcmp(image_type, IMAGE_TYPE_OCI) == 0) { + tmp = oci_resolve_image_name(image_name); + if (tmp == NULL) { + ERROR("Failed to resolve image %s", image_name); + ret = -1; + goto out; + } + image = oci_images_store_get(tmp); + if (image == NULL) { + WARN("Image %s not exist", tmp); + ret = -1; + goto out; + } + oci_image_unref(image); + } + +out: + free(tmp); + return ret; +} +#endif + +static void try_to_set_container_running(Container_Status status, container_t *cont, + const container_pid_t *pid_info) +{ + int pid = 0; + + pid = state_get_pid(cont->state); + if (status != CONTAINER_STATUS_RUNNING || pid != pid_info->pid) { + state_set_running(cont->state, pid_info, true); + } +} + +static int restore_check_id_valid(const char *id, const struct engine_container_summary_info *info, + size_t container_num) +{ + size_t i = 0; + + if (id == NULL) { + ERROR("Cannot get container id from config v2"); + return -1; + } + + for (i = 0; i < container_num; i++) { + if (strcmp(id, info[i].id) == 0) { + break; + } + } + + if (i >= container_num) { + ERROR("Container %s is not in runtime container array", id); + return -1; + } + + return (int)i; +} + +static int restore_stopped_container(Container_Status status, const container_t *cont, bool *need_save) +{ + const char *id = cont->common_config->id; + + if (status != CONTAINER_STATUS_STOPPED && \ + status != CONTAINER_STATUS_CREATED) { + int nret = post_stopped_container_to_gc(id, cont->runtime, cont->state_path, 0); + if (nret != 0) { + ERROR("Failed to post container %s to garbage" + "collector, that may lost some resources" + "used with container!", id); + } + state_set_stopped(cont->state, 255); + *need_save = true; + } + + return 0; +} + +static int restore_running_container(Container_Status status, container_t *cont, + const struct engine_container_summary_info *info) +{ + int ret = 0; + const char *id = cont->common_config->id; + container_pid_t *pid_info = NULL; + + pid_info = load_running_container_pid_info(cont); + if (pid_info == NULL) { + ERROR("Failed to restore container:%s due to unable to read container pid info", id); + int nret = post_stopped_container_to_gc(id, cont->runtime, cont->state_path, info->pid); + if (nret != 0) { + ERROR("Failed to post container %s to garbage" + "collector, that may lost some resources" + "used with container!", id); + } + ret = -1; + goto out; + } else { + try_to_set_container_running(status, cont, pid_info); + } + container_reset_manually_stopped(cont); + +out: + free(pid_info); + return ret; +} + +/* restore state */ +static int restore_state(container_t *cont, const struct engine_container_summary_info *info, size_t container_num) +{ + int ret = 0; + int c_index = 0; + bool need_save = false; + const char *id = cont->common_config->id; + Container_Status status = CONTAINER_STATUS_UNKNOWN; + + c_index = restore_check_id_valid(id, info, container_num); + if (c_index < 0) { + ret = -1; + goto out; + } + + status = state_get_status(cont->state); + (void)container_exit_on_next(cont); /* cancel restart policy */ + +#ifdef ENABLE_OCI_IMAGE + if (check_container_image_exist(cont) != 0) { + ERROR("Failed to restore container:%s due to image not exist", id); + post_nonexist_image_containers(cont, status, &info[c_index]); + ret = -1; + goto out; + } +#endif + + if (info[c_index].status == ENGINE_CONTAINER_STATUS_STOPPED) { + ret = restore_stopped_container(status, cont, &need_save); + if (ret != 0) { + goto out; + } + } else if (info[c_index].status == ENGINE_CONTAINER_STATUS_RUNNING) { + ret = restore_running_container(status, cont, &info[c_index]); + if (ret != 0) { + goto out; + } + } else { + ERROR("Container %s get invalid status %d", id, info[c_index].status); + } + + if (is_removal_in_progress(cont->state)) { + state_reset_removal_in_progress(cont->state); + need_save = true; + } + +out: + if (need_save && container_to_disk(cont) != 0) { + ERROR("Failed to re-save container \"%s\" to disk", id); + ret = -1; + } + return ret; +} + +/* remove invalid container */ +static int remove_invalid_container(const container_t *cont, const char *runtime, const char *root, const char *state, + const char *id) +{ + int ret = 0; + char container_root[PATH_MAX] = { 0x00 }; + char container_state[PATH_MAX] = { 0x00 }; + + ret = sprintf_s(container_state, sizeof(container_state), "%s/%s", state, id); + if (ret < 0 || (unsigned int)ret >= sizeof(container_state)) { + ERROR("Failed to sprintf container state %s/%s", state, id); + ret = -1; + goto out; + } + ret = util_recursive_rmdir(container_state, 0); + if (ret != 0) { + ERROR("Failed to delete container's state directory %s", container_state); + ret = -1; + goto out; + } + + ret = sprintf_s(container_root, sizeof(container_root), "%s/%s", root, id); + if (ret < 0 || (unsigned int)ret >= sizeof(container_root)) { + ERROR("Failed to sprintf invalid root directory %s/%s", root, id); + ret = -1; + goto out; + } + + if (cont != NULL && im_remove_container_rootfs(cont->common_config->image_type, id)) { + ERROR("Failed to remove rootfs for container %s", id); + ret = -1; + goto out; + } + + ret = util_recursive_rmdir(container_root, 0); + if (ret != 0) { + ERROR("Failed to delete container's state directory %s", container_state); + ret = -1; + goto out; + } +out: + return ret; +} + +static void restored_restart_container(container_t *cont) +{ + char *id = NULL; + char *started_at = NULL; + uint64_t timeout = 0; + + id = cont->common_config->id; + + started_at = state_get_started_at(cont->state); + if (restart_manager_should_restart(id, state_get_exitcode(cont->state), + cont->common_config->has_been_manually_stopped, + time_seconds_since(started_at), + &timeout)) { + cont->common_config->restart_count++; + INFO("Restart container %s after 5 second", id); + (void)container_restart_in_thread(id, 5ULL * Time_Second, (int)state_get_exitcode(cont->state)); + } + free(started_at); +} + +/* handle restored container */ +static void handle_restored_container() +{ + int ret = 0; + size_t i = 0; + size_t container_num = 0; + char *id = NULL; + container_t **conts = NULL; + container_t *cont = NULL; + + ret = containers_store_list(&conts, &container_num); + if (ret != 0) { + ERROR("query all containers info failed"); + return; + } + + for (i = 0; i < container_num; i++) { + cont = conts[i]; + container_lock(cont); + + (void)reset_restart_manager(cont, false); + + id = cont->common_config->id; + + if (is_running(cont->state)) { + if (restore_supervisor(id, cont->runtime, cont->state_path)) { + ERROR("Failed to restore %s supervisor", id); + } + init_health_monitor(id); + } else { + if (cont->hostconfig != NULL && cont->hostconfig->auto_remove_bak) { + (void)set_container_to_removal(cont); + container_unlock(cont); + (void)cleanup_container(cont, true); + container_lock(cont); + } else { + restored_restart_container(cont); + } + } + + container_unlock(cont); + container_unref(cont); + } + + free(conts); + return; +} + +/* scan dir to add store */ +static void scan_dir_to_add_store(const char *runtime, const char *rootpath, const char *statepath, + const size_t subdir_num, const char **subdir, const size_t container_num, + const struct engine_container_summary_info *info) +{ + size_t i = 0; + container_t *cont = NULL; + + for (i = 0; i < subdir_num; i++) { + cont = NULL; + bool aret = false; + bool index_flag = false; + cont = container_load(runtime, rootpath, statepath, subdir[i]); + if (cont == NULL) { + ERROR("Failed to load subdir:%s", subdir[i]); + goto error_load; + } + + if (restore_state(cont, info, container_num)) { + WARN("Failed to restore container %s state", subdir[i]); + goto error_load; + } + + index_flag = name_index_add(cont->common_config->name, cont->common_config->id); + if (!index_flag) { + ERROR("Failed add %s into name indexs", subdir[i]); + goto error_load; + } + aret = containers_store_add(cont->common_config->id, cont); + if (!aret) { + ERROR("Failed add container %s to store", subdir[i]); + goto error_load; + } + + continue; +error_load: + if (remove_invalid_container(cont, runtime, rootpath, statepath, subdir[i])) { + ERROR("Failed to delete subdir:%s", subdir[i]); + } + container_unref(cont); + + if (index_flag) { + name_index_remove(subdir[i]); + } + continue; + } +} + +/* query all containers info */ +static int query_all_containers_info(const char *runtime, struct engine_container_summary_info **container_summary, + size_t *container_num) +{ + int ret = 0; + int container_nums = 0; + char *engine_path = NULL; + struct engine_operation *engine_ops = NULL; + + if (runtime == NULL || container_summary == NULL || container_num == NULL) { + ERROR("invalid NULL param"); + return -1; + } + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_get_all_containers_info_op == NULL) { + ERROR("Failed to get list op of engine %s", runtime); + ret = -1; + goto out; + } + + engine_path = conf_get_routine_rootdir(runtime); + if (engine_path == NULL) { + ret = -1; + goto out; + } + container_nums = engine_ops->engine_get_all_containers_info_op(engine_path, container_summary); + if (container_nums < 0) { + ERROR("Engine %s get all containers info failed", runtime); + ret = -1; + goto out; + } + *container_num = (size_t)container_nums; + +out: + free(engine_path); + if (engine_ops != NULL) { + engine_ops->engine_clear_errmsg_op(); + } + + return ret; +} + +/* all containers info free */ +static void all_containers_info_free(const char *runtime, struct engine_container_summary_info *container_summary, + size_t container_num) +{ + struct engine_operation *engine_ops = NULL; + + if (container_summary == NULL || runtime == NULL) { + return; + } + + engine_ops = engines_get_handler(runtime); + if (engine_ops == NULL || engine_ops->engine_free_all_containers_info_op == NULL) { + ERROR("Failed to get free op of engine %s", runtime); + return; + } + + engine_ops->engine_free_all_containers_info_op(container_summary, (int)container_num); + + if (engine_ops->engine_clear_errmsg_op != NULL) { + engine_ops->engine_clear_errmsg_op(); + } + return; +} + +/* restore container by runtime */ +static int restore_container_by_runtime(const char *runtime) +{ + int ret = 0; + char *rootpath = NULL; + char *statepath = NULL; + size_t container_num = 0; + size_t subdir_num = 0; + char **subdir = NULL; + struct engine_container_summary_info *info = NULL; + + rootpath = conf_get_routine_rootdir(runtime); + if (rootpath == NULL) { + ERROR("Root path is NULL"); + ret = -1; + goto out; + } + + statepath = conf_get_routine_statedir(runtime); + if (statepath == NULL) { + ERROR("State path is NULL"); + ret = -1; + goto out; + } + + ret = util_list_all_subdir(rootpath, &subdir); + if (ret != 0) { + ERROR("Failed to read %s'subdirectory", rootpath); + ret = -1; + goto out; + } + subdir_num = util_array_len(subdir); + if (subdir_num == 0) { + goto out; + } + + ret = query_all_containers_info(runtime, &info, &container_num); + if (ret < 0) { + ERROR("query all containers info failed"); + ret = -1; + goto out; + } + + scan_dir_to_add_store(runtime, rootpath, statepath, subdir_num, (const char **)subdir, container_num, info); + +out: + all_containers_info_free(runtime, info, container_num); + free(rootpath); + free(statepath); + util_free_array(subdir); + return ret; +} + +/* containers restore */ +void containers_restore(void) +{ + int ret = 0; + size_t subdir_num = 0; + size_t i = 0; + char *engines_path = NULL; + char **subdir = NULL; + + engines_path = conf_get_engine_rootpath(); + if (engines_path == NULL) { + ERROR("Failed to get engines path"); + goto out; + } + + ret = util_list_all_subdir(engines_path, &subdir); + if (ret != 0) { + ERROR("Failed to list engines"); + goto out; + } + subdir_num = util_array_len(subdir); + + for (i = 0; i < subdir_num; i++) { + DEBUG("Restore the containers by runtime:%s", subdir[i]); + ret = restore_container_by_runtime(subdir[i]); + if (ret != 0) { + ERROR("Failed to restore containers by runtime:%s", subdir[i]); + } + } + + handle_restored_container(); + +out: + free(engines_path); + util_free_array(subdir); + return; +} + diff --git a/src/services/execution/manager/restore.h b/src/services/execution/manager/restore.h new file mode 100644 index 0000000..6546fb1 --- /dev/null +++ b/src/services/execution/manager/restore.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide restore definition + ******************************************************************************/ +#ifndef __LCRD_RESTORE_H +#define __LCRD_RESTORE_H + +#include +#include +#include + +#include "engine.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern void containers_restore(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/execution/manager/supervisor.c b/src/services/execution/manager/supervisor.c new file mode 100644 index 0000000..c71b05e --- /dev/null +++ b/src/services/execution/manager/supervisor.c @@ -0,0 +1,335 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container supervisor functions + ******************************************************************************/ +#define _GNU_SOURCE +#include +#include +#include + +#include "log.h" +#include "utils.h" +#include "supervisor.h" +#include "mainloop.h" +#include "securec.h" +#include "liblcrd.h" +#include "collector.h" +#include "execution.h" +#include "containers_gc.h" + +pthread_mutex_t g_supervisor_lock = PTHREAD_MUTEX_INITIALIZER; +struct epoll_descr g_supervisor_descr; + +struct supervisor_handler_data { + int fd; + int exit_code; + char *name; + char *runtime; + container_pid_t pid_info; +}; + +/* supervisor handler lock */ +static void supervisor_handler_lock() +{ + if (pthread_mutex_lock(&g_supervisor_lock) != 0) { + ERROR("Failed to lock supervisor lock"); + } +} + +/* supervisor handler unlock */ +static void supervisor_handler_unlock() +{ + if (pthread_mutex_unlock(&g_supervisor_lock) != 0) { + ERROR("Failed to lock supervisor lock"); + } +} + +#define EXIT_FIFO "exit_fifo" +/* exit fifo name */ +char *exit_fifo_name(const char *cont_state_path) +{ + int ret = 0; + char fifo_path[PATH_MAX] = { 0 }; + + if (cont_state_path == NULL) { + return NULL; + } + + ret = sprintf_s(fifo_path, sizeof(fifo_path), "%s/%s", cont_state_path, EXIT_FIFO); + if (ret < 0) { + ERROR("sprintf buffer failed"); + return NULL; + } + + return util_strdup_s(fifo_path); +} + +/* exit fifo create */ +char *exit_fifo_create(const char *cont_state_path) +{ + int ret = 0; + char fifo_path[PATH_MAX] = { 0 }; + + if (cont_state_path == NULL) { + return NULL; + } + + ret = sprintf_s(fifo_path, sizeof(fifo_path), "%s/%s", cont_state_path, EXIT_FIFO); + if (ret < 0) { + ERROR("sprintf buffer failed"); + return NULL; + } + + ret = mknod(fifo_path, S_IFIFO | S_IRUSR | S_IWUSR, (dev_t)0); + if (ret < 0 && errno != EEXIST) { + ERROR("Failed to mknod exit monitor fifo %s: %s.", fifo_path, strerror(errno)); + return NULL; + } + + return util_strdup_s(fifo_path); +} + +/* exit fifo open */ +int exit_fifo_open(const char *cont_exit_fifo) +{ + int ret = 0; + + if (cont_exit_fifo == NULL) { + return -1; + } + + if (!util_file_exists(cont_exit_fifo)) { + ERROR("Exit FIFO %s does not does not exist", cont_exit_fifo); + ret = -1; + goto out; + } + + ret = util_open(cont_exit_fifo, O_RDONLY | O_NONBLOCK, 0); + if (ret < 0) { + ERROR("Failed to open exit monitor FIFO %s: %s.", cont_exit_fifo, strerror(errno)); + ret = -1; + goto out; + } +out: + return ret; +} + +/* supervisor handler data free */ +static void supervisor_handler_data_free(struct supervisor_handler_data *data) +{ + if (data == NULL) { + return; + } + + free(data->name); + data->name = NULL; + + free(data->runtime); + data->runtime = NULL; + + if (data->fd >= 0) { + close(data->fd); + } + free(data); +} + +/* clean resources thread */ +static void *clean_resources_thread(void *arg) +{ + int ret = 0; + struct supervisor_handler_data *data = arg; + char *name = data->name; + char *runtime = data->runtime; + unsigned long long start_time = data->pid_info.start_time; + pid_t pid = data->pid_info.pid; + int retry_count = 0; + int max_retry = 10; + + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Set thread detach fail"); + } + + prctl(PR_SET_NAME, "Clean resource"); + +retry: + if (false == util_process_alive(pid, start_time)) { + ret = clean_container_resource(name, runtime, pid); + if (ret != 0) { + ERROR("Failed to clean resources of container %s", name); + } + } else { + ret = kill(pid, SIGKILL); + if (ret < 0 && errno != ESRCH) { + ERROR("Can not kill process (pid=%d) with SIGKILL for container %s", pid, name); + } + + if (retry_count < max_retry) { + usleep_nointerupt(100 * 1000); /* 100 millisecond */ + retry_count++; + goto retry; + } + + ret = gc_add_container(name, runtime, &data->pid_info); + if (ret != 0) { + ERROR("Failed to send container %s to garbage handler", name); + } + } + + (void)lcrd_monitor_send_event(name, STOPPED, (int)pid, data->exit_code); + + supervisor_handler_data_free(data); + + DAEMON_CLEAR_ERRMSG(); + return NULL; +} + +/* new clean resources thread */ +int new_clean_resources_thread(struct supervisor_handler_data *data) +{ + int ret = 0; + pthread_t clean_thread; + + if (pthread_create(&clean_thread, NULL, clean_resources_thread, data)) { + ERROR("Create clean resource thread failed"); + supervisor_handler_data_free(data); + ret = -1; + } + + return ret; +} + +/* supervisor exit cb */ +static int supervisor_exit_cb(int fd, uint32_t events, void *cbdata, + struct epoll_descr *descr) +{ + ssize_t r = 0; + int exit_code = 0; + struct supervisor_handler_data *data = cbdata; + char *name = data->name; + + r = util_read_nointr(fd, &exit_code, sizeof(int)); + if (r <= 0) { + exit_code = 137; + } + + data->exit_code = exit_code; + + INFO("The container %s 's monitor on fd %d has exited", name, fd); + supervisor_handler_lock(); + epoll_loop_del_handler(&g_supervisor_descr, fd); + supervisor_handler_unlock(); + + (void)new_clean_resources_thread(data); + + return 0; +} + +/* supervisor add exit monitor */ +int supervisor_add_exit_monitor(int fd, const container_pid_t *pid_info, const char *name, const char *runtime) +{ + int ret = 0; + struct supervisor_handler_data *data = NULL; + + if (fd < 0) { + ERROR("Invalid exit fifo fd"); + return -1; + } + + if (pid_info == NULL || name == NULL || runtime == NULL) { + ERROR("Invalid input arguments"); + close(fd); + return -1; + } + + data = util_common_calloc_s(sizeof(struct supervisor_handler_data)); + if (data == NULL) { + ERROR("Memory out"); + close(fd); + return -1; + } + + data->fd = fd; + data->name = util_strdup_s(name); + data->runtime = util_strdup_s(runtime); + data->pid_info.pid = pid_info->pid; + data->pid_info.start_time = pid_info->start_time; + data->pid_info.ppid = pid_info->ppid; + data->pid_info.pstart_time = pid_info->pstart_time; + + supervisor_handler_lock(); + ret = epoll_loop_add_handler(&g_supervisor_descr, fd, supervisor_exit_cb, data); + if (ret != 0) { + ERROR("Failed to add handler for exit fifo"); + goto err; + } + + goto out; + +err: + supervisor_handler_data_free(data); +out: + supervisor_handler_unlock(); + return ret; +} + +/* supervisor */ +static void *supervisor(void *arg) +{ + int ret = 0; + + ret = pthread_detach(pthread_self()); + if (ret != 0) { + CRIT("Set thread detach fail"); + goto pexit; + } + + prctl(PR_SET_NAME, "Supervisor"); + +restart: + ret = epoll_loop(&g_supervisor_descr, -1); + if (ret == 0) { + goto restart; + } + ERROR("Mainloop returned an error: %s", strerror(errno)); + + epoll_loop_close(&g_supervisor_descr); + +pexit: + DAEMON_CLEAR_ERRMSG(); + return NULL; +} + +/* new supervisor */ +int new_supervisor() +{ + int ret = 0; + pthread_t supervisor_thread; + + INFO("Starting supervisor..."); + + ret = epoll_loop_open(&g_supervisor_descr); + if (ret != 0) { + ERROR("Failed to create epoll_loop"); + ret = -1; + goto out; + } + + if (pthread_create(&supervisor_thread, NULL, supervisor, NULL) != 0) { + ERROR("Create supervisor thread failed"); + ret = -1; + } + +out: + return ret; +} diff --git a/src/services/execution/manager/supervisor.h b/src/services/execution/manager/supervisor.h new file mode 100644 index 0000000..ca91dd4 --- /dev/null +++ b/src/services/execution/manager/supervisor.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container supervisor definition + ******************************************************************************/ +#ifndef __LCRD_SUPERVISOR_H +#define __LCRD_SUPERVISOR_H +#include +#include +#include +#include "container_unix.h" + +extern char *exit_fifo_create(const char *cont_state_path); + +extern char *exit_fifo_name(const char *cont_state_path); + +extern int exit_fifo_open(const char *cont_exit_fifo); + +extern int supervisor_add_exit_monitor(int fd, const container_pid_t *pid_info, const char *name, + const char *runtime); + +extern int new_supervisor(); + +#endif diff --git a/src/services/execution/spec/CMakeLists.txt b/src/services/execution/spec/CMakeLists.txt new file mode 100644 index 0000000..0f7a38d --- /dev/null +++ b/src/services/execution/spec/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_spec_srcs) + +set(SPEC_SRCS + ${local_spec_srcs} + PARENT_SCOPE + ) diff --git a/src/services/execution/spec/specs.c b/src/services/execution/spec/specs.c new file mode 100644 index 0000000..02f967f --- /dev/null +++ b/src/services/execution/spec/specs.c @@ -0,0 +1,1816 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container specs functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "securec.h" +#include "log.h" +#include "specs.h" +#include "oci_runtime_spec.h" +#include "oci_runtime_hooks.h" +#include "docker_seccomp.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "utils.h" +#include "config.h" +#include "lcrd_config.h" +#include "namespace.h" +#include "specs_security.h" +#include "specs_mount.h" +#include "specs_extend.h" +#include "image.h" +#include "path.h" + +#ifndef CLONE_NEWUTS +#define CLONE_NEWUTS 0x04000000 +#endif + +#ifndef CLONE_NEWUSER +#define CLONE_NEWUSER 0x10000000 +#endif + +#ifndef CLONE_NEWNET +#define CLONE_NEWNET 0x40000000 +#endif + +#ifndef CLONE_NEWNS +#define CLONE_NEWNS 0x00020000 +#endif + +#ifndef CLONE_NEWPID +#define CLONE_NEWPID 0x20000000 +#endif + +#ifndef CLONE_NEWIPC +#define CLONE_NEWIPC 0x08000000 +#endif + +#ifndef CLONE_NEWCGROUP +#define CLONE_NEWCGROUP 0x02000000 +#endif + +#define OCICONFIGJSON "ociconfig.json" + +static int merge_annotations(oci_runtime_spec *oci_spec, container_custom_config *custom_conf) +{ + int ret = 0; + size_t i; + + if (custom_conf->annotations != NULL && custom_conf->annotations->len) { + if (oci_spec->annotations->len > LIST_SIZE_MAX - custom_conf->annotations->len) { + ERROR("Too many annotations to add, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many annotations to add, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto out; + } + for (i = 0; i < custom_conf->annotations->len; i++) { + ret = append_json_map_string_string(oci_spec->annotations, custom_conf->annotations->keys[i], + custom_conf->annotations->values[i]); + if (ret != 0) { + ERROR("Failed to append annotation:%s, value:%s", custom_conf->annotations->keys[i], + custom_conf->annotations->values[i]); + goto out; + } + } + } +out: + return ret; +} + +static int make_annotations_log_console(const oci_runtime_spec *oci_spec, const container_custom_config *custom_conf) +{ + int ret = 0; + int nret = 0; + char tmp_str[LCRD_NUMSTRLEN64] = {0}; + + if (custom_conf->log_config != NULL) { + if (custom_conf->log_config->log_file != NULL) { + if (append_json_map_string_string(oci_spec->annotations, CONTAINER_LOG_CONFIG_KEY_FILE, + custom_conf->log_config->log_file)) { + ERROR("append log console file failed"); + ret = -1; + goto out; + } + } + + nret = sprintf_s(tmp_str, sizeof(tmp_str), "%llu", + (unsigned long long)(custom_conf->log_config->log_file_rotate)); + if (nret < 0) { + ERROR("create rotate string failed"); + ret = -1; + goto out; + } + + if (append_json_map_string_string(oci_spec->annotations, CONTAINER_LOG_CONFIG_KEY_ROTATE, tmp_str)) { + ERROR("append log console file rotate failed"); + ret = -1; + goto out; + } + + if (custom_conf->log_config->log_file_size != NULL) { + if (append_json_map_string_string(oci_spec->annotations, CONTAINER_LOG_CONFIG_KEY_SIZE, + custom_conf->log_config->log_file_size)) { + ERROR("append log console file size failed"); + ret = -1; + goto out; + } + } + } + +out: + return ret; +} + +static int make_annotations_network_mode(const oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + if (host_spec->network_mode != NULL) { + if (append_json_map_string_string(oci_spec->annotations, "host.network.mode", host_spec->network_mode)) { + ERROR("append network mode failed"); + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int make_annotations_system_container(const oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + if (host_spec->system_container) { + if (append_json_map_string_string(oci_spec->annotations, "system.container", "true")) { + ERROR("Realloc annotations failed"); + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int make_annotations_cgroup_dir(const oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + char cleaned[PATH_MAX] = { 0 }; + char *default_cgroup_parent = NULL; + char *path = NULL; + + default_cgroup_parent = conf_get_lcrd_cgroup_parent(); + if (host_spec->cgroup_parent != NULL) { + path = host_spec->cgroup_parent; + } else if (default_cgroup_parent != NULL) { + path = default_cgroup_parent; + } + if (path == NULL) { + goto out; + } + if (cleanpath(path, cleaned, sizeof(cleaned)) == NULL) { + ERROR("Failed to clean path: %s", path); + ret = -1; + goto out; + } + if (append_json_map_string_string(oci_spec->annotations, "cgroup.dir", cleaned)) { + ERROR("Realloc annotations failed"); + ret = -1; + goto out; + } + +out: + free(default_cgroup_parent); + return ret; +} + +static int make_annotations_oom_score_adj(const oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + char tmp_str[LCRD_NUMSTRLEN64 + 1] = { 0 }; + + // oom_score_adj default value is 0, So there is no need to explicitly set this value + if (host_spec->oom_score_adj != 0) { + int nret = sprintf_s(tmp_str, sizeof(tmp_str), "%d", host_spec->oom_score_adj); + if (nret < 0) { + ERROR("create oom score adj string failed"); + ret = -1; + goto out; + } + if (append_json_map_string_string(oci_spec->annotations, "proc.oom_score_adj", tmp_str)) { + ERROR("append oom score adj which configure proc filesystem for the container failed "); + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int make_annotations_files_limit(const oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + char tmp_str[LCRD_NUMSTRLEN64 + 1] = { 0 }; + + // Not supported in oci runtime-spec, add 'files.limit' to annotations + if (host_spec->files_limit != 0) { + // need create new file limit item in annotations + int64_t filelimit = host_spec->files_limit; + int nret = sprintf_s(tmp_str, sizeof(tmp_str), "%lld", (long long)filelimit); + if (nret < 0) { + ERROR("create files limit string failed"); + ret = -1; + goto out; + } + + if (append_json_map_string_string(oci_spec->annotations, "files.limit", tmp_str)) { + ERROR("append files limit failed"); + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int make_sure_oci_spec_annotations(oci_runtime_spec *oci_spec) +{ + if (oci_spec->annotations == NULL) { + oci_spec->annotations = util_common_calloc_s(sizeof(json_map_string_string)); + if (oci_spec->annotations == NULL) { + return -1; + } + } + return 0; +} + +static int make_annotations(oci_runtime_spec *oci_spec, container_custom_config *custom_conf, host_config *host_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_annotations(oci_spec); + if (ret < 0) { + goto out; + } + + ret = make_annotations_network_mode(oci_spec, host_spec); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = make_annotations_system_container(oci_spec, host_spec); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = make_annotations_cgroup_dir(oci_spec, host_spec); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = make_annotations_oom_score_adj(oci_spec, host_spec); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = make_annotations_files_limit(oci_spec, host_spec); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = make_annotations_log_console(oci_spec, custom_conf); + if (ret != 0) { + ret = -1; + goto out; + } + + if (merge_annotations(oci_spec, custom_conf)) { + ret = -1; + goto out; + } + +out: + return ret; +} + +/* default_spec returns default oci spec used by lcrd. */ +oci_runtime_spec *default_spec() +{ + const char *oci_file = OCICONFIG_PATH; + oci_runtime_spec *oci_spec = NULL; + parser_error err = NULL; + + /* parse the input oci file */ + oci_spec = oci_runtime_spec_parse_file(oci_file, NULL, &err); + if (oci_spec == NULL) { + ERROR("Failed to parse OCI specification file \"%s\", error message: %s", oci_file, err); + lcrd_set_error_message("Can not read the default /etc/default/lcrd/config.json file: %s", err); + goto out; + } + +out: + free(err); + return oci_spec; +} + +static int make_sure_oci_spec_root(oci_runtime_spec *oci_spec) +{ + if (oci_spec->root == NULL) { + oci_spec->root = util_common_calloc_s(sizeof(oci_runtime_spec_root)); + if (oci_spec->root == NULL) { + return -1; + } + } + return 0; +} + +static int merge_root(oci_runtime_spec *oci_spec, const char *rootfs, const host_config *host_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_root(oci_spec); + if (ret < 0) { + goto out; + } + + // fill root path properties + if (rootfs != NULL) { + free(oci_spec->root->path); + oci_spec->root->path = util_strdup_s(rootfs); + } + if (host_spec->readonly_rootfs) { + oci_spec->root->readonly = host_spec->readonly_rootfs; + } + +out: + return ret; +} + +static int merge_blkio_weight(oci_runtime_spec *oci_spec, uint16_t blkio_weight) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_blkio(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->block_io->weight = blkio_weight; + +out: + return ret; +} + +static int make_sure_oci_spec_linux_resources_cpu(oci_runtime_spec *oci_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->linux->resources->cpu == NULL) { + oci_spec->linux->resources->cpu = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources_cpu)); + if (oci_spec->linux->resources->cpu == NULL) { + return -1; + } + } + return 0; +} + +static int merge_cpu_shares(oci_runtime_spec *oci_spec, int64_t cpu_shares) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_cpu(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->cpu->shares = (uint64_t)cpu_shares; + +out: + return ret; +} + +static int merge_cpu_period(oci_runtime_spec *oci_spec, int64_t cpu_period) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_cpu(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->cpu->period = (uint64_t)cpu_period; + +out: + return ret; +} + +static int merge_cpu_realtime_period(oci_runtime_spec *oci_spec, int64_t cpu_rt_period) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_cpu(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->cpu->realtime_period = (uint64_t)cpu_rt_period; + +out: + return ret; +} + +static int merge_cpu_realtime_runtime(oci_runtime_spec *oci_spec, int64_t cpu_rt_runtime) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_cpu(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->cpu->realtime_runtime = cpu_rt_runtime; + +out: + return ret; +} + +static int merge_cpu_quota(oci_runtime_spec *oci_spec, int64_t cpu_quota) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_cpu(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->cpu->quota = cpu_quota; + +out: + return ret; +} + +static int merge_cpuset_cpus(oci_runtime_spec *oci_spec, const char *cpuset_cpus) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_cpu(oci_spec); + if (ret < 0) { + goto out; + } + + free(oci_spec->linux->resources->cpu->cpus); + oci_spec->linux->resources->cpu->cpus = util_strdup_s(cpuset_cpus); + +out: + return ret; +} + +static int merge_cpuset_mems(oci_runtime_spec *oci_spec, const char *cpuset_mems) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_cpu(oci_spec); + if (ret < 0) { + goto out; + } + + free(oci_spec->linux->resources->cpu->mems); + oci_spec->linux->resources->cpu->mems = util_strdup_s(cpuset_mems); + +out: + return ret; +} + +static int make_sure_oci_spec_linux_resources_mem(oci_runtime_spec *oci_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->linux->resources->memory == NULL) { + oci_spec->linux->resources->memory = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources_memory)); + if (oci_spec->linux->resources->memory == NULL) { + return -1; + } + } + return 0; +} + +static int merge_memory_limit(oci_runtime_spec *oci_spec, int64_t memory) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_mem(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->memory->limit = memory; + +out: + return ret; +} + +static int merge_memory_oom_kill_disable(oci_runtime_spec *oci_spec, bool oom_kill_disable) +{ + int ret = 0; + + if (oci_spec->linux == NULL) { + oci_spec->linux = util_common_calloc_s(sizeof(oci_runtime_config_linux)); + if (oci_spec->linux == NULL) { + ret = -1; + goto out; + } + } + + if (oci_spec->linux->resources == NULL) { + oci_spec->linux->resources = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources)); + if (oci_spec->linux->resources == NULL) { + ret = -1; + goto out; + } + } + + if (oci_spec->linux->resources->memory == NULL) { + oci_spec->linux->resources->memory = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources_memory)); + if (oci_spec->linux->resources->memory == NULL) { + ret = -1; + goto out; + } + } + + oci_spec->linux->resources->memory->disable_oom_killer = oom_kill_disable; + +out: + return ret; +} + +static int merge_memory_swap(oci_runtime_spec *oci_spec, int64_t memory_swap) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_mem(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->memory->swap = memory_swap; + +out: + return ret; +} + +static int merge_memory_reservation(oci_runtime_spec *oci_spec, int64_t memory_reservation) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_mem(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->memory->reservation = memory_reservation; + +out: + return ret; +} + +static int merge_kernel_memory(oci_runtime_spec *oci_spec, int64_t kernel_memory) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_mem(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->memory->kernel = kernel_memory; + +out: + return ret; +} + +static int merge_hugetlbs(oci_runtime_spec *oci_spec, host_config_hugetlbs_element **hugetlbs, size_t hugetlbs_len) +{ + int ret = 0; + size_t i = 0; + size_t new_size, old_size; + oci_runtime_config_linux_resources_hugepage_limits_element **hugepage_limits_temp = NULL; + + ret = make_sure_oci_spec_linux_resources(oci_spec); + if (ret < 0) { + goto out; + } + + if (hugetlbs_len > SIZE_MAX / sizeof(oci_runtime_config_linux_resources_hugepage_limits_element *) - + oci_spec->linux->resources->hugepage_limits_len) { + ERROR("Too many hugetlbs to merge!"); + ret = -1; + goto out; + } + old_size = oci_spec->linux->resources->hugepage_limits_len * + sizeof(oci_runtime_config_linux_resources_hugepage_limits_element *); + new_size = (oci_spec->linux->resources->hugepage_limits_len + hugetlbs_len) + * sizeof(oci_runtime_config_linux_resources_hugepage_limits_element *); + ret = mem_realloc((void **)&hugepage_limits_temp, new_size, + oci_spec->linux->resources->hugepage_limits, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for hugepage limits"); + ret = -1; + goto out; + } + + oci_spec->linux->resources->hugepage_limits = hugepage_limits_temp; + + for (i = 0; i < hugetlbs_len; i++) { + oci_spec->linux->resources->hugepage_limits[oci_spec->linux->resources->hugepage_limits_len] + = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources_hugepage_limits_element)); + if (oci_spec->linux->resources->hugepage_limits[oci_spec->linux->resources->hugepage_limits_len] == NULL) { + ERROR("Failed to malloc memory for hugepage limits"); + ret = -1; + goto out; + } + oci_spec->linux->resources->hugepage_limits[oci_spec->linux->resources->hugepage_limits_len]->limit + = hugetlbs[i]->limit; + oci_spec->linux->resources->hugepage_limits[oci_spec->linux->resources->hugepage_limits_len]->page_size + = util_strdup_s(hugetlbs[i]->page_size); + oci_spec->linux->resources->hugepage_limits_len++; + } +out: + return ret; +} + +static int make_sure_oci_spec_hooks(oci_runtime_spec *oci_spec) +{ + if (oci_spec->hooks == NULL) { + oci_spec->hooks = util_common_calloc_s(sizeof(oci_runtime_spec_hooks)); + if (oci_spec->hooks == NULL) { + return -1; + } + } + return 0; +} + +static int merge_hook_spec(oci_runtime_spec *oci_spec, const char *hook_spec) +{ + int ret = 0; + parser_error err = NULL; + oci_runtime_spec_hooks *hooks = NULL; + + if (hook_spec == NULL) { + return 0; + } + + ret = make_sure_oci_spec_hooks(oci_spec); + if (ret < 0) { + goto out; + } + + hooks = oci_runtime_spec_hooks_parse_file(hook_spec, NULL, &err); + if (hooks == NULL) { + ERROR("Failed to parse hook-spec file: %s", err); + ret = -1; + goto out; + } + ret = merge_hooks(oci_spec->hooks, hooks); + free_oci_runtime_spec_hooks(hooks); + if (ret < 0) { + goto out; + } + +out: + free(err); + return ret; +} + +static void clean_correlated_selinux(oci_runtime_spec_process *process) +{ + if (process == NULL) { + return; + } + + free(process->selinux_label); + process->selinux_label = NULL; +} + +static void clean_correlated_read_only_path(oci_runtime_config_linux *linux) +{ + if (linux == NULL) { + return; + } + + if (linux->readonly_paths != NULL && linux->readonly_paths_len) { + size_t i; + for (i = 0; i < linux->readonly_paths_len; i++) { + free(linux->readonly_paths[i]); + linux->readonly_paths[i] = NULL; + } + free(linux->readonly_paths); + linux->readonly_paths = NULL; + linux->readonly_paths_len = 0; + } +} + +static void clean_correlated_masked_path(oci_runtime_config_linux *linux) +{ + if (linux == NULL) { + return; + } + + if (linux->masked_paths != NULL && linux->masked_paths_len) { + size_t i; + for (i = 0; i < linux->masked_paths_len; i++) { + free(linux->masked_paths[i]); + linux->masked_paths[i] = NULL; + } + free(linux->masked_paths); + linux->masked_paths = NULL; + linux->masked_paths_len = 0; + } +} + +static void clean_correlated_seccomp(oci_runtime_config_linux *linux) +{ + if (linux == NULL) { + return; + } + + free_oci_runtime_config_linux_seccomp(linux->seccomp); + linux->seccomp = NULL; +} + +static void clean_correlated_items(const oci_runtime_spec *oci_spec) +{ + if (oci_spec == NULL) { + return; + } + + clean_correlated_selinux(oci_spec->process); + clean_correlated_masked_path(oci_spec->linux); + clean_correlated_read_only_path(oci_spec->linux); + clean_correlated_seccomp(oci_spec->linux); +} + +static int adapt_settings_for_privileged(oci_runtime_spec *oci_spec, bool privileged) +{ + int ret = 0; + size_t all_caps_len = 0; + + if (!privileged) { + return 0; + } + + all_caps_len = util_get_all_caps_len(); + if (oci_spec == NULL) { + ret = -1; + goto out; + } + + clean_correlated_items(oci_spec); + + ret = set_mounts_readwrite_option(oci_spec); + if (ret != 0) { + goto out; + } + + /* add all capabilities */ + ret = make_sure_oci_spec_process(oci_spec); + if (ret < 0) { + goto out; + } + + ret = refill_oci_process_capabilities(&oci_spec->process->capabilities, g_all_caps, all_caps_len); + if (ret != 0) { + ERROR("Failed to copy all capabilities"); + ret = -1; + goto out; + } + + ret = merge_all_devices_and_all_permission(oci_spec); + if (ret != 0) { + ERROR("Failed to merge all devices on host and all devices's cgroup permission"); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int make_sure_oci_spec_linux_resources_pids(oci_runtime_spec *oci_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->linux->resources->pids == NULL) { + oci_spec->linux->resources->pids = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources_pids)); + if (oci_spec->linux->resources->pids == NULL) { + return -1; + } + } + return 0; +} + +static int merge_pids_limit(oci_runtime_spec *oci_spec, int64_t pids_limit) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux_resources_pids(oci_spec); + if (ret < 0) { + goto out; + } + + oci_spec->linux->resources->pids->limit = pids_limit; + +out: + return ret; +} + +static int merge_hostname(oci_runtime_spec *oci_spec, const host_config *host_spec, + container_custom_config *custom_spec) +{ + int ret = 0; + + if (custom_spec->hostname == NULL) { + if (host_spec->network_mode != NULL && !strcmp(host_spec->network_mode, "host")) { + char hostname[MAX_HOST_NAME_LEN] = { 0x00 }; + ret = gethostname(hostname, sizeof(hostname)); + if (ret != 0) { + ERROR("Get hostname error"); + goto out; + } + custom_spec->hostname = util_strdup_s(hostname); + } else { + custom_spec->hostname = util_strdup_s("localhost"); + } + } + + free(oci_spec->hostname); + oci_spec->hostname = util_strdup_s(custom_spec->hostname); +out: + return ret; +} + +static int merge_conf_cgroup_cpu_int64(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + /* cpu shares */ + if (host_spec->cpu_shares != 0) { + ret = merge_cpu_shares(oci_spec, host_spec->cpu_shares); + if (ret != 0) { + ERROR("Failed to merge cgroup cpu shares"); + goto out; + } + } + + /* cpu period */ + if (host_spec->cpu_period != 0) { + ret = merge_cpu_period(oci_spec, host_spec->cpu_period); + if (ret != 0) { + ERROR("Failed to merge cgroup cpu period"); + goto out; + } + } + + /* cpu realtime period */ + if (host_spec->cpu_realtime_period != 0) { + ret = merge_cpu_realtime_period(oci_spec, host_spec->cpu_realtime_period); + if (ret != 0) { + ERROR("Failed to merge cgroup cpu realtime period"); + goto out; + } + } + + /* cpu realtime runtime */ + if (host_spec->cpu_realtime_runtime != 0) { + ret = merge_cpu_realtime_runtime(oci_spec, host_spec->cpu_realtime_runtime); + if (ret != 0) { + ERROR("Failed to merge cgroup cpu realtime runtime"); + goto out; + } + } + + /* cpu quota */ + if (host_spec->cpu_quota != 0) { + ret = merge_cpu_quota(oci_spec, host_spec->cpu_quota); + if (ret != 0) { + ERROR("Failed to merge cgroup cpu quota"); + goto out; + } + } + +out: + return ret; +} + +static int merge_conf_cgroup_cpu(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + ret = merge_conf_cgroup_cpu_int64(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + /* cpuset-cpus */ + if (util_valid_str(host_spec->cpuset_cpus)) { + ret = merge_cpuset_cpus(oci_spec, host_spec->cpuset_cpus); + if (ret != 0) { + ERROR("Failed to merge cgroup cpuset cpus"); + goto out; + } + } + + /* cpuset mems */ + if (util_valid_str(host_spec->cpuset_mems)) { + ret = merge_cpuset_mems(oci_spec, host_spec->cpuset_mems); + if (ret != 0) { + ERROR("Failed to merge cgroup cpuset mems"); + goto out; + } + } + +out: + return ret; +} + +static int merge_conf_cgroup_memory(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + /* memory limit */ + if (host_spec->memory != 0) { + ret = merge_memory_limit(oci_spec, host_spec->memory); + if (ret != 0) { + ERROR("Failed to merge cgroup memory limit"); + goto out; + } + } + + if (host_spec->oom_kill_disable) { + if (host_spec->memory == 0) { + WARN("Disabling the OOM killer on containers without setting memory limit may be dangerous."); + } + + ret = merge_memory_oom_kill_disable(oci_spec, host_spec->oom_kill_disable); + if (ret != 0) { + ERROR("Failed to merge cgroup memory oom kill disable"); + goto out; + } + } + + /* memory swap*/ + if (host_spec->memory_swap != 0) { + ret = merge_memory_swap(oci_spec, host_spec->memory_swap); + if (ret != 0) { + ERROR("Failed to merge cgroup memory swap"); + goto out; + } + } + + /* memory reservation */ + if (host_spec->memory_reservation != 0) { + ret = merge_memory_reservation(oci_spec, host_spec->memory_reservation); + if (ret != 0) { + ERROR("Failed to merge cgroup memory reservation"); + goto out; + } + } + + /* kernel_memory */ + if (host_spec->kernel_memory != 0) { + ret = merge_kernel_memory(oci_spec, host_spec->kernel_memory); + if (ret != 0) { + ERROR("Failed to merge cgroup kernel_memory"); + goto out; + } + } + +out: + return ret; +} + +static int merge_conf_blkio_weight(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + /* blkio weight */ + if (host_spec->blkio_weight != 0) { + ret = merge_blkio_weight(oci_spec, host_spec->blkio_weight); + if (ret != 0) { + ERROR("Failed to merge cgroup blkio weight"); + goto out; + } + } +out: + return ret; +} + +static int do_merge_one_ulimit_override(const oci_runtime_spec *oci_spec, + oci_runtime_spec_process_rlimits_element *rlimit) +{ + size_t j; + bool exists = false; + + for (j = 0; j < oci_spec->process->rlimits_len; j++) { + if (oci_spec->process->rlimits[j]->type == NULL) { + ERROR("rlimit type is empty"); + free(rlimit->type); + free(rlimit); + return -1; + } + if (strcmp(oci_spec->process->rlimits[j]->type, rlimit->type) == 0) { + exists = true; + break; + } + } + if (exists) { + /* override ulimit */ + free_oci_runtime_spec_process_rlimits_element(oci_spec->process->rlimits[j]); + oci_spec->process->rlimits[j] = rlimit; + } else { + oci_spec->process->rlimits[oci_spec->process->rlimits_len] = rlimit; + oci_spec->process->rlimits_len++; + } + + return 0; +} + +static int merge_one_ulimit_override(const oci_runtime_spec *oci_spec, const host_config_ulimits_element *ulimit) +{ + oci_runtime_spec_process_rlimits_element *rlimit = NULL; + + if (trans_ulimit_to_rlimit(&rlimit, ulimit) != 0) { + return -1; + } + + return do_merge_one_ulimit_override(oci_spec, rlimit); +} + +static int merge_ulimits_override(oci_runtime_spec *oci_spec, host_config_ulimits_element **ulimits, size_t ulimits_len) +{ + int ret = 0; + size_t i = 0; + + if (oci_spec == NULL || ulimits == NULL || ulimits_len == 0) { + return -1; + } + + ret = merge_ulimits_pre(oci_spec, ulimits_len); + if (ret < 0) { + goto out; + } + + for (i = 0; i < ulimits_len; i++) { + ret = merge_one_ulimit_override(oci_spec, ulimits[i]); + if (ret != 0) { + ret = -1; + goto out; + } + } +out: + return ret; +} + +static int merge_conf_ulimits(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + /* rlimits */ + if (host_spec->ulimits != NULL && host_spec->ulimits_len != 0) { + if (host_spec->ulimits_len > LIST_SIZE_MAX) { + ERROR("Too many ulimits to add, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many ulimits to add, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto out; + } + ret = merge_ulimits_override(oci_spec, host_spec->ulimits, host_spec->ulimits_len); + if (ret != 0) { + ERROR("Failed to merge rlimits"); + goto out; + } + } + +out: + return ret; +} + +static int merge_conf_hugetlbs(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + /* hugepage limits */ + if (host_spec->hugetlbs_len != 0 && host_spec->hugetlbs != NULL) { + if (host_spec->hugetlbs_len > LIST_SIZE_MAX) { + ERROR("Too many hugetlbs to add, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many hugetlbs to add, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto out; + } + ret = merge_hugetlbs(oci_spec, host_spec->hugetlbs, host_spec->hugetlbs_len); + if (ret != 0) { + ERROR("Failed to merge cgroup hugepage limits"); + goto out; + } + } + +out: + return ret; +} + +static int merge_conf_pids_limit(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + /* pids limit */ + if (host_spec->pids_limit != 0) { + ret = merge_pids_limit(oci_spec, host_spec->pids_limit); + if (ret != 0) { + ERROR("Failed to merge pids limit"); + goto out; + } + } + +out: + return ret; +} + +static int merge_conf_cgroup(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + + ret = merge_conf_cgroup_cpu(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + ret = merge_conf_cgroup_memory(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + ret = merge_conf_blkio_weight(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + ret = merge_conf_ulimits(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + ret = merge_conf_hugetlbs(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + ret = merge_conf_pids_limit(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +static int prepare_process_args(oci_runtime_spec *oci_spec, size_t args_len) +{ + int ret = 0; + + ret = make_sure_oci_spec_process(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->process->args_len != 0 && oci_spec->process->args != NULL) { + size_t i; + for (i = 0; i < oci_spec->process->args_len; i++) { + free(oci_spec->process->args[i]); + oci_spec->process->args[i] = NULL; + } + free(oci_spec->process->args); + oci_spec->process->args = NULL; + oci_spec->process->args_len = 0; + } + + if (args_len > (SIZE_MAX / sizeof(char *))) { + return -1; + } + + oci_spec->process->args = util_common_calloc_s(args_len * sizeof(char *)); + if (oci_spec->process->args == NULL) { + return -1; + } + return 0; +} + +static int replace_entrypoint_cmds_from_spec(const oci_runtime_spec *oci_spec, container_custom_config *custom_spec) +{ + if (oci_spec->process->args_len == 0) { + ERROR("No command specified"); + lcrd_set_error_message("No command specified"); + return -1; + } + return dup_array_of_strings((const char **)(oci_spec->process->args), oci_spec->process->args_len, + &(custom_spec->cmd), &(custom_spec->cmd_len)); +} + +static int merge_conf_args(oci_runtime_spec *oci_spec, container_custom_config *custom_spec) +{ + int ret = 0; + size_t argslen = 0; + size_t i = 0; + + // Reset entrypoint if we do not want to use entrypoint from image + if (custom_spec->entrypoint_len == 1 && custom_spec->entrypoint[0][0] == '\0') { + free(custom_spec->entrypoint[0]); + custom_spec->entrypoint[0] = NULL; + free(custom_spec->entrypoint); + custom_spec->entrypoint = NULL; + custom_spec->entrypoint_len = 0; + } + + argslen = custom_spec->cmd_len; + if (custom_spec->entrypoint_len != 0) { + argslen += custom_spec->entrypoint_len; + } + + if (argslen > LIST_SIZE_MAX) { + ERROR("Too many commands to add, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many commands to add, the limit is %d", LIST_SIZE_MAX); + return -1; + } + + if (argslen == 0) { + return replace_entrypoint_cmds_from_spec(oci_spec, custom_spec); + } + + if (prepare_process_args(oci_spec, argslen) < 0) { + ret = -1; + goto out; + } + + // append commands... to entrypoint + for (i = 0; custom_spec->entrypoint != NULL && i < custom_spec->entrypoint_len; i++) { + oci_spec->process->args[oci_spec->process->args_len] = util_strdup_s(custom_spec->entrypoint[i]); + oci_spec->process->args_len++; + } + + for (i = 0; custom_spec->cmd != NULL && i < custom_spec->cmd_len; i++) { + oci_spec->process->args[oci_spec->process->args_len] = util_strdup_s(custom_spec->cmd[i]); + oci_spec->process->args_len++; + } + +out: + return ret; +} + +static int merge_share_namespace_helper(const oci_runtime_spec *oci_spec, const char *mode, const char *type) +{ + int ret = -1; + char *tmp_mode = NULL; + size_t len = 0; + size_t org_len = 0; + size_t i = 0; + oci_runtime_defs_linux_namespace_reference **work_ns = NULL; + + org_len = oci_spec->linux->namespaces_len; + len = oci_spec->linux->namespaces_len; + work_ns = oci_spec->linux->namespaces; + + tmp_mode = get_share_namespace_path(type, mode); + for (i = 0; i < org_len; i++) { + if (strcmp(mode, work_ns[i]->type) == 0) { + free(work_ns[i]->path); + work_ns[i]->path = NULL; + if (tmp_mode != NULL) { + work_ns[i]->path = util_strdup_s(tmp_mode); + } + break; + } + } + if (i >= org_len) { + if (len > (SIZE_MAX / sizeof(oci_runtime_defs_linux_namespace_reference *)) - 1) { + ret = -1; + ERROR("Out of memory"); + goto out; + } + + ret = mem_realloc((void **)&work_ns, (len + 1) * sizeof(oci_runtime_defs_linux_namespace_reference *), + (void *)work_ns, len * sizeof(oci_runtime_defs_linux_namespace_reference *)); + if (ret != 0) { + ERROR("Out of memory"); + goto out; + } + work_ns[len] = util_common_calloc_s(sizeof(oci_runtime_defs_linux_namespace_reference)); + if (work_ns[len] == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + work_ns[len]->type = util_strdup_s(type); + if (tmp_mode != NULL) { + work_ns[len]->path = util_strdup_s(tmp_mode); + } + len++; + } + ret = 0; +out: + free(tmp_mode); + if (work_ns != NULL) { + oci_spec->linux->namespaces = work_ns; + oci_spec->linux->namespaces_len = len; + } + return ret; +} + +static int merge_share_single_namespace(const oci_runtime_spec *oci_spec, const char *mode, const char *type) +{ + if (mode == NULL) { + return 0; + } + + return merge_share_namespace_helper(oci_spec, mode, type); +} + +static int merge_share_namespace(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = -1; + + if (oci_spec == NULL || host_spec == NULL) { + goto out; + } + + if (make_sure_oci_spec_linux(oci_spec) < 0) { + goto out; + } + + // user + if (merge_share_single_namespace(oci_spec, host_spec->userns_mode, TYPE_NAMESPACE_USER) != 0) { + ret = -1; + goto out; + } + + // user remap + if (host_spec->user_remap != NULL && merge_share_single_namespace(oci_spec, "user", TYPE_NAMESPACE_USER) != 0) { + ret = -1; + goto out; + } + + // network + if (merge_share_single_namespace(oci_spec, host_spec->network_mode, TYPE_NAMESPACE_NETWORK) != 0) { + ret = -1; + goto out; + } + + // ipc + if (merge_share_single_namespace(oci_spec, host_spec->ipc_mode, TYPE_NAMESPACE_IPC) != 0) { + ret = -1; + goto out; + } + + // pid + if (merge_share_single_namespace(oci_spec, host_spec->pid_mode, TYPE_NAMESPACE_PID) != 0) { + ret = -1; + goto out; + } + + // uts + if (merge_share_single_namespace(oci_spec, host_spec->uts_mode, TYPE_NAMESPACE_UTS) != 0) { + ret = -1; + goto out; + } + + ret = 0; +out: + return ret; +} + +static int merge_working_dir(oci_runtime_spec *oci_spec, const char *working_dir) +{ + int ret = 0; + + if (working_dir == NULL) { + return 0; + } + + ret = make_sure_oci_spec_process(oci_spec); + if (ret < 0) { + goto out; + } + + free(oci_spec->process->cwd); + oci_spec->process->cwd = util_strdup_s(working_dir); + +out: + return ret; +} + +static int merge_settings_for_system_container(oci_runtime_spec *oci_spec, host_config *host_spec, + container_custom_config *custom_spec) +{ + int ret = -1; + + if (oci_spec == NULL || host_spec == NULL) { + return -1; + } + + if (!host_spec->system_container) { + return 0; + } + + ret = adapt_settings_for_system_container(oci_spec, host_spec); + if (ret != 0) { + ERROR("Failed to adapt settings for system container"); + goto out; + } + if (!mount_run_tmpfs(oci_spec, "/run")) { + ret = -1; + ERROR("Failed to add /run mount to system container"); + goto out; + } + if (!mount_run_tmpfs(oci_spec, "/run/lock")) { + ret = -1; + ERROR("Failed to add /run/lock mount to system container"); + goto out; + } + + // append mounts of oci_spec + if (custom_spec->ns_change_opt != NULL) { + ret = adapt_settings_for_mounts(oci_spec, custom_spec); + if (ret != 0) { + ERROR("Failed to adapt settings for ns_change_opt"); + goto out; + } + } + +out: + return ret; +} + +static int merge_resources_conf(oci_runtime_spec *oci_spec, host_config *host_spec, + container_custom_config *custom_spec, container_config_v2_common_config *common_config) +{ + int ret = 0; + + ret = merge_share_namespace(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + ret = merge_conf_cgroup(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + ret = merge_conf_device(oci_spec, host_spec); + if (ret != 0) { + goto out; + } + + ret = merge_conf_mounts(oci_spec, custom_spec, host_spec, common_config); + if (ret) { + goto out; + } +out: + return ret; +} + +static int merge_process_conf(oci_runtime_spec *oci_spec, const host_config *host_spec, + container_custom_config *custom_spec) +{ + int ret = 0; + + ret = merge_conf_args(oci_spec, custom_spec); + if (ret != 0) { + goto out; + } + + /* environment variables */ + ret = merge_env(oci_spec, (const char **)custom_spec->env, custom_spec->env_len); + if (ret != 0) { + ERROR("Failed to merge environment variables"); + goto out; + } + + /* env target file */ + ret = merge_env_target_file(oci_spec, host_spec->env_target_file); + if (ret != 0) { + ERROR("Failed to merge env target file"); + goto out; + } + + /* working dir */ + ret = merge_working_dir(oci_spec, custom_spec->working_dir); + if (ret != 0) { + ERROR("Failed to merge working dir"); + goto out; + } + + /* hook-spec file */ + ret = merge_hook_spec(oci_spec, host_spec->hook_spec); + if (ret != 0) { + ERROR("Failed to merge hook spec"); + goto out; + } + +out: + return ret; +} + +static int merge_security_conf(oci_runtime_spec *oci_spec, host_config *host_spec) +{ + int ret = 0; + + ret = merge_caps(oci_spec, (const char **)host_spec->cap_add, host_spec->cap_add_len, + (const char **)host_spec->cap_drop, host_spec->cap_drop_len); + if (ret) { + ERROR("Failed to merge caps"); + goto out; + } + + ret = merge_default_seccomp_spec(oci_spec, oci_spec->process->capabilities); + if (ret != 0) { + ERROR("Failed to merge default seccomp file"); + goto out; + } + + // merge external parameter + ret = merge_seccomp(oci_spec, host_spec); + if (ret != 0) { + ERROR("Failed to merge user seccomp file"); + goto out; + } + +out: + return ret; +} + + +static int merge_conf(oci_runtime_spec *oci_spec, host_config *host_spec, + container_custom_config *custom_spec, container_config_v2_common_config *common_config) +{ + int ret = 0; + + ret = merge_resources_conf(oci_spec, host_spec, custom_spec, common_config); + if (ret != 0) { + goto out; + } + + ret = merge_process_conf(oci_spec, host_spec, custom_spec); + if (ret != 0) { + goto out; + } + + // merge sysctl + ret = merge_sysctls(oci_spec, host_spec->sysctls); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = merge_security_conf(oci_spec, host_spec); + if (ret != 0) { + ERROR("Failed to merge user seccomp file"); + goto out; + } + + /* settings for system container */ + ret = merge_settings_for_system_container(oci_spec, host_spec, custom_spec); + if (ret != 0) { + ERROR("Failed to merge system container conf"); + goto out; + } + + /* settings for privileged */ + ret = adapt_settings_for_privileged(oci_spec, host_spec->privileged); + if (ret != 0) { + ERROR("Failed to adapt settings for privileged container"); + goto out; + } + + ret = merge_hostname(oci_spec, host_spec, custom_spec); + if (ret != 0) { + ERROR("Failed to merge hostname"); + goto out; + } + + ret = make_annotations(oci_spec, custom_spec, host_spec); + if (ret != 0) { + ret = -1; + goto out; + } + + ret = merge_no_new_privileges(oci_spec, host_spec); + if (ret != 0) { + ERROR("Failed to merge no new privileges"); + goto out; + } + + ret = make_userns_remap(oci_spec, host_spec->user_remap); + if (ret != 0) { + ERROR("Failed to make user remap for container"); + goto out; + } + +out: + return ret; +} + +/* merge the default config with host config and image config and custom config */ +oci_runtime_spec *merge_container_config(const char *id, const char *image_type, const char *image_name, + const char *ext_config_image, host_config *host_spec, + container_custom_config *custom_spec, + container_config_v2_common_config *v2_spec, char **real_rootfs) +{ + oci_runtime_spec *oci_spec = NULL; + parser_error err = NULL; + int ret = 0; + + oci_spec = default_spec(); + if (oci_spec == NULL) { + goto out; + } + + ret = make_sure_oci_spec_linux(oci_spec); + if (ret < 0) { + goto out; + } + + ret = im_merge_image_config(id, image_type, image_name, ext_config_image, oci_spec, host_spec, + custom_spec, real_rootfs); + if (ret != 0) { + ERROR("Can not merge with image config"); + goto free_out; + } + + if (*real_rootfs != NULL) { + ret = merge_root(oci_spec, *real_rootfs, host_spec); + if (ret != 0) { + ERROR("Failed to merge root"); + goto free_out; + } + v2_spec->base_fs = util_strdup_s(*real_rootfs); + } + ret = merge_conf(oci_spec, host_spec, custom_spec, v2_spec); + if (ret != 0) { + ERROR("Failed to merge config"); + goto free_out; + } + + goto out; + +free_out: + free_oci_runtime_spec(oci_spec); + oci_spec = NULL; +out: + free(err); + return oci_spec; +} + +static inline bool is_valid_umask_value(const char *value) +{ + return (strcmp(value, UMASK_NORMAL) == 0 || strcmp(value, UMASK_SECURE) == 0); +} + +static int add_native_umask(const oci_runtime_spec *container) +{ + int ret = 0; + size_t i = 0; + char *umask = NULL; + + for (i = 0; i < container->annotations->len; i++) { + if (strcmp(container->annotations->keys[i], ANNOTATION_UMAKE_KEY) == 0) { + if (!is_valid_umask_value(container->annotations->values[i])) { + ERROR("native.umask option %s not supported", container->annotations->values[i]); + lcrd_set_error_message("native.umask option %s not supported", container->annotations->values[i]); + ret = -1; + } + goto out; + } + } + + umask = conf_get_lcrd_native_umask(); + if (umask == NULL) { + ERROR("Failed to get default native umask"); + ret = -1; + goto out; + } + + if (append_json_map_string_string(container->annotations, ANNOTATION_UMAKE_KEY, umask)) { + ERROR("Failed to append annotations: native.umask=%s", umask); + ret = -1; + goto out; + } + +out: + free(umask); + return ret; +} + +/* merge the default config with host config and custom config */ +int merge_global_config(oci_runtime_spec *oci_spec) +{ + int ret = 0; + + ret = merge_global_hook(oci_spec); + if (ret != 0) { + ERROR("Failed to merge global hooks"); + goto out; + } + + ret = merge_global_ulimit(oci_spec); + if (ret != 0) { + ERROR("Failed to merge global ulimit"); + goto out; + } + + /* add rootfs.mount */ + ret = add_rootfs_mount(oci_spec); + if (ret != 0) { + ERROR("Failed to add rootfs mount"); + goto out; + } + + /* add native.umask */ + ret = add_native_umask(oci_spec); + if (ret != 0) { + ERROR("Failed to add native umask"); + goto out; + } + +out: + return ret; +} + +/* read oci config */ +oci_runtime_spec *read_oci_config(const char *rootpath, const char *name) +{ + int nret; + char filename[PATH_MAX] = { 0x00 }; + parser_error err = NULL; + oci_runtime_spec *ociconfig = NULL; + + nret = sprintf_s(filename, sizeof(filename), "%s/%s/%s", rootpath, name, OCICONFIGJSON); + if (nret < 0) { + ERROR("Failed to print string"); + goto out; + } + + ociconfig = oci_runtime_spec_parse_file(filename, NULL, &err); + if (ociconfig == NULL) { + ERROR("Failed to parse oci config file:%s", err); + lcrd_set_error_message("Parse oci config file failed:%s", err); + goto out; + } +out: + free(err); + return ociconfig; +} diff --git a/src/services/execution/spec/specs.h b/src/services/execution/spec/specs.h new file mode 100644 index 0000000..2f8ada7 --- /dev/null +++ b/src/services/execution/spec/specs.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide specs definition + ******************************************************************************/ +#ifndef __SPECS_H__ +#define __SPECS_H__ + +#include +#include "liblcrd.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "container_config_v2.h" +#include "oci_runtime_hooks.h" +#include "oci_runtime_spec.h" + +oci_runtime_spec *merge_container_config(const char *id, const char *image_type, const char *image_name, + const char *ext_image_name, host_config *host_spec, + container_custom_config *custom_spec, + container_config_v2_common_config *v2_spec, char **real_rootfs); +int merge_global_config(oci_runtime_spec *oci_spec); +oci_runtime_spec *read_oci_config(const char *rootpath, const char *name); + +#endif diff --git a/src/services/execution/spec/specs_extend.c b/src/services/execution/spec/specs_extend.c new file mode 100644 index 0000000..5525c87 --- /dev/null +++ b/src/services/execution/spec/specs_extend.c @@ -0,0 +1,1289 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container specs functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "securec.h" +#include "log.h" +#include "oci_runtime_spec.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "utils.h" +#include "config.h" +#include "path.h" +#include "lcrd_config.h" +#include "specs_extend.h" + +#define MINUID 0 +#define MAXUID (((1LL << 31) - 1)) +#define DEFAULT_UID 0 + +#define UnixPasswdPath "/etc/passwd" +#define UnixGroupPath "/etc/group" + +#define MERGE_HOOKS_ITEM_DEF(item) \ + int merge_##item##_conf(oci_runtime_spec_hooks *dest, oci_runtime_spec_hooks * src) \ + {\ + size_t old_size = 0; \ + size_t new_size = 0; \ + int ret = 0; \ + size_t i = 0; \ + if (src->item##_len) { \ + defs_hook **item = NULL; \ + if (dest->item##_len > (LIST_SIZE_MAX - src->item##_len) - 1) { \ + ERROR("the length of item element is too long!"); \ + ret = -1; \ + goto out; \ + } \ + old_size = dest->item##_len * sizeof(defs_hook *); \ + new_size = (dest->item##_len + src->item##_len + 1) * sizeof(defs_hook *); \ + ret = mem_realloc((void **)&(item), new_size, dest->item, old_size); \ + if (ret != 0) { \ + ERROR("Failed to realloc memory for hooks_"#item" variables"); \ + ret = -1; \ + goto out; \ + }\ + dest->item = item; \ + for (; i < src->item##_len; i++) { \ + dest->item[dest->item##_len] = src->item[i]; \ + dest->item##_len++; \ + src->item[i] = NULL; \ + } \ + src->item##_len = 0; \ + free(src->item); \ + src->item = NULL; \ + } \ + out: \ + return ret; \ + } + +MERGE_HOOKS_ITEM_DEF(prestart) +MERGE_HOOKS_ITEM_DEF(poststart) +MERGE_HOOKS_ITEM_DEF(poststop) + + +int merge_hooks(oci_runtime_spec_hooks *dest, oci_runtime_spec_hooks *src) +{ + if (merge_prestart_conf(dest, src) || merge_poststart_conf(dest, src) || merge_poststop_conf(dest, src)) { + return -1; + } + return 0; +} + +int merge_global_hook(oci_runtime_spec *oci_spec) +{ + int ret = 0; + oci_runtime_spec_hooks *hooks = NULL; + oci_runtime_spec_hooks *tmp = NULL; + + if (conf_get_lcrd_hooks(&hooks)) { + ERROR("Failed to get lcrd hooks"); + ret = -1; + goto out; + } + if (oci_spec->hooks != NULL) { + if (hooks != NULL) { + if (merge_hooks(hooks, oci_spec->hooks)) { + ret = -1; + goto out; + } + tmp = hooks; + hooks = oci_spec->hooks; + oci_spec->hooks = tmp; + } + } else { + oci_spec->hooks = hooks; + hooks = NULL; + } +out: + free_oci_runtime_spec_hooks(hooks); + return ret; +} + +static int make_one_id_mapping(defs_id_mapping ***mappings, unsigned int id, unsigned int size) +{ + *mappings = util_common_calloc_s(sizeof(defs_id_mapping *)); + if (*mappings == NULL) { + return -1; + } + (*mappings)[0] = util_common_calloc_s(sizeof(defs_id_mapping)); + if ((*mappings)[0] == NULL) { + return -1; + } + (*mappings)[0]->host_id = id; + (*mappings)[0]->container_id = 0; + (*mappings)[0]->size = size; + return 0; +} + +static int make_linux_uid_gid_mappings(oci_runtime_spec *container, + unsigned int host_uid, + unsigned int host_gid, + unsigned int size) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux(container); + if (ret < 0) { + goto out; + } + + if (container->linux->uid_mappings == NULL) { + ret = make_one_id_mapping(&(container->linux->uid_mappings), host_uid, size); + if (ret < 0) { + goto out; + } + container->linux->uid_mappings_len++; + } + if (container->linux->gid_mappings == NULL) { + ret = make_one_id_mapping(&(container->linux->gid_mappings), host_gid, size); + if (ret < 0) { + goto out; + } + container->linux->gid_mappings_len++; + } + +out: + return ret; +} + +int make_userns_remap(oci_runtime_spec *container, const char *user_remap) +{ + int ret = 0; + unsigned int host_uid = 0; + unsigned int host_gid = 0; + unsigned int size = 0; + + if (user_remap == NULL) { + return 0; + } + + ret = util_parse_user_remap(user_remap, &host_uid, &host_gid, &size); + if (ret) { + ERROR("User remap '%s' format error", user_remap); + return ret; + } + if (host_uid == 0 && host_gid == 0) { + return 0; + } + ret = make_linux_uid_gid_mappings(container, host_uid, host_gid, size); + if (ret) { + ERROR("Make linux uid and gid mappings failed"); + return ret; + } + return ret; +} + +static int generate_env_map_from_file(FILE *fp, json_map_string_string *env_map) +{ + int ret = 0; + char *key = NULL; + char *value = NULL; + char *pline = NULL; + size_t length = 0; + char *saveptr = NULL; + + while (getline(&pline, &length, fp) != -1) { + util_trim_newline(pline); + pline = util_trim_space(pline); + if (pline == NULL || pline[0] == '#') { + continue; + } + key = strtok_r(pline, "=", &saveptr); + value = strtok_r(NULL, "=", &saveptr); + if (key != NULL && value != NULL) { + key = util_trim_space(key); + value = util_trim_space(value); + if ((size_t)(MAX_BUFFER_SIZE - 1) - strlen(key) < strlen(value)) { + ERROR("env length exceed %lld bytes", MAX_BUFFER_SIZE); + ret = -1; + goto out; + } + ret = append_json_map_string_string(env_map, key, value); + if (ret < 0) { + ERROR("append env to map failed"); + goto out; + } + } + } +out: + free(pline); + return ret; +} + +static json_map_string_string *parse_env_target_file(const char *env_path) +{ + int ret = 0; + FILE *fp = NULL; + json_map_string_string *env_map = (json_map_string_string *)util_common_calloc_s(sizeof(json_map_string_string)); + + if (env_map == NULL) { + ERROR("Out of memory"); + return NULL; + } + fp = util_fopen(env_path, "r"); + if (fp == NULL) { + SYSERROR("Failed to open env target file '%s'", env_path); + goto out; + } + ret = generate_env_map_from_file(fp, env_map); + if (ret != 0) { + ERROR("Failed to generate env map from file"); + goto out; + } + fclose(fp); + return env_map; +out: + if (fp != NULL) { + fclose(fp); + } + free_json_map_string_string(env_map); + return NULL; +} + +static int do_append_env(char ***env, size_t *env_len, const char *key, const char *value) +{ + char *tmp_env = NULL; + size_t tmp_env_len = 0; + + if (strlen(value) > ((SIZE_MAX - 2) - strlen(key))) { + ERROR("env value length too big"); + return -1; + } + + tmp_env_len = strlen(key) + strlen(value) + 2; + + tmp_env = util_common_calloc_s(tmp_env_len); + if (tmp_env == NULL) { + ERROR("Out of memory"); + return -1; + } + if (sprintf_s(tmp_env, tmp_env_len, "%s=%s", key, value) < 0) { + ERROR("Out of memory"); + free(tmp_env); + return -1; + } + if (util_array_append(env, tmp_env) < 0) { + ERROR("Failed to append env"); + free(tmp_env); + return -1; + } + free(tmp_env); + (*env_len)++; + return 0; +} + +static int check_env_need_append(const oci_runtime_spec *oci_spec, const char *env_key, bool *is_append) +{ + size_t i = 0; + char *key = NULL; + char *value = NULL; + char *saveptr = NULL; + + for (i = 0; i < oci_spec->process->env_len; i++) { + char *tmp_env = NULL; + tmp_env = util_strdup_s(oci_spec->process->env[i]); + key = strtok_r(tmp_env, "=", &saveptr); + value = strtok_r(NULL, "=", &saveptr); + if (key == NULL || value == NULL) { + ERROR("Bad env format"); + free(tmp_env); + tmp_env = NULL; + return -1; + } + if (strcmp(key, env_key) == 0) { + *is_append = false; + free(tmp_env); + tmp_env = NULL; + return 0; + } + free(tmp_env); + tmp_env = NULL; + } + return 0; +} + +static int do_merge_env_target(oci_runtime_spec *oci_spec, const json_map_string_string *env_map) +{ + int ret = 0; + size_t i = 0; + char **env = NULL; + size_t env_len = 0; + + env = (char **)util_common_calloc_s(sizeof(char *)); + if (env == NULL) { + ERROR("Out of memory"); + return -1; + } + + for (i = 0; i < env_map->len; i++) { + bool is_append = true; + ret = check_env_need_append(oci_spec, env_map->keys[i], &is_append); + if (ret < 0) { + goto out; + } + if (!is_append) { + continue; + } + if (do_append_env(&env, &env_len, + (const char *)env_map->keys[i], + (const char *)env_map->values[i]) < 0) { + ERROR("Failed to append env"); + ret = -1; + goto out; + } + } + ret = merge_env(oci_spec, (const char **)env, env_len); +out: + util_free_array(env); + return ret; +} + +static char *get_env_abs_file_path(const oci_runtime_spec *oci_spec, const char *env_target_file) +{ + char *abs_path = NULL; + int64_t file_size = 0; + + if (oci_spec->root == NULL || oci_spec->root->path == NULL) { + return NULL; + } + abs_path = util_path_join(oci_spec->root->path, env_target_file); + if (abs_path == NULL) { + ERROR("Failed to get env abs file path"); + return NULL; + } + if (strncmp(abs_path, oci_spec->root->path, strlen(oci_spec->root->path)) != 0) { + ERROR("env target file path must be under rootfs '%s'", oci_spec->root->path); + free(abs_path); + return NULL; + } + if (!util_file_exists(abs_path)) { + return abs_path; + } + file_size = util_file_size(abs_path); + if (file_size > REGULAR_FILE_SIZE) { + ERROR("env target file %s, size exceed limit: %lld", env_target_file, REGULAR_FILE_SIZE); + free(abs_path); + return NULL; + } + return abs_path; +} + +int merge_env_target_file(oci_runtime_spec *oci_spec, const char *env_target_file) +{ + int ret = 0; + char *env_path = NULL; + json_map_string_string *env_map = NULL; + + if (env_target_file == NULL) { + return 0; + } + env_path = get_env_abs_file_path(oci_spec, env_target_file); + if (env_path == NULL) { + ret = -1; + goto out; + } + if (!util_file_exists(env_path)) { + goto out; + } + env_map = parse_env_target_file(env_path); + if (env_map == NULL) { + ERROR("Failed to parse env target file"); + ret = -1; + goto out; + } + ret = do_merge_env_target(oci_spec, (const json_map_string_string *)env_map); + if (ret != 0) { + ERROR("Failed to merge env target file"); + goto out; + } +out: + free(env_path); + free_json_map_string_string(env_map); + return ret; +} + +int merge_env(oci_runtime_spec *oci_spec, const char **env, size_t env_len) +{ + int ret = 0; + size_t new_size = 0; + size_t old_size = 0; + size_t i = 0; + char **temp = NULL; + + if (env_len == 0 || env == NULL) { + return 0; + } + + ret = make_sure_oci_spec_process(oci_spec); + if (ret < 0) { + goto out; + } + + if (env_len > LIST_ENV_SIZE_MAX - oci_spec->process->env_len) { + ERROR("The length of envionment variables is too long, the limit is %d", LIST_ENV_SIZE_MAX); + lcrd_set_error_message("The length of envionment variables is too long, the limit is %d", LIST_ENV_SIZE_MAX); + ret = -1; + goto out; + } + new_size = (oci_spec->process->env_len + env_len) * sizeof(char *); + old_size = oci_spec->process->env_len * sizeof(char *); + ret = mem_realloc((void **)&temp, new_size, oci_spec->process->env, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for envionment variables"); + ret = -1; + goto out; + } + + oci_spec->process->env = temp; + for (i = 0; i < env_len; i++) { + oci_spec->process->env[oci_spec->process->env_len] = util_strdup_s(env[i]); + oci_spec->process->env_len++; + } +out: + return ret; +} + +static int read_user_file(const char *basefs, const char *user_path, FILE **stream) +{ + int nret; + int64_t filesize = 0; + char path[PATH_MAX] = {0}; + char real_path[PATH_MAX] = {0}; + + nret = sprintf_s(path, sizeof(path), "%s/%s", basefs, user_path); + if (nret < 0) { + ERROR("Path is too long"); + return -1; + } + if (cleanpath(path, real_path, sizeof(real_path)) == NULL) { + ERROR("Failed to clean path"); + return -1; + } + + filesize = util_file_size(real_path); + if (filesize > REGULAR_FILE_SIZE) { + ERROR("File %s is more than %lld", real_path, (long long)REGULAR_FILE_SIZE); + lcrd_set_error_message("File %s is more than %lld", real_path, (long long)REGULAR_FILE_SIZE); + return -1; + } + + *stream = util_fopen(real_path, "r"); + if (*stream == NULL) { + ERROR("Failed to open %s: %s", real_path, strerror(errno)); + return 0; + } + return 0; +} + +static void parse_user_group(const char *username, char **user, char **group, char **tmp_dup) +{ + char *tmp = NULL; + char *pdot = NULL; + + if (user == NULL || group == NULL || tmp_dup == NULL) { + return; + } + + if (username != NULL) { + tmp = util_strdup_s(username); + + // for free tmp in caller + *tmp_dup = tmp; + + pdot = strstr(tmp, ":"); + if (pdot != NULL) { + *pdot = '\0'; + if (pdot != tmp) { + // User found + *user = tmp; + } + if (*(pdot + 1) != '\0') { + // group found + *group = pdot + 1; + } + } else { + // No : found + if (*tmp != '\0') { + *user = tmp; + } + } + } + + return; +} + +static void uids_gids_range_err_log() +{ + ERROR("uids and gids must be in range 0-%d", MAXUID); + lcrd_set_error_message("uids and gids must be in range 0-%d", MAXUID); + return; +} + +static bool b_user_found(const char *user, const struct passwd *pwbufp) +{ + int uret = -1; + long long n_user = 0; + bool userfound = false; + + if (pwbufp == NULL) { + return false; + } + + if (user != NULL) { + uret = util_safe_llong(user, &n_user); + } + + if (user == NULL && pwbufp->pw_uid == DEFAULT_UID) { + userfound = true; + } + // Treat numeric usename as valid UID + if (uret == 0 && (long long)pwbufp->pw_uid == n_user) { + userfound = true; + } + if (uret != 0 && user != NULL && strcmp(user, pwbufp->pw_name) == 0) { + userfound = true; + } + + return userfound; +} + + +static int proc_by_fpasswd(FILE *f_passwd, const char *user, oci_runtime_spec_process_user *puser, + char **matched_username) +{ + int ret = 0; + int errval = 0; + int uret = -1; + bool userfound = false; + long long n_user = 0; + char buf[BUFSIZ]; + struct passwd pw, *pwbufp = NULL; + + if (f_passwd != NULL) { + errval = fgetpwent_r(f_passwd, &pw, buf, sizeof(buf), &pwbufp); + + while (errval == 0 && pwbufp != NULL) { + userfound = b_user_found(user, pwbufp); + // Take the first match as valid user + if (userfound) { + free(puser->username); + puser->username = util_strdup_s(pwbufp->pw_name); + puser->uid = pwbufp->pw_uid; + puser->gid = pwbufp->pw_gid; + *matched_username = puser->username; + break; + } + errval = fgetpwent_r(f_passwd, &pw, buf, sizeof(buf), &pwbufp); + } + } + + if (errval != 0 && errval != ENOENT) { + ERROR("Failed to parse passwd file: Insufficient buffer space supplied"); + lcrd_set_error_message("Failed to parse passwd file: Insufficient buffer space supplied"); + ret = -1; + goto out; + } + if (!userfound && user != NULL) { + uret = util_safe_llong(user, &n_user); + // user is not a valid numeric UID + if (uret != 0) { + ERROR("Unable to find user '%s'", user); + lcrd_set_error_message("Unable to find user '%s': no matching entries in passwd file", user); + ret = -1; + goto out; + } + if (n_user < MINUID || n_user > MAXUID) { + uids_gids_range_err_log(); + ret = -1; + goto out; + } + puser->uid = (uid_t)n_user; + } + +out: + return ret; +} + +static int append_additional_gids(gid_t gid, gid_t **additional_gids, size_t *len) +{ + int ret = 0; + size_t new_len = 0; + size_t i; + gid_t *new_gids = NULL; + + if (*len > (SIZE_MAX / sizeof(gid_t)) - 1) { + ERROR("Out of memory"); + return -1; + } + + new_len = *len + 1; + + for (i = 0; i < *len; i++) { + if ((*additional_gids)[i] == gid) { + return 0; + } + } + + ret = mem_realloc((void **)&new_gids, new_len * sizeof(gid_t), *additional_gids, (*len) * sizeof(gid_t)); + if (ret != 0) { + ERROR("Out of memory"); + return -1; + } + *additional_gids = new_gids; + (*additional_gids)[*len] = gid; + *len = new_len; + return 0; +} + +static int search_group_list(struct group *gbufp, const char *username, oci_runtime_spec_process_user *puser) +{ + char **username_list = gbufp->gr_mem; + while (username_list != NULL && *username_list != NULL) { + if (strcmp(*username_list, username) == 0) { + if (append_additional_gids(gbufp->gr_gid, &puser->additional_gids, &puser->additional_gids_len)) { + ERROR("Failed to append additional groups"); + return -1; + } + break; + } + username_list++; + } + return 0; +} + +static bool check_group_found(const char *group, const struct group *gbufp) +{ + int gret = -1; + long long n_grp = 0; + + if (group != NULL) { + gret = util_safe_llong(group, &n_grp); + } + + if (gret == 0 && n_grp == (long long)gbufp->gr_gid) { + return true; + } + if (gret != 0 && group != NULL && strcmp(group, gbufp->gr_name) == 0) { + return true; + } + + return false; +} + +static int do_proc_by_froup(FILE *f_group, const char *group, oci_runtime_spec_process_user *puser, + const char *matched_username, int *groupcnt) +{ + int errval = 0; + char buf[BUFSIZ] = { 0 }; + bool groupfound = false; + struct group grp, *gbufp = NULL; + + if (f_group == NULL) { + return 0; + } + + errval = fgetgrent_r(f_group, &grp, buf, sizeof(buf), &gbufp); + while (errval == 0 && gbufp != NULL) { + // Treat numeric group as valid GID + if (group == NULL) { + if (search_group_list(gbufp, matched_username, puser) != 0) { + return -1; + } + errval = fgetgrent_r(f_group, &grp, buf, sizeof(buf), &gbufp); + continue; + } + + groupfound = check_group_found(group, gbufp); + if (groupfound && *groupcnt != 1) { + // Continue search group list, but only take first found group + puser->gid = gbufp->gr_gid; + *groupcnt = 1; + } + errval = fgetgrent_r(f_group, &grp, buf, sizeof(buf), &gbufp); + } + + return 0; +} + +static int proc_by_fgroup(FILE *f_group, const char *group, oci_runtime_spec_process_user *puser, + const char *matched_username) +{ + int ret = 0; + int gret = -1; + int groupcnt = 0; + long long n_grp = 0; + + if (group != NULL || matched_username != NULL) { + if (do_proc_by_froup(f_group, group, puser, matched_username, &groupcnt) != 0) { + goto out; + } + + if (group != NULL && groupcnt == 0) { + gret = util_safe_llong(group, &n_grp); + // group is not a valid numeric GID + if (gret != 0) { + ERROR("Unable to find group '%s'", group); + lcrd_set_error_message("Unable to find group '%s': no matching entries in group file", group); + ret = -1; + goto out; + } + if (n_grp < MINUID || n_grp > MAXUID) { + uids_gids_range_err_log(); + ret = -1; + goto out; + } + puser->gid = (gid_t)n_grp; + } + } + +out: + return ret; +} + +static int get_exec_user(const char *username, FILE *f_passwd, FILE *f_group, oci_runtime_spec_process_user *puser) +{ + int ret = 0; + char *tmp = NULL; + char *user = NULL; + char *group = NULL; + char *matched_username = NULL; + + // parse user and group by username + parse_user_group(username, &user, &group, &tmp); + + // proc by f_passwd + ret = proc_by_fpasswd(f_passwd, user, puser, &matched_username); + if (ret != 0) { + ret = -1; + goto cleanup; + } + + // proc by f_group + ret = proc_by_fgroup(f_group, group, puser, matched_username); + if (ret != 0) { + ret = -1; + goto cleanup; + } + +cleanup: + free(tmp); + return ret; +} + +static int append_additional_groups(const struct group *grp, struct group **groups, size_t *len) +{ + int ret = 0; + struct group *new_groups = NULL; + size_t new_len = *len + 1; + + ret = mem_realloc((void **)&new_groups, new_len * sizeof(struct group), *groups, (*len) * sizeof(struct group)); + if (ret != 0) { + ERROR("Out of memory"); + return -1; + } + *groups = new_groups; + (*groups)[*len].gr_name = util_strdup_s(grp->gr_name); + (*groups)[*len].gr_gid = grp->gr_gid; + *len = new_len; + return 0; +} + +static bool group_matched(const char *group, const struct group *gbufp) +{ + bool matched = false; + long long n_gid = 0; + int gret = -1; + + if (group == NULL || gbufp == NULL) { + return false; + } + + gret = util_safe_llong(group, &n_gid); + if (strcmp(group, gbufp->gr_name) == 0 || (gret == 0 && n_gid == gbufp->gr_gid)) { + matched = true; + } + + return matched; +} + +static int get_one_additional_group(const char *additional_group, struct group *groups, size_t groups_len, + oci_runtime_spec_process_user *puser) +{ + int ret = 0; + int gret = -1; + long long n_gid = 0; + bool found = false; + size_t j; + + for (j = 0; groups != NULL && j < groups_len; j++) { + // Only take the first founded group + if (group_matched(additional_group, &groups[j])) { + found = true; + if (append_additional_gids(groups[j].gr_gid, &puser->additional_gids, &puser->additional_gids_len)) { + ERROR("Failed to append additional groups"); + ret = -1; + goto out; + } + break; + } + } + + if (!found) { + gret = util_safe_llong(additional_group, &n_gid); + if (gret != 0) { + ERROR("Unable to find group %s", additional_group); + lcrd_set_error_message("Unable to find group %s", additional_group); + ret = -1; + goto out; + } + if (n_gid < MINUID || n_gid > MAXUID) { + uids_gids_range_err_log(); + ret = -1; + goto out; + } + if (append_additional_gids((gid_t)n_gid, &puser->additional_gids, &puser->additional_gids_len)) { + ERROR("Failed to append additional groups"); + ret = -1; + goto out; + } + } + +out: + return ret; +} + + +int get_additional_groups(char **additional_groups, size_t additional_groups_len, + FILE *f_group, oci_runtime_spec_process_user *puser) +{ + int ret = 0; + size_t i; + size_t groups_len = 0; + char buf[BUFSIZ] = { 0 }; + struct group grp; + struct group *gbufp = NULL; + struct group *groups = NULL; + + while (f_group != NULL && fgetgrent_r(f_group, &grp, buf, sizeof(buf), &gbufp) == 0) { + for (i = 0; i < additional_groups_len; i++) { + if (!group_matched(additional_groups[i], gbufp)) { + continue; + } + if (append_additional_groups(gbufp, &groups, &groups_len)) { + ret = -1; + goto cleanup; + } + } + } + + for (i = 0; i < additional_groups_len; i++) { + ret = get_one_additional_group(additional_groups[i], groups, groups_len, puser); + if (ret != 0) { + ret = -1; + goto cleanup; + } + } + + +cleanup: + for (i = 0; groups != NULL && i < groups_len; i++) { + free(groups[i].gr_name); + } + free(groups); + + return ret; +} + +static int resolve_basefs(const char *basefs, char **resolved_basefs) +{ + struct stat s; + char real_path[PATH_MAX + 1] = { 0 }; + + if (strlen(basefs) > PATH_MAX || !realpath(basefs, real_path)) { + ERROR("invalid file path %s", basefs); + return -1; + } + + if (stat(real_path, &s) < 0) { + ERROR("stat failed, error: %s", strerror(errno)); + return -1; + } + + if ((s.st_mode & S_IFMT) == S_IFDIR) { + *resolved_basefs = util_strdup_s(real_path); + } else { + *resolved_basefs = util_strdup_s("/"); + } + + return 0; +} + +int get_user(const char *basefs, const host_config *hc, const char *userstr, oci_runtime_spec_process_user *puser) +{ + int ret = 0; + FILE *f_passwd = NULL; + FILE *f_group = NULL; + char *resolved_basefs = NULL; + + if (basefs == NULL || puser == NULL || hc == NULL) { + return -1; + } + + ret = resolve_basefs(basefs, &resolved_basefs); + if (ret != 0) { + goto cleanup; + } + + ret = read_user_file(resolved_basefs, UnixPasswdPath, &f_passwd); + if (ret != 0) { + goto cleanup; + } + ret = read_user_file(resolved_basefs, UnixGroupPath, &f_group); + if (ret != 0) { + goto cleanup; + } + + ret = get_exec_user(userstr, f_passwd, f_group, puser); + if (ret != 0) { + goto cleanup; + } + + if (hc->group_add != NULL && hc->group_add_len > 0) { + if (hc->group_add_len > LIST_SIZE_MAX) { + ERROR("Too many groups to add, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many groups to add, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto cleanup; + } + // Rewind f_group to serach from beginning again. + if (f_group != NULL) { + rewind(f_group); + } + ret = get_additional_groups(hc->group_add, hc->group_add_len, f_group, puser); + if (ret != 0) { + goto cleanup; + } + } + +cleanup: + if (f_passwd != NULL) { + fclose(f_passwd); + } + if (f_group != NULL) { + fclose(f_group); + } + + free(resolved_basefs); + + return ret; +} + +static int make_sure_oci_spec_porcess_user(oci_runtime_spec *oci_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_process(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->process->user == NULL) { + oci_spec->process->user = util_common_calloc_s(sizeof(oci_runtime_spec_process_user)); + if (oci_spec->process->user == NULL) { + return -1; + } + } + return 0; +} + +int merge_user(const char *basefs, oci_runtime_spec *oci_spec, const host_config *hc, const char *user) +{ + int ret = 0; + + ret = make_sure_oci_spec_porcess_user(oci_spec); + if (ret < 0) { + goto out; + } + + if (get_user(basefs, hc, user, oci_spec->process->user)) { + ERROR("Failed to get user with '%s'", user ? user : ""); + ret = -1; + goto out; + } + +out: + return ret; +} + +char *oci_container_get_env(const oci_runtime_spec *oci_spec, const char *key) +{ + const oci_runtime_spec_process *op = NULL; + + if (oci_spec == NULL) { + ERROR("nil oci_spec"); + return NULL; + } + if (oci_spec->process == NULL) { + ERROR("nil oci_spec->process"); + return NULL; + } + + op = oci_spec->process; + return util_env_get_val(op->env, op->env_len, key, strlen(key)); +} + +int make_sure_oci_spec_linux(oci_runtime_spec *oci_spec) +{ + if (oci_spec->linux == NULL) { + oci_spec->linux = util_common_calloc_s(sizeof(oci_runtime_config_linux)); + if (oci_spec->linux == NULL) { + return -1; + } + } + return 0; +} + +int make_sure_oci_spec_process(oci_runtime_spec *oci_spec) +{ + if (oci_spec->process == NULL) { + oci_spec->process = util_common_calloc_s(sizeof(oci_runtime_spec_process)); + if (oci_spec->process == NULL) { + return -1; + } + } + return 0; +} + +int make_sure_oci_spec_linux_resources(oci_runtime_spec *oci_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->linux->resources == NULL) { + oci_spec->linux->resources = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources)); + if (oci_spec->linux->resources == NULL) { + return -1; + } + } + return 0; +} + +int make_sure_oci_spec_linux_resources_blkio(oci_runtime_spec *oci_spec) +{ + int ret; + + ret = make_sure_oci_spec_linux_resources(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->linux->resources->block_io == NULL) { + oci_spec->linux->resources->block_io = + util_common_calloc_s(sizeof(oci_runtime_config_linux_resources_block_io)); + if (oci_spec->linux->resources->block_io == NULL) { + return -1; + } + } + return 0; +} + +int merge_ulimits_pre(oci_runtime_spec *oci_spec, size_t host_ulimits_len) +{ + int ret; + size_t new_size, old_size, tmp; + oci_runtime_spec_process_rlimits_element **rlimits_temp = NULL; + + ret = make_sure_oci_spec_process(oci_spec); + if (ret < 0) { + goto out; + } + + tmp = SIZE_MAX / sizeof(oci_runtime_spec_process_rlimits_element *) - oci_spec->process->rlimits_len; + if (host_ulimits_len > tmp) { + ERROR("Too many rlimits to merge!"); + ret = -1; + goto out; + } + old_size = oci_spec->process->rlimits_len * sizeof(oci_runtime_spec_process_rlimits_element *); + new_size = (oci_spec->process->rlimits_len + host_ulimits_len) * sizeof(oci_runtime_spec_process_rlimits_element *); + ret = mem_realloc((void **)&rlimits_temp, new_size, oci_spec->process->rlimits, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for rlimits"); + ret = -1; + goto out; + } + oci_spec->process->rlimits = rlimits_temp; +out: + return ret; +} + +int trans_ulimit_to_rlimit(oci_runtime_spec_process_rlimits_element **rlimit_dst, + const host_config_ulimits_element *ulimit) +{ +#define RLIMIT_PRE "RLIMIT_" + int ret = 0; + size_t j, namelen; + char *typename = NULL; + oci_runtime_spec_process_rlimits_element *rlimit = NULL; + + // name + "RLIMIT_" + '\0' + if (strlen(ulimit->name) > ((SIZE_MAX - strlen(RLIMIT_PRE)) - 1)) { + ERROR("Invalid ulimit name"); + return -1; + } + namelen = strlen(ulimit->name) + strlen(RLIMIT_PRE) + 1; + typename = util_common_calloc_s(namelen); + if (typename == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (strcat_s(typename, namelen, RLIMIT_PRE) != EOK) { + ERROR("Failed to cat string"); + ret = -1; + goto out; + } + for (j = 0; j < strlen(ulimit->name); j++) { + typename[j + strlen(RLIMIT_PRE)] = (char)toupper((int)(ulimit->name[j])); + } + + rlimit = util_common_calloc_s(sizeof(oci_runtime_spec_process_rlimits_element)); + if (rlimit == NULL) { + ERROR("Failed to malloc memory for rlimit"); + ret = -1; + goto out; + } + rlimit->type = typename; + rlimit->soft = (uint64_t)ulimit->soft; + rlimit->hard = (uint64_t)ulimit->hard; + + *rlimit_dst = rlimit; +out: + if (ret < 0) { + free(typename); + } + return ret; +} + +static int do_merge_one_ulimit(const oci_runtime_spec *oci_spec, oci_runtime_spec_process_rlimits_element *rlimit) +{ + size_t j; + bool exists = false; + + for (j = 0; j < oci_spec->process->rlimits_len; j++) { + if (oci_spec->process->rlimits[j]->type == NULL) { + ERROR("rlimit type is empty"); + UTIL_FREE_AND_SET_NULL(rlimit->type); + free(rlimit); + return -1; + } + if (strcmp(oci_spec->process->rlimits[j]->type, rlimit->type) == 0) { + exists = true; + break; + } + } + if (exists) { + /* ulimit exist, discard default ulimit */ + UTIL_FREE_AND_SET_NULL(rlimit->type); + free(rlimit); + } else { + oci_spec->process->rlimits[oci_spec->process->rlimits_len] = rlimit; + oci_spec->process->rlimits_len++; + } + + return 0; +} + +static int merge_one_ulimit(const oci_runtime_spec *oci_spec, const host_config_ulimits_element *ulimit) +{ + oci_runtime_spec_process_rlimits_element *rlimit = NULL; + + if (trans_ulimit_to_rlimit(&rlimit, ulimit) != 0) { + return -1; + } + + return do_merge_one_ulimit(oci_spec, rlimit); +} + +static int merge_ulimits(oci_runtime_spec *oci_spec, host_config_ulimits_element **ulimits, size_t ulimits_len) +{ + int ret = 0; + size_t i = 0; + + if (oci_spec == NULL || ulimits == NULL || ulimits_len == 0) { + return -1; + } + + ret = merge_ulimits_pre(oci_spec, ulimits_len); + if (ret < 0) { + goto out; + } + + for (i = 0; i < ulimits_len; i++) { + ret = merge_one_ulimit(oci_spec, ulimits[i]); + if (ret != 0) { + ret = -1; + goto out; + } + } +out: + return ret; +} + +int merge_global_ulimit(oci_runtime_spec *oci_spec) +{ + int ret = 0; + host_config_ulimits_element **ulimits = NULL; + size_t ulimits_len; + + if (conf_get_lcrd_default_ulimit(&ulimits) != 0) { + ERROR("Failed to get lcrd default ulimit"); + ret = -1; + goto out; + } + + if (ulimits != NULL) { + ulimits_len = ulimit_array_len(ulimits); + if (merge_ulimits(oci_spec, ulimits, ulimits_len)) { + ret = -1; + goto out; + } + } + +out: + free_default_ulimit(ulimits); + return ret; +} + diff --git a/src/services/execution/spec/specs_extend.h b/src/services/execution/spec/specs_extend.h new file mode 100644 index 0000000..1838376 --- /dev/null +++ b/src/services/execution/spec/specs_extend.h @@ -0,0 +1,65 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide specs definition + ******************************************************************************/ +#ifndef __SPECS_EXTEND_H__ +#define __SPECS_EXTEND_H__ + +#include +#include "liblcrd.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "container_config_v2.h" +#include "oci_runtime_hooks.h" +#include "oci_runtime_spec.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +int make_sure_oci_spec_linux(oci_runtime_spec *oci_spec); + +int make_sure_oci_spec_process(oci_runtime_spec *oci_spec); + +int make_sure_oci_spec_linux_resources(oci_runtime_spec *oci_spec); + +int make_sure_oci_spec_linux_resources_blkio(oci_runtime_spec *oci_spec); + +int merge_hooks(oci_runtime_spec_hooks *dest, oci_runtime_spec_hooks *src); + +int merge_global_hook(oci_runtime_spec *oci_spec); + +int merge_global_ulimit(oci_runtime_spec *oci_spec); + +int merge_ulimits_pre(oci_runtime_spec *oci_spec, size_t host_ulimits_len); + +int trans_ulimit_to_rlimit(oci_runtime_spec_process_rlimits_element **rlimit_dst, + const host_config_ulimits_element *ulimit); + +int make_userns_remap(oci_runtime_spec *container, const char *user_remap); + +int merge_env(oci_runtime_spec *oci_spec, const char **env, size_t env_len); + +int merge_env_target_file(oci_runtime_spec *oci_spec, const char *env_target_file); + +int merge_user(const char *basefs, oci_runtime_spec *oci_spec, const host_config *hc, const char *user); + +char *oci_container_get_env(const oci_runtime_spec *oci_spec, const char *key); + +int get_user(const char *basefs, const host_config *hc, const char *userstr, oci_runtime_spec_process_user *puser); + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif diff --git a/src/services/execution/spec/specs_mount.c b/src/services/execution/spec/specs_mount.c new file mode 100644 index 0000000..572539b --- /dev/null +++ b/src/services/execution/spec/specs_mount.c @@ -0,0 +1,2097 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container specs functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "securec.h" +#include "log.h" +#include "oci_runtime_spec.h" +#include "oci_runtime_hooks.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "utils.h" +#include "config.h" +#include "path.h" +#include "lcrd_config.h" +#include "namespace.h" +#include "parse_common.h" +#include "specs_mount.h" +#include "specs_extend.h" + +static int get_devices(const char *dir, char ***devices, size_t *device_len, + int recursive_depth); + + +static int append_additional_mounts(oci_runtime_spec *oci_spec, const char *type) +{ + int ret = 0; + size_t i = 0; + size_t j = 0; + size_t new_size = 0; + size_t old_size = 0; + defs_mount **spec_mounts = NULL; + defs_mount *tmp_mount = NULL; + char **files = NULL; + size_t files_len = 0; + char *mount_options[] = {"nosuid", "noexec", "nodev"}; + size_t mount_options_len = 0; + char *net_files[] = {"/proc/sys/net"}; + char *ipc_files[] = { + "/proc/sys/kernel/shmmax", + "/proc/sys/kernel/shmmni", + "/proc/sys/kernel/shmall", + "/proc/sys/kernel/shm_rmid_forced", + "/proc/sys/kernel/msgmax", + "/proc/sys/kernel/msgmni", + "/proc/sys/kernel/msgmnb", + "/proc/sys/kernel/sem", + "/proc/sys/fs/mqueue" + }; + + if (strcmp(type, "net") == 0) { + files = &net_files[0]; + files_len = sizeof(net_files) / sizeof(net_files[0]); + } else if (strcmp(type, "ipc") == 0) { + files = &ipc_files[0]; + files_len = sizeof(ipc_files) / sizeof(ipc_files[0]); + } else { + return -1; + } + + if (files_len > (SIZE_MAX / sizeof(defs_mount *)) - oci_spec->mounts_len) { + ERROR("Too many mounts to append!"); + ret = -1; + goto out; + } + + mount_options_len = sizeof(mount_options) / sizeof(mount_options[0]); + new_size = (oci_spec->mounts_len + files_len) * sizeof(defs_mount *); + old_size = oci_spec->mounts_len * sizeof(defs_mount *); + + ret = mem_realloc((void **)&spec_mounts, new_size, oci_spec->mounts, old_size); + if (ret != 0) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + oci_spec->mounts = spec_mounts; + for (i = 0; i < files_len; i++) { + tmp_mount = util_common_calloc_s(sizeof(defs_mount)); + if (tmp_mount == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + tmp_mount->source = util_strdup_s(files[i]); + tmp_mount->destination = util_strdup_s(files[i]); + tmp_mount->type = util_strdup_s("proc"); + for (j = 0; j < mount_options_len; j++) { + ret = util_array_append(&(tmp_mount->options), mount_options[j]); + if (ret != 0) { + ERROR("append mount options to array failed"); + ret = -1; + goto out; + } + tmp_mount->options_len++; + } + + spec_mounts[oci_spec->mounts_len++] = tmp_mount; + tmp_mount = NULL; + } + +out: + free_defs_mount(tmp_mount); + return ret; +} + +int adapt_settings_for_mounts(oci_runtime_spec *oci_spec, container_custom_config *custom_spec) +{ + size_t i, array_str_len; + int ret = 0; + char **array_str = NULL; + + if (custom_spec == NULL) { + return -1; + } + + array_str = util_string_split(custom_spec->ns_change_opt, ','); + if (array_str == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + array_str_len = util_array_len(array_str); + + for (i = 0; i < array_str_len; i++) { + if (strcmp(array_str[i], "net") == 0) { + ret = append_additional_mounts(oci_spec, "net"); + } else { + ret = append_additional_mounts(oci_spec, "ipc"); + } + if (ret != 0) { + goto out; + } + } + +out: + util_free_array(array_str); + return ret; +} + +static bool valid_dirent_info(const char *dir, const struct dirent *info_archivo) +{ + size_t i = 0; + int nret = 0; + char *pdot = NULL; + char fullpath[PATH_MAX] = {0}; + char *skip_files[] = { "/dev/pts", "/dev/shm", "/dev/fd", "/dev/mqueue", "/dev/console" }; + + if (dir == NULL || info_archivo == NULL) { + return false; + } + + // skip . .. + if (strncmp(info_archivo->d_name, ".", PATH_MAX) == 0 || + strncmp(info_archivo->d_name, "..", PATH_MAX) == 0) { + return false; + } + + // do not map device which name containes ":" + pdot = strstr(info_archivo->d_name, ":"); + if (pdot != NULL) { + INFO("Skipping device %s include \":\"", info_archivo->d_name); + return false; + } + + nret = sprintf_s(fullpath, PATH_MAX, "%s/%s", dir, info_archivo->d_name); + if (nret < 0) { + ERROR("get_devices: Failed to combine device path"); + return false; + } + + for (i = 0; i < sizeof(skip_files) / sizeof(skip_files[0]); i++) { + if (strncmp(fullpath, skip_files[i], PATH_MAX) == 0) { + INFO("Skipping device: %s", fullpath); + return false; + } + } + + return true; +} + +static int pack_devices(const char *fullpath, char ***devices, size_t *device_len) +{ + int ret = 0; + size_t tmp_length, new_size = 0, old_size = 0; + char **tmp_device = NULL; + + tmp_length = *device_len; + if (tmp_length > (SIZE_MAX / sizeof(char *)) - 1) { + ERROR("Too many devices"); + ret = -1; + goto out; + } + + old_size = sizeof(char *) * tmp_length; + tmp_length += 1; + new_size = sizeof(char *) * tmp_length; + + ret = mem_realloc((void **)&tmp_device, new_size, *devices, old_size); + if (ret != 0) { + ERROR("get_devices: Failed to realloc memory"); + ret = -1; + goto out; + } + + tmp_device[tmp_length - 1] = util_strdup_s(fullpath); + *devices = tmp_device; + *device_len = tmp_length; + +out: + return ret; +} + +static int get_devices_in_path(const char *fullpath, int recursive_depth, char ***devices, size_t *device_len) +{ + int ret = 0; + struct stat fileStat; + + if (lstat(fullpath, &fileStat) != 0) { + ERROR("Failed to get(%s) file stat.", fullpath); + ret = 0; + goto out; + } + + // scan device recursively + if (S_ISDIR(fileStat.st_mode)) { + ret = get_devices(fullpath, devices, device_len, (recursive_depth + 1)); + if (ret != 0) { + INFO("get_devices: Failed in path: %s", fullpath); + ret = -1; + goto out; + } + } else if (S_ISCHR(fileStat.st_mode) || S_ISBLK(fileStat.st_mode)) { + if (pack_devices(fullpath, devices, device_len) != 0) { + ret = -1; + goto out; + } + } else { + INFO("Skip device %s, mode(%o) not support to merge.", fullpath, fileStat.st_mode & S_IFMT); + } + +out: + return ret; +} + +/* get_devices: retrieve all devices under dir + * when errors occurs, caller should free memory pointing to *devices + */ +static int get_devices(const char *dir, char ***devices, size_t *device_len, + int recursive_depth) +{ + int nret = 0; + char *fullpath = NULL; + DIR *midir = NULL; + struct dirent *info_archivo = NULL; + + if ((recursive_depth + 1) > MAX_PATH_DEPTH) { + ERROR("Reach the max dev path depth:%s", dir); + return -1; + } + midir = opendir(dir); + if (midir == NULL) { + ERROR("get_devices: Error in opendir"); + return -1; + } + info_archivo = readdir(midir); + for (; info_archivo != NULL; info_archivo = readdir(midir)) { + if (!valid_dirent_info(dir, info_archivo)) { + continue; + } + + fullpath = util_common_calloc_s(PATH_MAX); + if (fullpath == NULL) { + ERROR("Memory out"); + closedir(midir); + return -1; + } + nret = sprintf_s(fullpath, PATH_MAX, "%s/%s", dir, info_archivo->d_name); + if (nret < 0) { + ERROR("get_devices: Failed to combine device path"); + closedir(midir); + free(fullpath); + return -1; + } + + if (get_devices_in_path(fullpath, recursive_depth, devices, device_len) != 0) { + closedir(midir); + free(fullpath); + return -1; + } + + free(fullpath); + fullpath = NULL; + } + + closedir(midir); + return 0; +} + +#define DefaultPropagationMode "rprivate" +#define DefaultROMode "rw" +#define DefaultRBind "rbind" + +static int fill_mounts_readonly_item(const char *value, defs_mount *mount_element) +{ + char *romode = DefaultROMode; + + if (mount_element == NULL) { + return 2; + } + + if (value != NULL) { + if (util_valid_value_true(value)) { + romode = "ro"; + } else if (util_valid_value_false(value)) { + /* use default value rw */ + } else { + ERROR("invalid value for readonly: %s", value); + return 2; + } + } + + if (util_array_append(&mount_element->options, romode)) { + ERROR("append ro mode to array failed"); + return 2; + } + mount_element->options_len++; + + return 0; +} + +/* + * 0: success + * 1: ignore this item, continue + * 2: failed + */ +static int fill_mounts_item(const char *key, const char *value, defs_mount *mount_element, bool *has_ro, bool *has_pro) +{ + if (value == NULL && !util_valid_key_ro(key)) { + ERROR("unsupported item %s", key); + return 1; + } + + if (util_valid_key_type(key)) { + free(mount_element->type); + mount_element->type = util_strdup_s(value); + } else if (util_valid_key_src(key)) { + free(mount_element->source); + mount_element->source = util_strdup_s(value); + } else if (util_valid_key_dst(key)) { + free(mount_element->destination); + mount_element->destination = util_strdup_s(value); + } else if (util_valid_key_ro(key)) { + int ret = fill_mounts_readonly_item(value, mount_element); + if (ret != 0) { + return ret; + } + + *has_ro = true; + } else if (util_valid_key_propagation(key)) { + if (!util_valid_propagation_mode(value)) { + ERROR("invalid propagation mode %s", value); + return 2; + } + + if (util_array_append(&mount_element->options, value)) { + ERROR("append bind propagation to array failed"); + return 2; + } + mount_element->options_len++; + *has_pro = true; + } else if (util_valid_key_selinux(key)) { + if (!util_valid_label_mode(value)) { + ERROR("invalid bind selinux opts %s", value); + return 2; + } + + /* This option is not supported currently. Hasen does't want to modify + * code if it's supported in future, so we support it in interface but + * not implement it currently. + */ + WARN("Valid bind selinux opts %s found but not configured for now", value); + } else { + ERROR("unsupported item %s", key); + return 2; + } + + return 0; +} + +static int check_mount_element(const defs_mount *mount_element) +{ + int ret = 0; + + if (mount_element == NULL) { + ret = EINVALIDARGS; + goto out; + } + + if (mount_element->type == NULL) { + ERROR("type is requested"); + ret = EINVALIDARGS; + goto out; + } + + if (strcmp(mount_element->type, "squashfs") && + strcmp(mount_element->type, "bind")) { + ERROR("invalid type %s, only support squashfs and bind", mount_element->type); + ret = EINVALIDARGS; + goto out; + } + + if (mount_element->source == NULL) { + ERROR("source is requested"); + ret = EINVALIDARGS; + goto out; + } + + if (mount_element->source[0] != '/') { + ERROR("source should be absolute path"); + ret = EINVALIDARGS; + goto out; + } + + if (mount_element->destination == NULL) { + ERROR("destination is requested"); + ret = EINVALIDARGS; + goto out; + } + + if (mount_element->destination[0] != '/') { + ERROR("destination should be absolute path"); + ret = EINVALIDARGS; + goto out; + } +out: + return ret; +} + +static int append_default_mount_options(defs_mount *mount_element, bool has_ro, bool has_pro) +{ + int ret = 0; + + if (mount_element == NULL) { + ret = -1; + goto out; + } + + if (strcmp(mount_element->type, "bind") == 0) { + if (!has_ro) { + ret = util_array_append(&mount_element->options, DefaultROMode); + if (ret != 0) { + ERROR("append default ro mode to array failed"); + ret = -1; + goto out; + } + mount_element->options_len++; + } + + if (!has_pro) { + ret = util_array_append(&mount_element->options, DefaultPropagationMode); + if (ret != 0) { + ERROR("append default propagation mode to array failed"); + ret = -1; + goto out; + } + mount_element->options_len++; + } + + ret = util_array_append(&mount_element->options, DefaultRBind); + if (ret != 0) { + ERROR("append default rbind to array failed"); + ret = -1; + goto out; + } + mount_element->options_len++; + } + +out: + return ret; +} + +defs_mount *parse_mount(const char *mount) +{ + char **items = NULL; + defs_mount *mount_element = NULL; + int ret = 0; + size_t items_len = 0; + size_t i = 0; + char **kv = NULL; + bool has_ro = false; + bool has_pro = false; + char dstpath[PATH_MAX] = {0}; + + if (mount == NULL) { + ERROR("invalid NULL param"); + ret = EINVALIDARGS; + goto out; + } + if (!mount[0]) { + ERROR("mount can't be empty"); + ret = EINVALIDARGS; + goto out; + } + + mount_element = util_common_calloc_s(sizeof(defs_mount)); + if (mount_element == NULL) { + ERROR("Out of memory"); + return NULL; + } + + items = util_string_split(mount, ','); + if (items == NULL) { + ERROR("split mount %s failed", mount); + ret = -1; + goto out; + } + + items_len = util_array_len(items); + + for (i = 0; i < items_len; i++) { + kv = util_string_split(items[i], '='); + if (kv == NULL) { + continue; + } + + ret = fill_mounts_item(kv[0], kv[1], mount_element, &has_ro, &has_pro); + if (ret == 1) { /* ignore this item */ + ret = 0; + util_free_array(kv); + kv = NULL; + continue; + } else if (ret == 2) { /* invalid args */ + ret = EINVALIDARGS; + goto out; + } + util_free_array(kv); + kv = NULL; + } + + ret = check_mount_element(mount_element); + if (ret != 0) { + goto out; + } + + if (!cleanpath(mount_element->destination, dstpath, sizeof(dstpath))) { + ERROR("failed to get clean path"); + ret = EINVALIDARGS; + goto out; + } + + free(mount_element->destination); + mount_element->destination = util_strdup_s(dstpath); + if (mount_element->destination == NULL) { + ERROR("out of memory"); + ret = -1; + goto out; + } + + /* append default options if it's bind mount */ + ret = append_default_mount_options(mount_element, has_ro, has_pro); + if (ret != 0) { + goto out; + } + +out: + if (ret != 0) { + free_defs_mount(mount_element); + mount_element = NULL; + } + + util_free_array(kv); + util_free_array(items); + + return mount_element; +} + +static int check_volume_element(const char *volume) +{ + int ret = 0; + + if (volume == NULL || !strcmp(volume, "")) { + ERROR("Volume can't be empty"); + ret = -1; + return ret; + } + + if (volume[0] == ':' || volume[strlen(volume) - 1] == ':') { + ERROR("Delimiter ':' can't be the first or the last character"); + ret = -1; + return ret; + } + + return ret; +} + +static int get_src_dst_mode_by_volume(const char *volume, defs_mount *mount_element, char ***modes) +{ + int ret = 0; + size_t alen = 0; + char **array = NULL; + + // split volume to src:dest:mode + array = util_string_split(volume, ':'); + if (array == NULL) { + ERROR("Out of memory"); + ret = -1; + goto free_out; + } + + alen = util_array_len(array); + switch (alen) { + case 1: + ERROR("Not supported volume format '%s'", volume); + ret = -1; + break; + case 2: + if (util_valid_mount_mode(array[1])) { + // Destination + Mode is not a valid volume - volumes + // cannot include a mode. eg /foo:rw + ERROR("Invalid volume specification '%s'", volume); + lcrd_set_error_message("Invalid volume specification '%s',Invalid mode:%s", volume, array[1]); + ret = -1; + break; + } + mount_element->source = util_strdup_s(array[0]); + mount_element->destination = util_strdup_s(array[1]); + break; + case 3: + mount_element->source = util_strdup_s(array[0]); + mount_element->destination = util_strdup_s(array[1]); + if (!util_valid_mount_mode(array[2])) { + ERROR("Invalid volume specification '%s'", volume); + lcrd_set_error_message("Invalid volume specification '%s'.Invalid mode:%s", volume, array[2]); + ret = -1; + break; + } + *modes = util_string_split(array[2], ','); + if (*modes == NULL) { + ERROR("Out of memory"); + ret = -1; + break; + } + + break; + default: + ERROR("Invalid volume specification '%s'", volume); + ret = -1; + break; + } + if (ret != 0) { + goto free_out; + } + + if (mount_element->source[0] != '/' || mount_element->destination[0] != '/' || + strcmp(mount_element->destination, "/") == 0) { + ERROR("Invalid volume: path must be absolute, and destination can't be '/'"); + ret = -1; + goto free_out; + } + +free_out: + util_free_array(array); + return ret; +} + +defs_mount *parse_volume(const char *volume) +{ + int ret = 0; + size_t i = 0, mlen = 0; + defs_mount *mount_element = NULL; + char **modes = NULL; + char dstpath[PATH_MAX] = { 0x00 }; + char *rw = "rw", *pro = DefaultPropagationMode; + + ret = check_volume_element(volume); + if (ret != 0) { + goto free_out; + } + + mount_element = util_common_calloc_s(sizeof(defs_mount)); + if (mount_element == NULL) { + ERROR("Out of memory"); + return NULL; + } + + ret = get_src_dst_mode_by_volume(volume, mount_element, &modes); + if (ret != 0) { + goto free_out; + } + + mlen = util_array_len(modes); + for (i = 0; i < mlen; i++) { + if (util_valid_rw_mode(modes[i])) { + rw = modes[i]; + } else if (util_valid_propagation_mode(modes[i])) { + pro = modes[i]; + } else if (util_valid_label_mode(modes[i])) { + WARN("Valid mode '%s' found but not configured for now", modes[i]); + } else if (util_valid_copy_mode(modes[i])) { + WARN("Valid mode '%s' found but not configured for now", modes[i]); + } + } + + if (!cleanpath(mount_element->destination, dstpath, sizeof(dstpath))) { + ERROR("Failed to get clean path"); + ret = -1; + goto free_out; + } + free(mount_element->destination); + mount_element->destination = util_strdup_s(dstpath); + + mount_element->options = util_common_calloc_s(3 * sizeof(char *)); + if (mount_element->options == NULL) { + ERROR("Out of memory"); + mount_element->options_len = 0; + ret = -1; + goto free_out; + } + mount_element->options[0] = util_strdup_s(rw); + mount_element->options[1] = util_strdup_s(pro); + mount_element->options[2] = util_strdup_s("rbind"); + mount_element->options_len = 3; + mount_element->type = util_strdup_s("bind"); + +free_out: + util_free_array(modes); + if (ret != 0) { + free_defs_mount(mount_element); + mount_element = NULL; + } + return mount_element; +} + +static host_config_devices_element *parse_one_device(const char *device_path, const char *dir_host, + const char *dir_container, const char *permissions) +{ + int nret = 0; + const char *cgroup_permissions = NULL; + host_config_devices_element *device_map = NULL; + char tmp_container_path[PATH_MAX] = {0}; + + if (device_path == NULL || !strcmp(device_path, "")) { + ERROR("devices can't be empty"); + return NULL; + } + + cgroup_permissions = permissions ? permissions : "rwm"; + + device_map = util_common_calloc_s(sizeof(host_config_devices_element)); + if (device_map == NULL) { + ERROR("Out of memory"); + return NULL; + } + + device_map->path_on_host = util_strdup_s(device_path); + if (dir_container != NULL) { + nret = sprintf_s(tmp_container_path, sizeof(tmp_container_path), "%s/%s", + dir_container, device_path + strlen(dir_host)); + if (nret < 0 || (unsigned int)nret >= sizeof(tmp_container_path)) { + ERROR("Failed to sprintf device path in container %s/%s", dir_container, device_path + strlen(dir_host)); + goto erro_out; + } + device_map->path_in_container = util_strdup_s(tmp_container_path); + } else { + device_map->path_in_container = util_strdup_s(device_path); + } + + device_map->cgroup_permissions = util_strdup_s(cgroup_permissions); + + if (device_map->path_on_host == NULL || device_map->path_in_container == NULL || + device_map->cgroup_permissions == NULL) { + goto erro_out; + } else { + return device_map; + } + +erro_out: + free_host_config_devices_element(device_map); + return NULL; +} + +static int get_devices_from_path(const host_config_devices_element *dev_map, oci_runtime_defs_linux_device *spec_dev, + oci_runtime_defs_linux_device_cgroup *spec_dev_cgroup) +{ + int ret = 0; + struct stat st; + char *dev_type = NULL; + unsigned int file_mode = 0; + + if (dev_map == NULL || spec_dev == NULL || spec_dev_cgroup == NULL) { + return -1; + } + + ret = stat(dev_map->path_on_host, &st); + if (ret < 0) { + ERROR("device %s no exists", dev_map->path_on_host); + lcrd_set_error_message("Error gathering device information while adding device \"%s\",stat %s: " + "no such file or directory", dev_map->path_on_host, dev_map->path_on_host); + return -1; + } + + file_mode = st.st_mode & 0777; + + /* check device type first */ + if (S_ISBLK(st.st_mode)) { + file_mode |= S_IFBLK; + dev_type = "b"; + } else if (S_ISCHR(st.st_mode)) { + file_mode |= S_IFCHR; + dev_type = "c"; + } else { + ERROR("Cannot determine the device number for device %s", + dev_map->path_on_host); + return -1; + } + + /* fill spec dev */ + spec_dev->major = (int64_t)major(st.st_rdev); + spec_dev->minor = (int64_t)minor(st.st_rdev); + spec_dev->uid = st.st_uid; + spec_dev->gid = st.st_gid; + spec_dev->file_mode = (int)file_mode; + spec_dev->type = util_strdup_s(dev_type); + spec_dev->path = util_strdup_s(dev_map->path_in_container); + + /* fill spec cgroup dev */ + spec_dev_cgroup->allow = true; + spec_dev_cgroup->access = util_strdup_s(dev_map->cgroup_permissions); + spec_dev_cgroup->type = util_strdup_s(dev_type); + spec_dev_cgroup->major = (int64_t)major(st.st_rdev); + spec_dev_cgroup->minor = (int64_t)minor(st.st_rdev); + + return 0; +} + +static int merge_custom_device(oci_runtime_defs_linux_device **out_spec_dev, + oci_runtime_defs_linux_device_cgroup **out_spec_dev_cgroup, + const host_config_devices_element *dev_map) +{ + int ret = 0; + oci_runtime_defs_linux_device *spec_dev = NULL; + oci_runtime_defs_linux_device_cgroup *spec_dev_cgroup = NULL; + + spec_dev = util_common_calloc_s(sizeof(oci_runtime_defs_linux_device)); + if (spec_dev == NULL) { + ERROR("Memory out"); + ret = -1; + goto erro_out; + } + + spec_dev_cgroup = util_common_calloc_s(sizeof(oci_runtime_defs_linux_device_cgroup)); + if (spec_dev_cgroup == NULL) { + ERROR("Memory out"); + ret = -1; + goto erro_out; + } + + ret = get_devices_from_path(dev_map, spec_dev, spec_dev_cgroup); + if (ret != 0) { + ERROR("Failed to get devices info"); + ret = -1; + goto erro_out; + } + + *out_spec_dev = spec_dev; + *out_spec_dev_cgroup = spec_dev_cgroup; + goto out; + +erro_out: + free_oci_runtime_defs_linux_device(spec_dev); + free_oci_runtime_defs_linux_device_cgroup(spec_dev_cgroup); +out: + return ret; +} + +static int get_weight_devices_from_path(const host_config_blkio_weight_device_element *weight_dev, + oci_runtime_defs_linux_block_io_device_weight *spec_weight_dev) +{ + int ret = 0; + struct stat st; + + if (weight_dev == NULL || spec_weight_dev == NULL) { + return -1; + } + + ret = stat(weight_dev->path, &st); + if (ret < 0) { + ERROR("Failed to get state of device:%s", weight_dev->path); + return -1; + } + + /* fill spec weight dev */ + spec_weight_dev->major = (int64_t)major(st.st_rdev); + spec_weight_dev->minor = (int64_t)minor(st.st_rdev); + spec_weight_dev->weight = weight_dev->weight; + /* Notes You MUST specify at least one of weight or leafWeight + * in a given entry, and MAY specify both + * docker did not specify the leaf weight + */ + spec_weight_dev->leaf_weight = 0; + + return 0; +} + +static int merge_host_config_blk_weight_device(oci_runtime_defs_linux_block_io_device_weight **out_spec_weight_dev, + const host_config_blkio_weight_device_element *weight_dev) +{ + int ret = 0; + oci_runtime_defs_linux_block_io_device_weight *spec_weight_dev = NULL; + + spec_weight_dev = util_common_calloc_s(sizeof(oci_runtime_defs_linux_block_io_device_weight)); + if (spec_weight_dev == NULL) { + ERROR("Memory out"); + ret = -1; + goto erro_out; + } + + ret = get_weight_devices_from_path(weight_dev, spec_weight_dev); + if (ret != 0) { + ERROR("Failed to get weight devices info"); + ret = -1; + goto erro_out; + } + + *out_spec_weight_dev = spec_weight_dev; + goto out; + +erro_out: + free_oci_runtime_defs_linux_block_io_device_weight(spec_weight_dev); + +out: + return ret; +} + +static int get_read_bps_devices_from_path(const host_config_blkio_device_read_bps_element *read_bps_dev, + oci_runtime_defs_linux_block_io_device_throttle *spec_read_bps_dev) +{ + int ret = 0; + struct stat st; + + if (read_bps_dev == NULL || spec_read_bps_dev == NULL) { + return -1; + } + + ret = stat(read_bps_dev->path, &st); + if (ret < 0) { + ERROR("Failed to get state of device:%s", read_bps_dev->path); + lcrd_set_error_message("no such file or directory: %s", read_bps_dev->path); + return -1; + } + + /* fill spec throttle read bps dev */ + spec_read_bps_dev->rate = read_bps_dev->rate; + spec_read_bps_dev->major = (int64_t)major(st.st_rdev); + spec_read_bps_dev->minor = (int64_t)minor(st.st_rdev); + + return 0; +} + +static int merge_host_config_blk_read_bps_device( + oci_runtime_defs_linux_block_io_device_throttle **out_spec_read_bps_dev, + const host_config_blkio_device_read_bps_element *blkio_device_read_bps) +{ + int ret = 0; + oci_runtime_defs_linux_block_io_device_throttle *spec_read_bps_dev = NULL; + + spec_read_bps_dev = util_common_calloc_s(sizeof(oci_runtime_defs_linux_block_io_device_throttle)); + if (spec_read_bps_dev == NULL) { + ERROR("Memory out"); + ret = -1; + goto erro_out; + } + + ret = get_read_bps_devices_from_path(blkio_device_read_bps, spec_read_bps_dev); + if (ret != 0) { + ERROR("Failed to get throttle read bps devices info"); + ret = -1; + goto erro_out; + } + + *out_spec_read_bps_dev = spec_read_bps_dev; + goto out; + +erro_out: + free_oci_runtime_defs_linux_block_io_device_throttle(spec_read_bps_dev); + +out: + return ret; +} + +static int get_write_bps_devices_from_path(const host_config_blkio_device_write_bps_element *write_bps_dev, + oci_runtime_defs_linux_block_io_device_throttle *spec_write_bps_dev) +{ + int ret = 0; + struct stat st; + + if (write_bps_dev == NULL || spec_write_bps_dev == NULL) { + return -1; + } + + ret = stat(write_bps_dev->path, &st); + if (ret < 0) { + ERROR("no such file or directory :%s", write_bps_dev->path); + lcrd_set_error_message("no such file or directory: %s", write_bps_dev->path); + return -1; + } + + /* fill spec throttle write bps dev */ + spec_write_bps_dev->rate = write_bps_dev->rate; + spec_write_bps_dev->major = (int64_t)major(st.st_rdev); + spec_write_bps_dev->minor = (int64_t)minor(st.st_rdev); + + return 0; +} + +static int merge_host_config_blk_write_bps_device( + oci_runtime_defs_linux_block_io_device_throttle **out_spec_write_bps_dev, + const host_config_blkio_device_write_bps_element *blkio_device_write_bps) +{ + int ret = 0; + oci_runtime_defs_linux_block_io_device_throttle *spec_write_bps_dev = NULL; + + spec_write_bps_dev = util_common_calloc_s(sizeof(oci_runtime_defs_linux_block_io_device_throttle)); + if (spec_write_bps_dev == NULL) { + ERROR("Memory out"); + ret = -1; + goto erro_out; + } + + ret = get_write_bps_devices_from_path(blkio_device_write_bps, spec_write_bps_dev); + if (ret != 0) { + ERROR("Failed to get throttle write bps devices info"); + ret = -1; + goto erro_out; + } + + *out_spec_write_bps_dev = spec_write_bps_dev; + goto out; + +erro_out: + free_oci_runtime_defs_linux_block_io_device_throttle(spec_write_bps_dev); + +out: + return ret; +} + +static int merge_all_devices(oci_runtime_spec *oci_spec, host_config_devices_element **dev_maps, + size_t devices_len, const char *permissions) +{ + int ret = 0; + size_t new_size = 0, old_size = 0; + size_t i = 0; + oci_runtime_defs_linux_device **spec_dev = NULL; + + ret = make_sure_oci_spec_linux_resources(oci_spec); + if (ret < 0) { + goto out; + } + + /* malloc for linux->device */ + if (devices_len > LIST_DEVICE_SIZE_MAX - oci_spec->linux->devices_len) { + ERROR("Too many linux devices to merge, the limit is %d", LIST_DEVICE_SIZE_MAX); + lcrd_set_error_message("Too many linux devices to merge, the limit is %d", LIST_DEVICE_SIZE_MAX); + ret = -1; + goto out; + } + new_size = (oci_spec->linux->devices_len + devices_len) * sizeof(oci_runtime_defs_linux_device *); + old_size = oci_spec->linux->devices_len * sizeof(oci_runtime_defs_linux_device *); + ret = mem_realloc((void **)&spec_dev, new_size, oci_spec->linux->devices, old_size); + if (ret != 0) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + oci_spec->linux->devices = spec_dev; + + /* malloc for cgroup->device */ + oci_runtime_defs_linux_device_cgroup **spec_cgroup_dev = NULL; + if (devices_len > SIZE_MAX / sizeof(oci_runtime_defs_linux_device_cgroup *) - + oci_spec->linux->resources->devices_len) { + ERROR("Too many cgroup devices to merge!"); + ret = -1; + goto out; + } + new_size = (oci_spec->linux->resources->devices_len + devices_len) + * sizeof(oci_runtime_defs_linux_device_cgroup *); + old_size = oci_spec->linux->resources->devices_len * sizeof(oci_runtime_defs_linux_device_cgroup *); + ret = mem_realloc((void **)&spec_cgroup_dev, new_size, oci_spec->linux->resources->devices, old_size); + if (ret != 0) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + oci_spec->linux->resources->devices = spec_cgroup_dev; + + for (i = 0; i < devices_len; i++) { + ret = merge_custom_device(&oci_spec->linux->devices[oci_spec->linux->devices_len], + &oci_spec->linux->resources->devices[oci_spec->linux->resources->devices_len], + dev_maps[i]); + if (ret != 0) { + ERROR("Failed to merge custom device"); + ret = -1; + goto out; + } + oci_spec->linux->devices_len++; + oci_spec->linux->resources->devices_len++; + } +out: + return ret; +} + +static void free_multi_dev_maps(host_config_devices_element **dev_maps, size_t devices_len) +{ + size_t i = 0; + + if (dev_maps == NULL) { + return; + } + + for (i = 0; i < devices_len; i++) { + free_host_config_devices_element(dev_maps[i]); + dev_maps[i] = NULL; + } + free(dev_maps); +} + +static host_config_devices_element **parse_multi_devices(const char *dir_host, const char *dir_container, + const char *permissions, char **devices, size_t devices_len) +{ + size_t i = 0; + host_config_devices_element **dev_maps = NULL; + + if (devices_len == 0) { + return NULL; + } + + if (devices_len > SIZE_MAX / sizeof(host_config_devices_element *)) { + ERROR("Too many devices"); + return NULL; + } + + dev_maps = util_common_calloc_s(devices_len * sizeof(host_config_devices_element *)); + if (dev_maps == NULL) { + ERROR("Memory out"); + return NULL; + } + + for (i = 0; i < devices_len; i++) { + dev_maps[i] = parse_one_device(devices[i], dir_host, dir_container, permissions); + if (dev_maps[i] == NULL) { + ERROR("Failed to parse device %s", devices[i]); + goto on_error; + } + } + + return dev_maps; + +on_error: + free_multi_dev_maps(dev_maps, devices_len); + return NULL; +} + +static int merge_all_devices_in_dir(const char *dir, const char *dir_container, const char *permissions, + oci_runtime_spec *oci_spec) +{ + int ret = 0; + size_t i = 0; + char **devices = NULL; + size_t devices_len = 0; + host_config_devices_element **dev_maps = NULL; + + ret = get_devices(dir, &devices, &devices_len, 0); + if (ret != 0) { + ERROR("Failed to get host's device in directory:%s", dir); + lcrd_set_error_message("Failed to get host's device in directory:%s", dir); + ret = -1; + goto out; + } + if (devices_len == 0) { + ERROR("Error gathering device information while adding devices in directory \"%s\":no available device nodes", + dir); + lcrd_set_error_message("Error gathering device information while adding devices in directory" + " \"%s\":,no available device nodes", dir); + ret = -1; + goto out; + } + /* devices which will be populated into container */ + if (devices != NULL) { + dev_maps = parse_multi_devices(dir, dir_container, permissions, devices, devices_len); + if (dev_maps == NULL) { + ERROR("Failed to parse multi devices"); + ret = -1; + goto out; + } + + ret = merge_all_devices(oci_spec, dev_maps, devices_len, permissions); + if (ret != 0) { + ERROR("Failed to merge devices"); + ret = -1; + goto out; + } + } +out: + if (devices != NULL) { + for (i = 0; i < devices_len; i++) { + free(devices[i]); + } + free(devices); + } + + free_multi_dev_maps(dev_maps, devices_len); + return ret; +} + +int merge_all_devices_and_all_permission(oci_runtime_spec *oci_spec) +{ + int ret = 0; + size_t i = 0; + oci_runtime_config_linux_resources *ptr = NULL; + oci_runtime_defs_linux_device_cgroup *spec_dev_cgroup = NULL; + + ret = merge_all_devices_in_dir("/dev", NULL, NULL, oci_spec); + if (ret != 0) { + ERROR("Failed to merge all devices in /dev"); + ret = -1; + goto out; + } + + ret = make_sure_oci_spec_linux_resources(oci_spec); + if (ret < 0) { + goto out; + } + + ptr = oci_spec->linux->resources; + if (ptr->devices != NULL) { + for (i = 0; i < ptr->devices_len; i++) { + free_oci_runtime_defs_linux_device_cgroup(ptr->devices[i]); + ptr->devices[i] = NULL; + } + free(ptr->devices); + ptr->devices = NULL; + ptr->devices_len = 0; + } + + ptr->devices = util_common_calloc_s(sizeof(oci_runtime_defs_linux_device_cgroup *)); + if (ptr->devices == NULL) { + ret = -1; + goto out; + } + + ptr->devices_len = 1; + + spec_dev_cgroup = util_common_calloc_s(sizeof(oci_runtime_defs_linux_device_cgroup)); + if (spec_dev_cgroup == NULL) { + ret = -1; + goto out; + } + + spec_dev_cgroup->allow = true; + spec_dev_cgroup->access = util_strdup_s("rwm"); + spec_dev_cgroup->type = util_strdup_s("a"); + spec_dev_cgroup->major = -1; + spec_dev_cgroup->minor = -1; + + ptr->devices[0] = spec_dev_cgroup; + +out: + return ret; +} + +static inline bool is_sys_proc_mount_destination(const char *destination) +{ + return strcmp("/sys", destination) == 0 || strcmp("/proc", destination) == 0 || \ + strcmp("/sys/fs/cgroup", destination) == 0; +} + +static inline bool is_ro_mount_option(const char *option) +{ + return strncmp(option, "ro", strlen("ro")) == 0; +} + +int set_mounts_readwrite_option(const oci_runtime_spec *oci_spec) +{ + size_t i = 0; + size_t j = 0; + + if (oci_spec == NULL) { + return -1; + } + + if (oci_spec->mounts == NULL || oci_spec->mounts_len == 0) { + return 0; + } + + /* make sure /sys and proc and sys/fs/cgroup writeable */ + + for (i = 0; i < oci_spec->mounts_len; i++) { + if (!oci_spec->mounts[i]->destination) { + continue; + } + if (!is_sys_proc_mount_destination(oci_spec->mounts[i]->destination)) { + continue; + } + if (!oci_spec->mounts[i]->options || !oci_spec->mounts[i]->options_len) { + continue; + } + // default mount permission is rw, so do nothing if "ro" not found + for (j = 0; j < oci_spec->mounts[i]->options_len; j++) { + // change mount permission to rw + if (is_ro_mount_option(oci_spec->mounts[i]->options[j])) { + oci_spec->mounts[i]->options[j][1] = 'w'; + break; + } + } + } + + return 0; +} + +static container_config_v2_common_config_mount_points_element *defs_mnt_to_mount_point(const defs_mount *mnt) +{ + container_config_v2_common_config_mount_points_element *mp = NULL; + size_t i; + char *mode = NULL; + + mp = util_common_calloc_s(sizeof(container_config_v2_common_config_mount_points_element)); + if (mp == NULL) { + ERROR("Out of memory"); + return NULL; + } + mp->source = util_strdup_s(mnt->source); + mp->destination = util_strdup_s(mnt->destination); + mp->rw = true; + for (i = 0; i < mnt->options_len; i++) { + if (strcmp(mnt->options[i], "ro") == 0) { + mp->rw = false; + } + if (util_valid_propagation_mode(mnt->options[i])) { + free(mp->propagation); + mp->propagation = util_strdup_s(mnt->options[i]); + } + if (strstr(mnt->options[i], "bind") != NULL) { + continue; + } + if (mode == NULL) { + mode = util_strdup_s(mnt->options[i]); + } else { + int pret; + size_t len = strlen(mode) + strlen(mnt->options[i]) + 2; + char *new_mode = util_common_calloc_s(len); + if (new_mode == NULL) { + ERROR("Out of memory"); + goto cleanup; + } + pret = sprintf_s(new_mode, len, "%s,%s", mode, mnt->options[i]); + if (pret < 0) { + ERROR("Sprintf failed"); + free(new_mode); + goto cleanup; + } + free(mode); + mode = new_mode; + } + } + mp->relabel = mode; + return mp; +cleanup: + free(mode); + free_container_config_v2_common_config_mount_points_element(mp); + return NULL; +} + +int merge_volumes(oci_runtime_spec *oci_spec, char **volumes, size_t volumes_len, + container_config_v2_common_config *common_config, parse_mount_cb parse_mount) +{ + int ret = 0; + size_t new_size = 0, old_size = 0; + size_t new_mp_key_size, new_mp_val_size, old_mp_key_size, old_mp_val_size; + size_t i = 0; + char **mp_key = NULL; + container_config_v2_common_config_mount_points_element **mp_val = NULL; + defs_mount **mounts_temp = NULL; + if (oci_spec == NULL) { + ret = -1; + goto out; + } + if (volumes_len > LIST_SIZE_MAX - oci_spec->mounts_len) { + ERROR("Too many volumes to merge, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many volumes to merge, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto out; + } + new_size = (oci_spec->mounts_len + volumes_len) * sizeof(defs_mount *); + old_size = oci_spec->mounts_len * sizeof(defs_mount *); + ret = mem_realloc((void **)&mounts_temp, new_size, oci_spec->mounts, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory volumes"); + ret = -1; + goto out; + } + oci_spec->mounts = mounts_temp; + + if (common_config != NULL) { + if (common_config->mount_points == NULL) { + common_config->mount_points = util_common_calloc_s(sizeof( + container_config_v2_common_config_mount_points)); + if (common_config->mount_points == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + } + new_mp_key_size = (common_config->mount_points->len + volumes_len) * sizeof(char *); + old_mp_key_size = common_config->mount_points->len * sizeof(char *); + new_mp_val_size = (common_config->mount_points->len + volumes_len) * + sizeof(container_config_v2_common_config_mount_points_element *); + old_mp_val_size = common_config->mount_points->len * + sizeof(container_config_v2_common_config_mount_points_element *); + + ret = mem_realloc((void **)&mp_key, new_mp_key_size, common_config->mount_points->keys, old_mp_key_size); + if (ret != 0) { + ERROR("Failed to realloc memory mount point"); + ret = -1; + goto out; + } + common_config->mount_points->keys = mp_key; + ret = mem_realloc((void **)&mp_val, new_mp_val_size, common_config->mount_points->values, old_mp_val_size); + if (ret != 0) { + ERROR("Failed to realloc memory mount point"); + ret = -1; + goto out; + } + common_config->mount_points->values = mp_val; + } + + for (i = 0; i < volumes_len; i++) { + defs_mount *mnt = parse_mount(volumes[i]); + if (mnt == NULL) { + ERROR("Failed to parse volume: %s", volumes[i]); + ret = -1; + goto out; + } + oci_spec->mounts[oci_spec->mounts_len] = mnt; + oci_spec->mounts_len++; + + if (common_config != NULL) { + common_config->mount_points->values[common_config->mount_points->len] = defs_mnt_to_mount_point(mnt); + if (common_config->mount_points->values[common_config->mount_points->len] == NULL) { + ERROR("Failed to transform to mount point"); + ret = -1; + goto out; + } + common_config->mount_points->keys[common_config->mount_points->len] = util_strdup_s(mnt->destination); + common_config->mount_points->len++; + } + } + +out: + return ret; +} + +static int merge_custom_one_device(oci_runtime_spec *oci_spec, const host_config_devices_element *device) +{ + int ret = 0; + size_t new_size = 0; + size_t old_size = 0; + + ret = make_sure_oci_spec_linux_resources(oci_spec); + if (ret < 0) { + goto out; + } + + /* malloc for linux->device*/ + oci_runtime_defs_linux_device **spec_dev = NULL; + if (oci_spec->linux->devices_len > SIZE_MAX / sizeof(oci_runtime_defs_linux_device *) - 1) { + ERROR("Too many linux devices to merge!"); + ret = -1; + goto out; + } + new_size = (oci_spec->linux->devices_len + 1) * sizeof(oci_runtime_defs_linux_device *); + old_size = new_size - sizeof(oci_runtime_defs_linux_device *); + ret = mem_realloc((void **)&spec_dev, new_size, oci_spec->linux->devices, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for devices"); + ret = -1; + goto out; + } + + oci_spec->linux->devices = spec_dev; + + /* malloc for cgroup->device */ + oci_runtime_defs_linux_device_cgroup **spec_cgroup_dev = NULL; + if (oci_spec->linux->resources->devices_len > SIZE_MAX / sizeof(oci_runtime_defs_linux_device_cgroup *) - 1) { + ERROR("Too many cgroup devices to merge!"); + ret = -1; + goto out; + } + new_size = (oci_spec->linux->resources->devices_len + 1) * sizeof(oci_runtime_defs_linux_device_cgroup *); + old_size = new_size - sizeof(oci_runtime_defs_linux_device_cgroup *); + ret = mem_realloc((void **)&spec_cgroup_dev, new_size, oci_spec->linux->resources->devices, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for cgroup devices"); + ret = -1; + goto out; + } + oci_spec->linux->resources->devices = spec_cgroup_dev; + + + ret = merge_custom_device(&oci_spec->linux->devices[oci_spec->linux->devices_len], + &oci_spec->linux->resources->devices[oci_spec->linux->resources->devices_len], device); + if (ret != 0) { + ERROR("Failed to merge custom device"); + ret = -1; + goto out; + } + oci_spec->linux->devices_len++; + oci_spec->linux->resources->devices_len++; + +out: + return ret; +} + + +static int merge_custom_devices(oci_runtime_spec *oci_spec, host_config_devices_element **devices, size_t devices_len) +{ + int ret = 0; + size_t i = 0; + char *path_on_host = NULL; + char *cgroup_permissions = NULL; + char *dir_in_container = NULL; + host_config_devices_element *device = NULL; + + if (oci_spec == NULL) { + ret = -1; + goto out; + } + + for (i = 0; i < devices_len; i++) { + device = devices[i]; + path_on_host = device->path_on_host; + cgroup_permissions = device->cgroup_permissions; + dir_in_container = device->path_in_container; + + if (util_dir_exists(path_on_host)) { + ret = merge_all_devices_in_dir(path_on_host, dir_in_container, cgroup_permissions, oci_spec); + if (ret != 0) { + ERROR("Failed to merge all devices in directory:%s", path_on_host); + ret = -1; + goto out; + } + } else { + ret = merge_custom_one_device(oci_spec, device); + if (ret != 0) { + ERROR("Failed to merge device %s", path_on_host); + ret = -1; + goto out; + } + } + } + +out: + return ret; +} + +static int merge_blkio_weight_device(oci_runtime_spec *oci_spec, + host_config_blkio_weight_device_element **blkio_weight_device, + size_t blkio_weight_device_len) +{ + int ret = 0; + size_t new_size = 0; + size_t old_size = 0; + size_t i = 0; + oci_runtime_defs_linux_block_io_device_weight **weight_device = NULL; + + ret = make_sure_oci_spec_linux_resources_blkio(oci_spec); + if (ret < 0) { + goto out; + } + + if (oci_spec->linux->resources->block_io->weight_device_len > LIST_DEVICE_SIZE_MAX - blkio_weight_device_len) { + ERROR("Too many weight devices to merge, the limit is %d", LIST_DEVICE_SIZE_MAX); + lcrd_set_error_message("Too many weight devices to merge, the limit is %d", LIST_DEVICE_SIZE_MAX); + ret = -1; + goto out; + } + + new_size = (oci_spec->linux->resources->block_io->weight_device_len + blkio_weight_device_len) + * sizeof(oci_runtime_defs_linux_block_io_device_weight *); + old_size = oci_spec->linux->resources->block_io->weight_device_len * + sizeof(oci_runtime_defs_linux_block_io_device_weight *); + ret = mem_realloc((void **)&weight_device, new_size, oci_spec->linux->resources->block_io->weight_device, + old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for weight devices"); + ret = -1; + goto out; + } + oci_spec->linux->resources->block_io->weight_device = weight_device; + + for (i = 0; i < blkio_weight_device_len; i++) { + ret = merge_host_config_blk_weight_device( + &oci_spec->linux->resources->block_io->weight_device[oci_spec-> + linux->resources->block_io->weight_device_len], + blkio_weight_device[i]); + if (ret != 0) { + ERROR("Failed to merge blkio weight device"); + ret = -1; + goto out; + } + oci_spec->linux->resources->block_io->weight_device_len++; + } + +out: + return ret; +} + +static int merge_blkio_read_bps_device(oci_runtime_spec *oci_spec, + host_config_blkio_device_read_bps_element **blkio_read_bps_device, + size_t throttle_read_bps_device_len) +{ + int ret = 0; + size_t new_size = 0; + size_t old_size = 0; + size_t i = 0; + oci_runtime_defs_linux_block_io_device_throttle **throttle_read_bps_device = NULL; + + ret = make_sure_oci_spec_linux_resources_blkio(oci_spec); + if (ret < 0) { + goto out; + } + + if (oci_spec->linux->resources->block_io->throttle_read_bps_device_len > + LIST_DEVICE_SIZE_MAX - throttle_read_bps_device_len) { + ERROR("Too many throttle read bps devices to merge, the limit is %d", LIST_DEVICE_SIZE_MAX); + lcrd_set_error_message("Too many throttle read bps devices devices to merge, the limit is %d", + LIST_DEVICE_SIZE_MAX); + ret = -1; + goto out; + } + + new_size = (oci_spec->linux->resources->block_io->throttle_read_bps_device_len + throttle_read_bps_device_len) + * sizeof(oci_runtime_defs_linux_block_io_device_throttle *); + old_size = oci_spec->linux->resources->block_io->throttle_read_bps_device_len * + sizeof(oci_runtime_defs_linux_block_io_device_throttle *); + ret = mem_realloc((void **)&throttle_read_bps_device, new_size, + oci_spec->linux->resources->block_io->throttle_read_bps_device, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for blkio throttle read bps devices"); + ret = -1; + goto out; + } + oci_spec->linux->resources->block_io->throttle_read_bps_device = throttle_read_bps_device; + + for (i = 0; i < throttle_read_bps_device_len; i++) { + ret = merge_host_config_blk_read_bps_device( + &oci_spec->linux->resources->block_io->throttle_read_bps_device[ + oci_spec->linux->resources->block_io->throttle_read_bps_device_len], blkio_read_bps_device[i]); + if (ret != 0) { + ERROR("Failed to merge blkio throttle read bps device"); + ret = -1; + goto out; + } + oci_spec->linux->resources->block_io->throttle_read_bps_device_len++; + } + +out: + return ret; +} + +static int merge_blkio_write_bps_device(oci_runtime_spec *oci_spec, + host_config_blkio_device_write_bps_element **blkio_write_bps_device, + size_t throttle_write_bps_device_len) +{ + int ret = 0; + size_t new_size = 0; + size_t old_size = 0; + size_t i = 0; + oci_runtime_defs_linux_block_io_device_throttle **throttle_write_bps_device = NULL; + + ret = make_sure_oci_spec_linux_resources_blkio(oci_spec); + if (ret < 0) { + goto out; + } + + if (oci_spec->linux->resources->block_io->throttle_write_bps_device_len > + LIST_DEVICE_SIZE_MAX - throttle_write_bps_device_len) { + ERROR("Too many throttle write bps devices to merge, the limit is %d", LIST_DEVICE_SIZE_MAX); + lcrd_set_error_message("Too many throttle write bps devices devices to merge, the limit is %d", + LIST_DEVICE_SIZE_MAX); + ret = -1; + goto out; + } + + new_size = (oci_spec->linux->resources->block_io->throttle_write_bps_device_len + throttle_write_bps_device_len) + * sizeof(oci_runtime_defs_linux_block_io_device_throttle *); + old_size = oci_spec->linux->resources->block_io->throttle_write_bps_device_len * + sizeof(oci_runtime_defs_linux_block_io_device_throttle *); + ret = mem_realloc((void **)&throttle_write_bps_device, new_size, + oci_spec->linux->resources->block_io->throttle_write_bps_device, old_size); + if (ret != 0) { + ERROR("Failed to realloc memory for throttle write bps devices"); + ret = -1; + goto out; + } + oci_spec->linux->resources->block_io->throttle_write_bps_device = throttle_write_bps_device; + + for (i = 0; i < throttle_write_bps_device_len; i++) { + ret = merge_host_config_blk_write_bps_device( + &oci_spec->linux->resources->block_io->throttle_write_bps_device[ + oci_spec->linux->resources->block_io->throttle_write_bps_device_len], blkio_write_bps_device[i]); + if (ret != 0) { + ERROR("Failed to merge blkio throttle write bps device"); + ret = -1; + goto out; + } + oci_spec->linux->resources->block_io->throttle_write_bps_device_len++; + } + +out: + return ret; +} + +int merge_conf_device(oci_runtime_spec *oci_spec, host_config *host_spec) +{ + int ret = 0; + + /* blkio weight devices */ + if (host_spec->blkio_weight_device != NULL && host_spec->blkio_weight_device_len != 0) { + ret = merge_blkio_weight_device(oci_spec, host_spec->blkio_weight_device, host_spec->blkio_weight_device_len); + if (ret != 0) { + ERROR("Failed to merge blkio weight devices"); + goto out; + } + } + + /* blkio throttle read bps devices */ + if (host_spec->blkio_device_read_bps != NULL && host_spec->blkio_device_read_bps_len != 0) { + ret = merge_blkio_read_bps_device(oci_spec, host_spec->blkio_device_read_bps, + host_spec->blkio_device_read_bps_len); + if (ret != 0) { + ERROR("Failed to merge blkio read bps devices"); + goto out; + } + } + + /* blkio throttle write bps devices */ + if (host_spec->blkio_device_write_bps != NULL && host_spec->blkio_device_write_bps_len != 0) { + ret = merge_blkio_write_bps_device(oci_spec, host_spec->blkio_device_write_bps, + host_spec->blkio_device_write_bps_len); + if (ret != 0) { + ERROR("Failed to merge blkio write bps devices"); + goto out; + } + } + + /* devices which will be populated into container */ + if (host_spec->devices != NULL && host_spec->devices_len != 0) { + /* privileged containers will populate all devices in host */ + if (!host_spec->privileged) { + ret = merge_custom_devices(oci_spec, host_spec->devices, host_spec->devices_len); + if (ret != 0) { + ERROR("Failed to merge custom devices"); + goto out; + } + } else { + INFO("Skipped \"--device\" due to conflict with \"--privileged\""); + } + } + +out: + return ret; +} + +static bool mounts_expand(oci_runtime_spec *container, size_t add_len) +{ + defs_mount **tmp_mount = NULL; + int ret = 0; + size_t old_len = container->mounts_len; + if (add_len > SIZE_MAX / sizeof(defs_mount *) - old_len) { + ERROR("Too many mount elements!"); + return false; + } + ret = mem_realloc((void **)&tmp_mount, (old_len + add_len) * sizeof(defs_mount *), + container->mounts, old_len * sizeof(defs_mount *)); + if (ret < 0) { + ERROR("memory realloc failed for mount array expand"); + return false; + } + container->mounts = tmp_mount; + container->mounts_len = old_len + add_len; + + return true; +} + +bool mount_run_tmpfs(oci_runtime_spec *container, const char *path) +{ + char **options = NULL; + size_t options_len = 5; + bool ret = false; + defs_mount *tmp_mounts = NULL; + + if (options_len > SIZE_MAX / sizeof(char *)) { + ERROR("Invalid option size"); + return ret; + } + options = util_common_calloc_s(options_len * sizeof(char *)); + if (options == NULL) { + ERROR("Out of memory"); + goto out_free; + } + options[0] = util_strdup_s("nosuid"); + options[1] = util_strdup_s("noexec"); + options[2] = util_strdup_s("nodev"); + options[3] = util_strdup_s("relatime"); + options[4] = util_strdup_s("mode=755"); + tmp_mounts = util_common_calloc_s(sizeof(defs_mount)); + if (tmp_mounts == NULL) { + ERROR("Malloc tmp_mounts memory failed"); + goto out_free; + } + + tmp_mounts->destination = util_strdup_s(path); + tmp_mounts->source = util_strdup_s("tmpfs"); + tmp_mounts->type = util_strdup_s("tmpfs"); + tmp_mounts->options = options; + tmp_mounts->options_len = options_len; + options = NULL; + + /*expand mount array*/ + if (!mounts_expand(container, 1)) { + goto out_free; + } + /*add a new mount node*/ + container->mounts[container->mounts_len - 1] = tmp_mounts; + + ret = true; +out_free: + + if (!ret) { + util_free_array(options); + free_defs_mount(tmp_mounts); + } + return ret; +} + +static bool mount_file(oci_runtime_spec *container, const char *src_path, const char *dst_path) +{ + char **options = NULL; + size_t options_len = 2; + bool ret = false; + defs_mount *tmp_mounts = NULL; + + /*mount options*/ + if (options_len > SIZE_MAX / sizeof(char *)) { + ERROR("Options len is too long!"); + goto out_free; + } + options = util_common_calloc_s(options_len * sizeof(char *)); + if (options == NULL) { + ERROR("Out of memory"); + goto out_free; + } + options[0] = util_strdup_s("rbind"); + options[1] = util_strdup_s("rprivate"); + /*generate mount node*/ + tmp_mounts = util_common_calloc_s(sizeof(defs_mount)); + if (tmp_mounts == NULL) { + ERROR("Malloc tmp_mounts memory failed"); + goto out_free; + } + + tmp_mounts->destination = util_strdup_s(dst_path); + tmp_mounts->source = util_strdup_s(src_path); + tmp_mounts->type = util_strdup_s("bind"); + tmp_mounts->options = options; + tmp_mounts->options_len = options_len; + options = NULL; + + /*expand mount array*/ + if (!mounts_expand(container, 1)) { + goto out_free; + } + /*add a new mount node*/ + container->mounts[container->mounts_len - 1] = tmp_mounts; + + ret = true; +out_free: + + if (!ret) { + util_free_array(options); + free_defs_mount(tmp_mounts); + } + return ret; +} + +static bool add_host_channel_mount(oci_runtime_spec *container, const host_config_host_channel *host_channel) +{ + char **options = NULL; + size_t options_len = 3; + bool ret = false; + defs_mount *tmp_mounts = NULL; + + if (options_len > SIZE_MAX / sizeof(char *)) { + ERROR("Invalid option size"); + return ret; + } + options = util_common_calloc_s(options_len * sizeof(char *)); + if (options == NULL) { + ERROR("Out of memory"); + goto out_free; + } + options[0] = util_strdup_s("rbind"); + options[1] = util_strdup_s("rprivate"); + options[2] = util_strdup_s(host_channel->permissions); + /*generate mount node*/ + tmp_mounts = util_common_calloc_s(sizeof(defs_mount)); + if (tmp_mounts == NULL) { + ERROR("Malloc tmp_mounts memory failed"); + goto out_free; + } + + tmp_mounts->destination = util_strdup_s(host_channel->path_in_container); + tmp_mounts->source = util_strdup_s(host_channel->path_on_host); + tmp_mounts->type = util_strdup_s("bind"); + tmp_mounts->options = options; + tmp_mounts->options_len = options_len; + options = NULL; + + /*expand mount array*/ + if (!mounts_expand(container, 1)) { + goto out_free; + } + /*add a new mount node*/ + container->mounts[container->mounts_len - 1] = tmp_mounts; + + ret = true; +out_free: + + if (!ret) { + util_free_array(options); + free_defs_mount(tmp_mounts); + } + return ret; +} + +static int change_dev_shm_size(oci_runtime_spec *oci_spec, int64_t shm_size) +{ + size_t i = 0; + size_t j = 0; + char size_opt[MOUNT_PROPERTIES_SIZE] = { 0 }; + char *tmp = NULL; + if (sprintf_s(size_opt, sizeof(size_opt), "size=%lld", (long long int)shm_size) < 0) { + ERROR("Out of memory"); + return -1; + } + + if (oci_spec->mounts == NULL) { + return -1; + } + + for (i = 0; i < oci_spec->mounts_len; i++) { + if (strcmp("/dev/shm", oci_spec->mounts[i]->destination) != 0) { + continue; + } + + for (j = 0; j < oci_spec->mounts[i]->options_len; j++) { + // change dev shm mount size + if (strncmp("size=", oci_spec->mounts[i]->options[j], strlen("size=")) == 0) { + tmp = oci_spec->mounts[i]->options[j]; + oci_spec->mounts[i]->options[j] = util_strdup_s(size_opt); + free(tmp); + tmp = NULL; + return 0; + } + } + } + + ERROR("/dev/shm mount point not exist"); + return -1; +} + +int merge_conf_mounts(oci_runtime_spec *oci_spec, container_custom_config *custom_spec, host_config *host_spec, + container_config_v2_common_config *common_config) +{ + int ret = 0; + + /* volumes to mount */ + if (host_spec->binds != NULL && host_spec->binds_len) { + ret = merge_volumes(oci_spec, host_spec->binds, + host_spec->binds_len, common_config, + parse_volume); + if (ret) { + ERROR("Failed to merge volumes"); + goto out; + } + } + + /* host channel to mount */ + if (host_spec->host_channel != NULL) { + if (!add_host_channel_mount(oci_spec, host_spec->host_channel)) { + ERROR("Failed to merge host channel mount"); + goto out; + } + } + + /* mounts to mount filesystem */ + if (custom_spec->mounts && custom_spec->mounts_len) { + ret = merge_volumes(oci_spec, custom_spec->mounts, + custom_spec->mounts_len, common_config, + parse_mount); + if (ret) { + ERROR("Failed to merge mounts"); + goto out; + } + } + + if (host_spec->shm_size >= 0) { + if (host_spec->shm_size == 0) { + host_spec->shm_size = DEFAULT_SHM_SIZE; + } + + ret = change_dev_shm_size(oci_spec, host_spec->shm_size); + if (ret) { + ERROR("Failed to set dev shm size"); + goto out; + } + } + + if (is_container(host_spec->network_mode)) { + /* add network config files */ + if (common_config->hosts_path != NULL && !mount_file(oci_spec, common_config->hosts_path, ETC_HOSTS)) { + ERROR("Merge hosts mount failed"); + ret = -1; + goto out; + } + if (common_config->resolv_conf_path != NULL && + !mount_file(oci_spec, common_config->resolv_conf_path, RESOLV_CONF_PATH)) { + ERROR("Merge resolv.conf mount failed"); + ret = -1; + goto out; + } + } +out: + return ret; +} + +int add_rootfs_mount(const oci_runtime_spec *container) +{ + int ret = 0; + char *mntparent = NULL; + + mntparent = conf_get_lcrd_mount_rootfs(); + if (mntparent == NULL) { + ERROR("Failed to get mount parent directory"); + ret = -1; + goto out; + } + if (append_json_map_string_string(container->annotations, "rootfs.mount", mntparent)) { + ERROR("Realloc annotatinons failed"); + ret = -1; + goto out; + } + +out: + free(mntparent); + return ret; +} + + diff --git a/src/services/execution/spec/specs_mount.h b/src/services/execution/spec/specs_mount.h new file mode 100644 index 0000000..1921839 --- /dev/null +++ b/src/services/execution/spec/specs_mount.h @@ -0,0 +1,51 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide specs definition + ******************************************************************************/ +#ifndef __SPECS_MOUNT_H__ +#define __SPECS_MOUNT_H__ + +#include +#include "liblcrd.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "container_config_v2.h" +#include "oci_runtime_hooks.h" +#include "oci_runtime_spec.h" + +int adapt_settings_for_mounts(oci_runtime_spec *oci_spec, container_custom_config *custom_spec); + +typedef defs_mount *(*parse_mount_cb)(const char *mount); + +int merge_volumes(oci_runtime_spec *oci_spec, char **volumes, + size_t volumes_len, container_config_v2_common_config *common_config, + parse_mount_cb parse_mount); + +defs_mount *parse_mount(const char *mount); + +defs_mount *parse_volume(const char *volume); + +int merge_conf_mounts(oci_runtime_spec *oci_spec, container_custom_config *custom_spec, host_config *host_spec, + container_config_v2_common_config *common_config); + +int add_rootfs_mount(const oci_runtime_spec *container); + +int set_mounts_readwrite_option(const oci_runtime_spec *oci_spec); + +int merge_all_devices_and_all_permission(oci_runtime_spec *oci_spec); + +bool mount_run_tmpfs(oci_runtime_spec *container, const char *path); + +int merge_conf_device(oci_runtime_spec *oci_spec, host_config *host_spec); + +#endif diff --git a/src/services/execution/spec/specs_security.c b/src/services/execution/spec/specs_security.c new file mode 100644 index 0000000..e077557 --- /dev/null +++ b/src/services/execution/spec/specs_security.c @@ -0,0 +1,1061 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container specs functions + ******************************************************************************/ +#include "specs_security.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_LIBCAP_H +#include +#endif + +#include "error.h" +#include "securec.h" +#include "log.h" +#include "oci_runtime_spec.h" +#include "docker_seccomp.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "utils.h" +#include "config.h" +#include "lcrd_config.h" +#include "parse_common.h" +#include "container_def.h" +#include "liblcrd.h" +#include "specs_extend.h" + +#define MAX_CAP_LEN 32 + +static const char * const g_system_caps[] = { + "SYS_BOOT", + "SETPCAP", + "NET_RAW", + "NET_BIND_SERVICE", +#ifdef CAP_AUDIT_WRITE + "AUDIT_WRITE", +#endif + "DAC_OVERRIDE", + "SETFCAP", + "SETGID", + "SETUID", + "MKNOD", + "CHOWN", + "FOWNER", + "FSETID", + "KILL", + "SYS_CHROOT" +}; + +static int append_capability(char ***dstcaps, size_t *dstcaps_len, const char *cap) +{ + int ret = 0; + char **tmp = NULL; + + if (*dstcaps_len > SIZE_MAX / sizeof(char *) - 1) { + ERROR("Too many capabilities to append!"); + ret = -1; + goto out; + } + ret = mem_realloc((void **)&tmp, sizeof(char *) * (*dstcaps_len + 1), *dstcaps, sizeof(char *) * (*dstcaps_len)); + if (ret != 0) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + *dstcaps = tmp; + (*dstcaps_len)++; + + (*dstcaps)[*dstcaps_len - 1] = util_strdup_s(cap); +out: + return ret; +} + +static int copy_capabilities(char ***dstcaps, size_t *dstcaps_len, const char **srccaps, size_t srccaps_len) +{ + size_t i; + int ret = 0; + + if (srccaps == NULL || srccaps_len == 0) { + *dstcaps = NULL; + *dstcaps_len = 0; + return ret; + } + if (srccaps_len > SIZE_MAX / sizeof(char *)) { + ERROR("Too many capabilities to copy!"); + return -1; + } + + *dstcaps = util_common_calloc_s(srccaps_len * sizeof(char *)); + if (*dstcaps == NULL) { + ret = -1; + goto out; + } + + *dstcaps_len = srccaps_len; + + for (i = 0; i < srccaps_len; i++) { + (*dstcaps)[i] = util_strdup_s(srccaps[i]); + } +out: + return ret; +} + +static int tweak_drops_capabilities(char ***new_caps, size_t *new_caps_len, char **basic_caps, size_t basic_caps_len, + const char **drops, size_t drops_len) +{ + size_t i = 0; + int ret = 0; + + if (strings_in_slice((const char **)drops, drops_len, "all")) { + goto out; + } + + for (i = 0; (basic_caps != NULL && i < basic_caps_len); i++) { + // skip `all` already handled above + if (!basic_caps[i] || !strcasecmp(basic_caps[i], "all")) { + continue; + } + + // if we don't drop `all`, add back all the non-dropped caps + if (!strings_in_slice((const char **)drops, drops_len, basic_caps[i] + strlen("CAP_"))) { + ret = append_capability(new_caps, new_caps_len, basic_caps[i]); + if (ret != 0) { + ERROR("Failed to append capabilities"); + ret = -1; + goto out; + } + } + } + +out: + return ret; +} + +static int tweak_adds_capabilities(char ***new_caps, size_t *new_caps_len, const char **adds, size_t adds_len) +{ + size_t i = 0; + int ret = 0; + int nret = 0; + size_t all_caps_len = 0; + char tmpcap[MAX_CAP_LEN] = { 0 }; + + all_caps_len = util_get_all_caps_len(); + + for (i = 0; i < adds_len; i++) { + // skip `all` already handled above + if (strcasecmp(adds[i], "all") == 0) { + continue; + } + + nret = sprintf_s(tmpcap, sizeof(tmpcap), "CAP_%s", adds[i]); + if (nret < 0) { + ERROR("Failed to print string"); + ret = -1; + goto out; + } + if (!strings_in_slice(g_all_caps, all_caps_len, tmpcap)) { + ERROR("Unknown capability to add: '%s'", tmpcap); + ret = -1; + goto out; + } + + // add cap if not already in the list + if (!strings_in_slice((const char **)*new_caps, *new_caps_len, tmpcap)) { + ret = append_capability(new_caps, new_caps_len, tmpcap); + if (ret != 0) { + ERROR("Failed to append capabilities"); + ret = -1; + goto out; + } + } + } + +out: + return ret; +} + +static bool valid_drops_cap(const char **drops, size_t drops_len) +{ + int nret = 0; + size_t i; + size_t all_caps_len = 0; + char tmpcap[MAX_CAP_LEN] = { 0 }; + + all_caps_len = util_get_all_caps_len(); + // look for invalid cap in the drop list + for (i = 0; i < drops_len; i++) { + if (strcasecmp(drops[i], "all") == 0) { + continue; + } + + nret = sprintf_s(tmpcap, sizeof(tmpcap), "CAP_%s", drops[i]); + if (nret < 0) { + ERROR("Failed to print string"); + return false; + } + if (!strings_in_slice(g_all_caps, all_caps_len, tmpcap)) { + ERROR("Unknown capability to drop: '%s'", drops[i]); + return false; + } + } + + return true; +} + +// tweak_capabilities can tweak capabilities by adding or dropping capabilities +// based on the basic capabilities. +static int tweak_capabilities(char ***caps, size_t *caps_len, const char **adds, size_t adds_len, const char **drops, + size_t drops_len) +{ + size_t i; + size_t all_caps_len = 0; + int ret = 0; + char **new_caps = NULL; + char **basic_caps = NULL; + size_t new_caps_len = 0; + size_t basic_caps_len = 0; + + all_caps_len = util_get_all_caps_len(); + if (!valid_drops_cap(drops, drops_len)) { + return -1; + } + + if (strings_in_slice((const char **)adds, adds_len, "all")) { + ret = copy_capabilities(&basic_caps, &basic_caps_len, g_all_caps, all_caps_len); + } else { + ret = copy_capabilities(&basic_caps, &basic_caps_len, (const char **)*caps, *caps_len); + } + if (ret != 0) { + ERROR("Failed to copy capabilities"); + ret = -1; + goto free_out; + } + + ret = tweak_drops_capabilities(&new_caps, &new_caps_len, basic_caps, basic_caps_len, drops, drops_len); + if (ret != 0) { + ret = -1; + goto free_out; + } + + ret = tweak_adds_capabilities(&new_caps, &new_caps_len, adds, adds_len); + if (ret != 0) { + ret = -1; + goto free_out; + } + +free_out: + for (i = 0; i < basic_caps_len; i++) { + free(basic_caps[i]); + } + free(basic_caps); + + // free old caps + for (i = 0; i < *caps_len; i++) { + free((*caps)[i]); + (*caps)[i] = NULL; + } + free(*caps); + + // set new caps + *caps = new_caps; + *caps_len = new_caps_len; + + return ret; +} + +int refill_oci_process_capabilities(oci_runtime_spec_process_capabilities **caps, const char **src_caps, + size_t src_caps_len) +{ + int ret = 0; + size_t i = 0; + + if (*caps == NULL) { + *caps = util_common_calloc_s(sizeof(oci_runtime_spec_process_capabilities)); + if (*caps == NULL) { + ret = -1; + goto out; + } + } + + if ((*caps)->bounding != NULL) { + // free current capabilities + for (i = 0; i < ((*caps)->bounding_len); i++) { + free((*caps)->bounding[i]); + (*caps)->bounding[i] = NULL; + } + free((*caps)->bounding); + (*caps)->bounding = NULL; + } + (*caps)->bounding_len = 0; + + // copy capabilities + ret = copy_capabilities(&((*caps)->bounding), &((*caps)->bounding_len), src_caps, src_caps_len); + if (ret != 0) { + ERROR("Failed to copy all capabilities"); + } +out: + return ret; +} + +static char *seccomp_trans_arch_for_docker(const char *arch) +{ + size_t i = 0; + char *arch_map[][2] = { + { "SCMP_ARCH_X86", "x86" }, + { "SCMP_ARCH_X86_64", "amd64" }, + { "SCMP_ARCH_X32", "x32" }, + { "SCMP_ARCH_ARM", "arm" }, + { "SCMP_ARCH_AARCH64", "arm64" }, + { "SCMP_ARCH_MIPS", "mips" }, + { "SCMP_ARCH_MIPS64", "mips64" }, + { "SCMP_ARCH_MIPS64N32", "mips64n32" }, + { "SCMP_ARCH_MIPSEL", "mipsel" }, + { "SCMP_ARCH_MIPSEL64", "mipsel64" }, + { "SCMP_ARCH_MIPSEL64N32", "mipsel64n32" }, + { "SCMP_ARCH_PPC", "ppc" }, + { "SCMP_ARCH_PPC64", "ppc64" }, + { "SCMP_ARCH_PPC64LE", "ppc64le" }, + { "SCMP_ARCH_S390", "s390" }, + { "SCMP_ARCH_S390X", "s390x" }, + { "SCMP_ARCH_PARISC", "parisc" }, + { "SCMP_ARCH_PARISC64", "parisc64" }, + { "SCMP_ARCH_ALL", "all" } + }; + for (i = 0; i < sizeof(arch_map) / sizeof(arch_map[0]); i++) { + if (strcmp(arch, arch_map[i][0]) == 0) { + return util_strdup_s(arch_map[i][1]); + } + } + + return NULL; +} + +static bool is_arch_in_seccomp(const docker_seccomp *seccomp, const char *arch) +{ + size_t i, j; + char *arch_for_docker = NULL; + + for (i = 0; i < seccomp->arch_map_len; i++) { + int nret = 0; + arch_for_docker = seccomp_trans_arch_for_docker(seccomp->arch_map[i]->architecture); + if (arch_for_docker == NULL) { + return false; + } + nret = strcmp(arch_for_docker, arch); + free(arch_for_docker); + if (nret == 0) { + return true; + } + for (j = 0; j < seccomp->arch_map[i]->sub_architectures_len; j++) { + arch_for_docker = seccomp_trans_arch_for_docker(seccomp->arch_map[i]->sub_architectures[j]); + if (arch_for_docker == NULL) { + return false; + } + nret = strcmp(arch_for_docker, arch); + free(arch_for_docker); + if (nret == 0) { + return true; + } + } + } + return false; +} + +static bool is_cap_in_seccomp(const oci_runtime_spec_process_capabilities *capabilites, const char *cap) +{ + size_t i = 0; + + if (capabilites == NULL) { + return false; + } + + for (i = 0; i < capabilites->bounding_len; i++) { + if (strcasecmp(capabilites->bounding[i], cap) == 0) { + return true; + } + } + return false; +} + +static void meet_include(const docker_seccomp *seccomp, const docker_seccomp_syscalls_element *syscall, + const oci_runtime_spec_process_capabilities *capabilites, bool *meet_include_arch, + bool *meet_include_cap) +{ + size_t i; + + if (syscall->includes == NULL) { + *meet_include_arch = true; + *meet_include_cap = true; + return; + } + if (syscall->includes->arches == NULL) { + *meet_include_arch = true; + } else { + for (i = 0; i < syscall->includes->arches_len; i++) { + if (is_arch_in_seccomp(seccomp, syscall->includes->arches[i])) { + *meet_include_arch = true; + break; + } + } + } + if (syscall->includes->caps == NULL) { + *meet_include_cap = true; + } else { + for (i = 0; i < syscall->includes->caps_len; i++) { + if (is_cap_in_seccomp(capabilites, syscall->includes->caps[i])) { + *meet_include_cap = true; + break; + } + } + } +} + +static void meet_exclude(const docker_seccomp *seccomp, const docker_seccomp_syscalls_element *syscall, + const oci_runtime_spec_process_capabilities *capabilites, bool *meet_exclude_arch, + bool *meet_exclude_cap) +{ + size_t i; + + if (syscall->excludes == NULL) { + *meet_exclude_arch = true; + *meet_exclude_cap = true; + return; + } + + if (syscall->excludes->arches == NULL) { + *meet_exclude_arch = true; + } else { + for (i = 0; i < syscall->excludes->arches_len; i++) { + if (is_arch_in_seccomp(seccomp, syscall->excludes->arches[i])) { + *meet_exclude_arch = false; + break; + } + } + } + if (syscall->excludes->caps == NULL) { + *meet_exclude_cap = true; + } else { + for (i = 0; i < syscall->excludes->caps_len; i++) { + if (is_cap_in_seccomp(capabilites, syscall->excludes->caps[i])) { + *meet_exclude_cap = false; + break; + } + } + } +} + +static bool meet_filtering_rules(const docker_seccomp *seccomp, const docker_seccomp_syscalls_element *syscall, + const oci_runtime_spec_process_capabilities *capabilites) +{ + bool meet_include_arch = false; + bool meet_include_cap = false; + bool meet_exclude_arch = true; + bool meet_exclude_cap = true; + + meet_include(seccomp, syscall, capabilites, &meet_include_arch, &meet_include_cap); + meet_exclude(seccomp, syscall, capabilites, &meet_exclude_arch, &meet_exclude_cap); + + return meet_include_arch && meet_include_cap && meet_exclude_arch && meet_exclude_cap; +} + +static size_t docker_seccomp_arches_count(const docker_seccomp *docker_seccomp_spec) +{ + size_t count = 0; + size_t i = 0; + for (i = 0; i < docker_seccomp_spec->arch_map_len; i++) { + count += docker_seccomp_spec->arch_map[i]->sub_architectures_len + 1; + } + return count; +} + +static int dup_architectures_to_oci_spec(const docker_seccomp *docker_seccomp_spec, + oci_runtime_config_linux_seccomp *oci_seccomp_spec) +{ + size_t arch_size = 0; + + arch_size = docker_seccomp_arches_count(docker_seccomp_spec); + if (arch_size != 0) { + size_t i; + size_t j; + if (arch_size > (SIZE_MAX / sizeof(char *))) { + return -1; + } + oci_seccomp_spec->architectures = util_common_calloc_s(arch_size * sizeof(char *)); + if (oci_seccomp_spec->architectures == NULL) { + return -1; + } + for (i = 0; i < docker_seccomp_spec->arch_map_len; i++) { + oci_seccomp_spec->architectures[oci_seccomp_spec->architectures_len++] = + util_strdup_s(docker_seccomp_spec->arch_map[i]->architecture); + for (j = 0; j < docker_seccomp_spec->arch_map[i]->sub_architectures_len; j++) { + oci_seccomp_spec->architectures[oci_seccomp_spec->architectures_len++] = + util_strdup_s(docker_seccomp_spec->arch_map[i]->sub_architectures[j]); + } + } + } + + return 0; +} + +static int dup_syscall_args_to_oci_spec(const docker_seccomp_syscalls_element *docker_syscall, + oci_runtime_defs_linux_syscall *oci_syscall) +{ + size_t i = 0; + + if (docker_syscall->args_len == 0) { + return 0; + } + + if (docker_syscall->args_len > (SIZE_MAX / sizeof(oci_runtime_defs_linux_syscall_arg *))) { + return -1; + } + + oci_syscall->args = util_common_calloc_s(docker_syscall->args_len * sizeof(oci_runtime_defs_linux_syscall_arg *)); + if (oci_syscall->args == NULL) { + return -1; + } + for (i = 0; i < docker_syscall->args_len; i++) { + oci_syscall->args[i] = util_common_calloc_s(sizeof(oci_runtime_defs_linux_syscall_arg)); + if (oci_syscall->args[i] == NULL) { + return -1; + } + oci_runtime_defs_linux_syscall_arg *args_element = oci_syscall->args[i]; + args_element->index = docker_syscall->args[i]->index; + args_element->value = docker_syscall->args[i]->value; + args_element->value_two = docker_syscall->args[i]->value_two; + args_element->op = util_strdup_s(docker_syscall->args[i]->op); + oci_syscall->args_len++; + } + + return 0; +} + +static int dup_syscall_to_oci_spec(const docker_seccomp *docker_seccomp_spec, + oci_runtime_config_linux_seccomp *oci_seccomp_spec, + const oci_runtime_spec_process_capabilities *capabilites) +{ + int ret = 0; + size_t i, j, k; + size_t new_size, old_size; + oci_runtime_defs_linux_syscall **tmp_syscalls = NULL; + + if (docker_seccomp_spec->syscalls_len == 0) { + return 0; + } + + if (docker_seccomp_spec->syscalls_len > (SIZE_MAX / sizeof(oci_runtime_defs_linux_syscall *))) { + return -1; + } + + oci_seccomp_spec->syscalls = + util_common_calloc_s(docker_seccomp_spec->syscalls_len * sizeof(oci_runtime_defs_linux_syscall *)); + if (oci_seccomp_spec->syscalls == NULL) { + return -1; + } + for (i = 0; i < docker_seccomp_spec->syscalls_len; i++) { + if (!meet_filtering_rules(docker_seccomp_spec, docker_seccomp_spec->syscalls[i], capabilites)) { + continue; + } + k = oci_seccomp_spec->syscalls_len; + oci_seccomp_spec->syscalls[k] = util_common_calloc_s(sizeof(oci_runtime_defs_linux_syscall)); + if (oci_seccomp_spec->syscalls[k] == NULL) { + return -1; + } + oci_seccomp_spec->syscalls_len++; + + if (docker_seccomp_spec->syscalls[i]->names_len > (SIZE_MAX / sizeof(char *))) { + return -1; + } + + oci_seccomp_spec->syscalls[k]->names = + util_common_calloc_s(docker_seccomp_spec->syscalls[i]->names_len * sizeof(char *)); + if (oci_seccomp_spec->syscalls[k]->names == NULL) { + return -1; + } + for (j = 0; j < docker_seccomp_spec->syscalls[i]->names_len; j++) { + oci_seccomp_spec->syscalls[k]->names[j] = util_strdup_s(docker_seccomp_spec->syscalls[i]->names[j]); + oci_seccomp_spec->syscalls[k]->names_len++; + } + oci_seccomp_spec->syscalls[k]->action = util_strdup_s(docker_seccomp_spec->syscalls[i]->action); + if (dup_syscall_args_to_oci_spec(docker_seccomp_spec->syscalls[i], oci_seccomp_spec->syscalls[k])) { + return -1; + } + } + + new_size = sizeof(oci_runtime_defs_linux_syscall *) * oci_seccomp_spec->syscalls_len; + old_size = sizeof(oci_runtime_defs_linux_syscall *) * docker_seccomp_spec->syscalls_len; + ret = mem_realloc((void **)&tmp_syscalls, new_size, oci_seccomp_spec->syscalls, old_size); + if (ret < 0) { + ERROR("Out of memory"); + return -1; + } + oci_seccomp_spec->syscalls = tmp_syscalls; + + return 0; +} + +static oci_runtime_config_linux_seccomp *trans_docker_seccomp_to_oci_format( + const docker_seccomp *docker_seccomp_spec, const oci_runtime_spec_process_capabilities *capabilites) +{ + oci_runtime_config_linux_seccomp *oci_seccomp_spec = NULL; + + oci_seccomp_spec = util_common_calloc_s(sizeof(oci_runtime_config_linux_seccomp)); + if (oci_seccomp_spec == NULL) { + goto out; + } + + // default action + oci_seccomp_spec->default_action = util_strdup_s(docker_seccomp_spec->default_action); + + // architectures + if (dup_architectures_to_oci_spec(docker_seccomp_spec, oci_seccomp_spec)) { + goto out; + } + + // syscalls + if (dup_syscall_to_oci_spec(docker_seccomp_spec, oci_seccomp_spec, capabilites)) { + goto out; + } + + goto done; +out: + if (oci_seccomp_spec != NULL) { + free_oci_runtime_config_linux_seccomp(oci_seccomp_spec); + } + return NULL; + +done: + return oci_seccomp_spec; +} + +int merge_default_seccomp_spec(oci_runtime_spec *oci_spec, const oci_runtime_spec_process_capabilities *capabilites) +{ + oci_runtime_config_linux_seccomp *oci_seccomp_spec = NULL; + docker_seccomp *docker_seccomp_spec = NULL; + + if (oci_spec->process == NULL || oci_spec->process->capabilities == NULL) { + return 0; + } + + docker_seccomp_spec = get_seccomp_security_opt_spec(SECCOMP_DEFAULT_PATH); + if (docker_seccomp_spec == NULL) { + ERROR("Failed to parse docker format seccomp specification file \"%s\"", SECCOMP_DEFAULT_PATH); + lcrd_set_error_message("failed to parse seccomp file: %s", SECCOMP_DEFAULT_PATH); + return -1; + } + oci_seccomp_spec = trans_docker_seccomp_to_oci_format(docker_seccomp_spec, capabilites); + free_docker_seccomp(docker_seccomp_spec); + if (oci_seccomp_spec == NULL) { + ERROR("Failed to trans docker format seccomp profile to oci standard"); + lcrd_set_error_message("Failed to trans docker format seccomp profile to oci standard"); + return -1; + } + + oci_spec->linux->seccomp = oci_seccomp_spec; + + return 0; +} + +static int append_systemcall_to_seccomp(oci_runtime_config_linux_seccomp *seccomp, + oci_runtime_defs_linux_syscall *element) +{ + int nret = 0; + size_t old_size, new_size; + oci_runtime_defs_linux_syscall **tmp_syscalls = NULL; + if (seccomp == NULL || element == NULL) { + return -1; + } + + if (seccomp->syscalls_len > SIZE_MAX / sizeof(oci_runtime_defs_linux_syscall *) - 1) { + CRIT("Too many syscalls to append!"); + return -1; + } + new_size = (seccomp->syscalls_len + 1) * sizeof(oci_runtime_defs_linux_syscall *); + old_size = new_size - sizeof(oci_runtime_defs_linux_syscall *); + nret = mem_realloc((void **)&tmp_syscalls, new_size, seccomp->syscalls, old_size); + if (nret < 0) { + CRIT("Memory allocation error."); + return -1; + } + tmp_syscalls[seccomp->syscalls_len++] = element; + seccomp->syscalls = tmp_syscalls; + + return 0; +} + +static oci_runtime_defs_linux_syscall *make_seccomp_syscalls_element(const char **names, size_t names_len, + const char *action, size_t args_len, + oci_runtime_defs_linux_syscall_arg **args) +{ + size_t i = 0; + oci_runtime_defs_linux_syscall *ret = NULL; + ret = util_common_calloc_s(sizeof(oci_runtime_defs_linux_syscall)); + if (ret == NULL) { + CRIT("Memory allocation error."); + goto out; + } + ret->action = util_strdup_s(action ? action : ""); + ret->args_len = args_len; + if (args_len) { + if (args_len > SIZE_MAX / sizeof(oci_runtime_defs_linux_syscall_arg *)) { + CRIT("Too many seccomp syscalls!"); + goto out; + } + ret->args = util_common_calloc_s(args_len * sizeof(oci_runtime_defs_linux_syscall_arg *)); + if (ret->args == NULL) { + CRIT("Memory allocation error."); + goto out; + } + for (i = 0; i < args_len; i++) { + ret->args[i] = util_common_calloc_s(sizeof(oci_runtime_defs_linux_syscall_arg)); + if (ret->args[i] == NULL) { + CRIT("Memory allocation error."); + goto out; + } + ret->args[i]->index = args[i]->index; + ret->args[i]->value = args[i]->value; + ret->args[i]->value_two = args[i]->value_two; + ret->args[i]->op = util_strdup_s(args[i]->op); + } + } + + ret->names_len = names_len; + if (names_len > SIZE_MAX / sizeof(char *)) { + CRIT("Too many syscalls!"); + goto out; + } + ret->names = util_common_calloc_s(names_len * sizeof(char *)); + if (ret->names == NULL) { + CRIT("Memory allocation error."); + goto out; + } + for (i = 0; i < names_len; i++) { + ret->names[i] = util_strdup_s(names[i]); + } + + return ret; + +out: + free_oci_runtime_defs_linux_syscall(ret); + ret = NULL; + return ret; +} + +static int make_sure_oci_spec_process_capabilities(oci_runtime_spec *oci_spec) +{ + int ret = make_sure_oci_spec_process(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->process->capabilities == NULL) { + oci_spec->process->capabilities = util_common_calloc_s(sizeof(oci_runtime_spec_process_capabilities)); + if (oci_spec->process->capabilities == NULL) { + return -1; + } + } + return 0; +} + +int merge_caps(oci_runtime_spec *oci_spec, const char **adds, size_t adds_len, const char **drops, size_t drops_len) +{ + int ret = 0; + + if (adds == NULL && drops == NULL) { + return 0; + } + + ret = make_sure_oci_spec_process_capabilities(oci_spec); + if (ret < 0) { + goto out; + } + + if (adds_len > LIST_SIZE_MAX || drops_len > LIST_SIZE_MAX) { + ERROR("Too many capabilities to add or drop, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many capabilities to add or drop, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto out; + } + + ret = tweak_capabilities(&oci_spec->process->capabilities->bounding, &oci_spec->process->capabilities->bounding_len, + adds, adds_len, drops, drops_len); + if (ret != 0) { + ERROR("Failed to tweak capabilities"); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int make_sure_oci_spec_linux_sysctl(oci_runtime_spec *oci_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->linux->sysctl == NULL) { + oci_spec->linux->sysctl = util_common_calloc_s(sizeof(json_map_string_string)); + if (oci_spec->linux->sysctl == NULL) { + return -1; + } + } + return 0; +} + +int merge_sysctls(oci_runtime_spec *oci_spec, const json_map_string_string *sysctls) +{ + int ret = 0; + size_t i; + + if (sysctls == NULL || sysctls->len == 0) { + return 0; + } + + ret = make_sure_oci_spec_linux_sysctl(oci_spec); + if (ret < 0) { + goto out; + } + + if (sysctls->len > LIST_SIZE_MAX) { + ERROR("Too many sysctls to add, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many sysctls to add, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto out; + } + + for (i = 0; i < sysctls->len; i++) { + if (append_json_map_string_string(oci_spec->linux->sysctl, sysctls->keys[i], sysctls->values[i]) != 0) { + ERROR("Append string failed"); + goto out; + } + } +out: + return ret; +} + +int merge_no_new_privileges(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + size_t i = 0; + + ret = make_sure_oci_spec_process(oci_spec); + if (ret < 0) { + goto out; + } + oci_spec->process->no_new_privileges = false; + + if (host_spec->security_opt == NULL || host_spec->security_opt_len == 0) { + return 0; + } + + for (i = 0; i < host_spec->security_opt_len; i++) { + if (strcmp(host_spec->security_opt[i], "no-new-privileges") == 0) { + oci_spec->process->no_new_privileges = true; + break; + } + } + +out: + return ret; +} + +static int get_adds_cap_for_system_container(const host_config *host_spec, char ***adds, size_t *adds_len) +{ + size_t i = 0; + int ret = 0; + char **drops = NULL; + size_t drops_len = 0; + size_t system_caps_len = sizeof(g_system_caps) / sizeof(char *); + + if (host_spec == NULL || adds == NULL || adds_len == NULL) { + return -1; + } + + if (host_spec->cap_drop != NULL) { + drops = host_spec->cap_drop; + drops_len = host_spec->cap_drop_len; + } + + // if cap_drop in g_system_caps, move it from g_system_caps + for (i = 0; i < system_caps_len; i++) { + if (!strings_in_slice((const char **)drops, drops_len, g_system_caps[i])) { + ret = append_capability(adds, adds_len, g_system_caps[i]); + if (ret != 0) { + ERROR("Failed to append capabilities"); + ret = -1; + goto out; + } + } + } + +out: + return ret; +} + +static void free_adds_cap_for_system_container(char **adds, size_t adds_len) +{ + size_t i = 0; + + if (adds == NULL) { + return; + } + + for (i = 0; i < adds_len; i++) { + free(adds[i]); + } + free(adds); +} + +static int make_sure_oci_spec_linux_seccomp(oci_runtime_spec *oci_spec) +{ + int ret = 0; + + ret = make_sure_oci_spec_linux(oci_spec); + if (ret < 0) { + return -1; + } + + if (oci_spec->linux->seccomp == NULL) { + oci_spec->linux->seccomp = util_common_calloc_s(sizeof(oci_runtime_config_linux_seccomp)); + if (oci_spec->linux->seccomp == NULL) { + return -1; + } + } + + return 0; +} + + +int adapt_settings_for_system_container(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + char *unblocked_systemcall_for_system_container[] = { "mount", "umount2", "reboot", "name_to_handle_at", + "unshare" + }; + char **adds = NULL; + size_t adds_len = 0; + + ret = get_adds_cap_for_system_container(host_spec, &adds, &adds_len); + if (ret != 0) { + ERROR("Failed to get adds cap for system container"); + ret = -1; + goto out; + } + + ret = merge_caps(oci_spec, (const char **)adds, adds_len, NULL, 0); + if (ret != 0) { + ERROR("Failed to merge capabilities"); + ret = -1; + goto out; + } + + ret = make_sure_oci_spec_linux(oci_spec); + if (ret < 0) { + goto out; + } + + ret = make_sure_oci_spec_linux_seccomp(oci_spec); + if (ret < 0) { + goto out; + } + + ret = append_systemcall_to_seccomp( + oci_spec->linux->seccomp, + make_seccomp_syscalls_element((const char **)unblocked_systemcall_for_system_container, + sizeof(unblocked_systemcall_for_system_container) / + sizeof(unblocked_systemcall_for_system_container[0]), + "SCMP_ACT_ALLOW", 0, NULL)); + if (ret != 0) { + ERROR("Failed to append systemcall to seccomp file"); + ret = -1; + goto out; + } +out: + free_adds_cap_for_system_container(adds, adds_len); + return ret; +} + +int merge_seccomp(oci_runtime_spec *oci_spec, const host_config *host_spec) +{ + int ret = 0; + size_t k = 0; + parser_error err = NULL; + char *seccomp_json = NULL; + docker_seccomp *docker_seccomp = NULL; + + if (host_spec->security_opt == NULL || host_spec->security_opt_len == 0) { + return 0; + } + + if (host_spec->security_opt_len > LIST_SIZE_MAX) { + ERROR("Too many security option to add, the limit is %d", LIST_SIZE_MAX); + lcrd_set_error_message("Too many security option to add, the limit is %d", LIST_SIZE_MAX); + ret = -1; + goto out; + } + for (k = 0; k < host_spec->security_opt_len; k++) { + if (strncmp(host_spec->security_opt[k], "seccomp=", strlen("seccomp=")) != 0) { + continue; + } + ret = make_sure_oci_spec_linux(oci_spec); + if (ret < 0) { + goto out; + } + // free default seccomp + free_oci_runtime_config_linux_seccomp(oci_spec->linux->seccomp); + oci_spec->linux->seccomp = NULL; + + seccomp_json = host_spec->security_opt[k] + strlen("seccomp") + 1; + if (strcmp(seccomp_json, "unconfined") == 0) { + continue; + } + docker_seccomp = docker_seccomp_parse_data((const char *)seccomp_json, NULL, &err); + if (docker_seccomp == NULL) { + ERROR("Failed to parse host config data:%s", err); + ret = -1; + goto out; + } + oci_spec->linux->seccomp = trans_docker_seccomp_to_oci_format(docker_seccomp, oci_spec->process->capabilities); + if (oci_spec->linux->seccomp == NULL) { + ERROR("Failed to trans docker seccomp format to oci profile"); + ret = -1; + goto out; + } + free_docker_seccomp(docker_seccomp); + docker_seccomp = NULL; + } + +out: + free(err); + free_docker_seccomp(docker_seccomp); + return ret; +} + diff --git a/src/services/execution/spec/specs_security.h b/src/services/execution/spec/specs_security.h new file mode 100644 index 0000000..536781b --- /dev/null +++ b/src/services/execution/spec/specs_security.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide specs definition + ******************************************************************************/ +#ifndef __SPECS_SECURITY_H__ +#define __SPECS_SECURITY_H__ + +#include +#include "liblcrd.h" +#include "host_config.h" +#include "container_custom_config.h" +#include "container_config_v2.h" +#include "oci_runtime_spec.h" +int merge_default_seccomp_spec(oci_runtime_spec *oci_spec, + const oci_runtime_spec_process_capabilities *capabilites); +int merge_caps(oci_runtime_spec *oci_spec, const char **adds, size_t adds_len, const char **drops, + size_t drops_len); +int refill_oci_process_capabilities(oci_runtime_spec_process_capabilities **caps, + const char **src_caps, size_t src_caps_len); +int merge_sysctls(oci_runtime_spec *oci_spec, const json_map_string_string *sysctls); +int merge_no_new_privileges(oci_runtime_spec *oci_spec, const host_config *host_spec); +int adapt_settings_for_system_container(oci_runtime_spec *oci_spec, const host_config *host_spec); +int merge_seccomp(oci_runtime_spec *oci_spec, const host_config *host_spec); + +#endif diff --git a/src/services/execution/spec/sysinfo.c b/src/services/execution/spec/sysinfo.c new file mode 100644 index 0000000..ecd5e45 --- /dev/null +++ b/src/services/execution/spec/sysinfo.c @@ -0,0 +1,1341 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: maoweiyong + * Create: 2017-11-22 + * Description: provide system information functions + ******************************************************************************/ +#include +#include +#include +#include +#include +#include + +#include "error.h" +#include "liblcrd.h" +#include "sysinfo.h" +#include "log.h" +#include "securec.h" +#include "read_file.h" + +// Cgroup Item Definition +#define CGROUP_BLKIO_WEIGHT "blkio.weight" +#define CGROUP_BLKIO_WEIGHT_DEVICE "blkio.weight_device" +#define CGROUP_BLKIO_READ_BPS_DEVICE "blkio.throttle.read_bps_device" +#define CGROUP_BLKIO_WRITE_BPS_DEVICE "blkio.throttle.write_bps_device" +#define CGROUP_BLKIO_READ_IOPS_DEVICE "blkio.throttle.read_iops_device" +#define CGROUP_BLKIO_WRITE_IOPS_DEVICE "blkio.throttle.write_iops_device" +#define CGROUP_CPU_SHARES "cpu.shares" +#define CGROUP_CPU_PERIOD "cpu.cfs_period_us" +#define CGROUP_CPU_QUOTA "cpu.cfs_quota_us" +#define CGROUP_CPU_RT_PERIOD "cpu.rt_period_us" +#define CGROUP_CPU_RT_RUNTIME "cpu.rt_runtime_us" +#define CGROUP_CPUSET_CPUS "cpuset.cpus" +#define CGROUP_CPUSET_MEMS "cpuset.mems" +#define CGROUP_MEMORY_LIMIT "memory.limit_in_bytes" +#define CGROUP_MEMORY_SWAP "memory.memsw.limit_in_bytes" +#define CGROUP_MEMORY_SWAPPINESS "memory.swappiness" +#define CGROUP_MEMORY_RESERVATION "memory.soft_limit_in_bytes" +#define CGROUP_KENEL_MEMORY_LIMIT "memory.kmem.limit_in_bytes" +#define CGROUP_MEMORY_OOM_CONTROL "memory.oom_control" + +struct layer { + char **controllers; + char *mountpoint; +}; + +static void free_list(char **str_list) +{ + int i; + + if (str_list == NULL) { + return; + } + + for (i = 0; str_list[i]; i++) { + free(str_list[i]); + str_list[i] = NULL; + } + + free(str_list); +} + +static void free_layer(struct layer **layers) +{ + struct layer **it = NULL; + + if (layers == NULL) { + return; + } + + for (it = layers; it && *it; it++) { + free((*it)->mountpoint); + (*it)->mountpoint = NULL; + free_list((*it)->controllers); + (*it)->controllers = NULL; + free(*it); + *it = NULL; + } + free(layers); +} + +static int add_null_to_list(void ***list) +{ + int ret = 0; + size_t index = 0; + size_t newsize, oldsize; + void **newlist = NULL; + + if (*list != NULL) { + for (; (*list)[index] != NULL; index++) { + } + } + + if (index > SIZE_MAX / sizeof(void **) - 2) { + ERROR("Out of range"); + return -1; + } + newsize = (index + 2) * sizeof(void **); + oldsize = index * sizeof(void **); + ret = mem_realloc((void **)&newlist, newsize, (*list), oldsize); + if (ret < 0) { + ERROR("Out of memory"); + return -1; + } + *list = newlist; + (*list)[index + 1] = NULL; + return (int)index; +} + +static int append_string(char ***list, const char *entry) +{ + int index; + char *dup_entry = NULL; + + index = add_null_to_list((void ***)list); + if (index < 0) { + return -1; + } + dup_entry = util_strdup_s(entry); + if (dup_entry == NULL) { + return -1; + } + (*list)[index] = dup_entry; + return 0; +} + +static int append_subsystem_to_list(char ***klist, char ***nlist, const char *ptoken) +{ + int ret = 0; + + if (strncmp(ptoken, "name=", 5) == 0) { + ret = append_string(nlist, ptoken); + if (ret != 0) { + ERROR("Failed to append string"); + return -1; + } + } else { + ret = append_string(klist, ptoken); + if (ret != 0) { + ERROR("Failed to append string"); + return -1; + } + } + + return 0; +} + +static int get_cgroup_subsystems(char ***klist, char ***nlist) +{ + int ret = 0; + size_t length = 0; + FILE *fp = NULL; + char *pline = NULL; + + fp = util_fopen("/proc/self/cgroup", "r"); + if (fp == NULL) { + return -1; + } + + while (getline(&pline, &length, fp) != -1) { + char *pos = NULL; + char *pos2 = NULL; + char *ptoken = NULL; + char *psave = NULL; + pos = strchr(pline, ':'); + if (pos == NULL) { + ERROR("Invalid cgroup entry: must contain at least two colons: %s", pline); + ret = -1; + goto out; + } + pos++; + pos2 = strchr(pos, ':'); + if (pos2 == NULL) { + ERROR("Invalid cgroup entry: must contain at least two colons: %s", pline); + ret = -1; + goto out; + } + *pos2 = '\0'; + + if ((pos2 - pos) == 0) { + INFO("Not supported cgroup entry: %s", pline); + continue; + } + + for (ptoken = strtok_r(pos, ",", &psave); ptoken; ptoken = strtok_r(NULL, ",", &psave)) { + if (append_subsystem_to_list(klist, nlist, ptoken)) { + goto out; + } + } + } + +out: + free(pline); + fclose(fp); + if (ret != 0) { + free_list(*klist); + *klist = NULL; + free_list(*nlist); + *nlist = NULL; + } + return ret; +} + +static bool list_contain_string(const char **a_list, const char *str) +{ + int i; + + if (a_list == NULL) { + return false; + } + + for (i = 0; a_list[i]; i++) { + if (strcmp(a_list[i], str) == 0) { + return true; + } + } + + return false; +} + +static char *cgroup_legacy_must_prefix_named(const char *entry) +{ + size_t len; + char *prefixed = NULL; + const char *prefix = "name="; + + len = strlen(entry); + + if (((SIZE_MAX - len) - 1) < strlen(prefix)) { + ERROR("Out of memory"); + return NULL; + } + + prefixed = util_common_calloc_s(len + strlen(prefix) + 1); + if (prefixed == NULL) { + ERROR("Out of memory"); + return NULL; + } + + if (memcpy_s(prefixed, len + strlen(prefix) + 1, prefix, strlen(prefix)) != EOK) { + ERROR("Failed to copy memory!"); + free(prefixed); + return NULL; + } + if (memcpy_s(prefixed + strlen(prefix), len + 1, entry, len) != EOK) { + ERROR("Failed to copy memory!"); + free(prefixed); + return NULL; + } + prefixed[len + strlen(prefix)] = '\0'; + return prefixed; +} + +static int append_controller(const char **klist, const char **nlist, char ***clist, const char *entry) +{ + int index; + char *dup_entry = NULL; + + if (list_contain_string(klist, entry) && list_contain_string(nlist, entry)) { + ERROR("Refusing to use ambiguous controller \"%s\"", entry); + ERROR("It is both a named and kernel subsystem"); + return -1; + } + + index = add_null_to_list((void ***)clist); + if (index < 0) { + return -1; + } + + if (strncmp(entry, "name=", 5) == 0) { + dup_entry = util_strdup_s(entry); + } else if (list_contain_string(klist, entry)) { + dup_entry = util_strdup_s(entry); + } else { + dup_entry = cgroup_legacy_must_prefix_named(entry); + } + if (dup_entry == NULL) { + return -1; + } + (*clist)[index] = dup_entry; + return 0; +} + +static inline bool is_cgroup_mountpoint(const char *mp) +{ + return strncmp(mp, "/sys/fs/cgroup/", strlen("/sys/fs/cgroup/")) == 0; +} + +static void set_char_to_terminator(char *p) +{ + *p = '\0'; +} + +static char **cgroup_get_controllers(const char **klist, const char **nlist, const char *line) +{ + int index; + char *dup = NULL; + char *pos2 = NULL; + char *tok = NULL; + const char *pos = line; + char *psave = NULL; + char *sep = ","; + char **pret = NULL; + + for (index = 0; index < 4; index++) { + pos = strchr(pos, ' '); + if (pos == NULL) { + ERROR("Invalid mountinfo format \"%s\"", line); + return NULL; + } + pos++; + } + + if (!is_cgroup_mountpoint(pos)) { + return NULL; + } + + pos += strlen("/sys/fs/cgroup/"); + pos2 = strchr(pos, ' '); + if (pos2 == NULL) { + ERROR("Invalid mountinfo format \"%s\"", line); + return NULL; + } + set_char_to_terminator(pos2); + + dup = util_strdup_s(pos); + *pos2 = ' '; + + for (tok = strtok_r(dup, sep, &psave); tok; tok = strtok_r(NULL, sep, &psave)) { + if (append_controller(klist, nlist, &pret, tok)) { + ERROR("Failed to append controller"); + free_list(pret); + pret = NULL; + break; + } + } + + free(dup); + + return pret; +} + +/* add hierarchy */ +static int cgroup_add_layer(struct layer ***layers, char **clist, char *mountpoint) +{ + int index; + struct layer *newh = NULL; + + newh = util_common_calloc_s(sizeof(struct layer)); + if (newh == NULL) { + return -1; + } + + newh->controllers = clist; + newh->mountpoint = mountpoint; + index = add_null_to_list((void ***)layers); + if (index < 0) { + free(newh); + return -1; + } + (*layers)[index] = newh; + return 0; +} + +int cgroup_get_mountpoint_and_root(char *pline, char **mountpoint, char **root) +{ + int index; + char *posmp = NULL; + char *posrt = NULL; + char *pos = pline; + + // find root + for (index = 0; index < 3; index++) { + pos = strchr(pos, ' '); + if (pos == NULL) { + return -1; + } + pos++; + } + posrt = pos; + + // find mountpoint + pos = strchr(pos, ' '); + if (pos == NULL) { + return -1; + } + + *pos = '\0'; + if (root != NULL) { + *root = util_strdup_s(posrt); + } + + pos++; + posmp = pos; + + if (!is_cgroup_mountpoint(posmp)) { + return -1; + } + + pos = strchr(pos + strlen("/sys/fs/cgroup/"), ' '); + if (pos == NULL) { + return -1; + } + *pos = '\0'; + + if (mountpoint != NULL) { + *mountpoint = util_strdup_s(posmp); + } + + return 0; +} + +static bool lists_intersect(const char **controllers, const char **list) +{ + int index; + + if (controllers == NULL || list == NULL) { + return false; + } + + for (index = 0; controllers[index]; index++) { + if (list_contain_string(list, controllers[index])) { + return true; + } + } + + return false; +} + +static bool controller_list_is_dup(struct layer **llist, const char **clist) +{ + int index; + + if (llist == NULL) { + return false; + } + + for (index = 0; llist[index]; index++) { + if (lists_intersect((const char **)llist[index]->controllers, (const char **)clist)) { + return true; + } + } + + return false; +} + +static struct layer **cgroup_layers_find(void) +{ + int nret; + FILE *fp = NULL; + size_t length = 0; + char *pline = NULL; + char **klist = NULL; + char **nlist = NULL; + struct layer **layers = NULL; + + nret = get_cgroup_subsystems(&klist, &nlist); + if (nret < 0) { + ERROR("Failed to retrieve available legacy cgroup controllers\n"); + return NULL; + } + + fp = util_fopen("/proc/self/mountinfo", "r"); + if (fp == NULL) { + ERROR("Failed to open \"/proc/self/mountinfo\"\n"); + goto out; + } + + while (getline(&pline, &length, fp) != -1) { + char *mountpoint = NULL; + char **clist = NULL; + int mret; + + clist = cgroup_get_controllers((const char **)klist, (const char **)nlist, pline); + if (clist == NULL) { + goto list_out; + } + + if (controller_list_is_dup(layers, (const char **)clist)) { + goto list_out; + } + + mret = cgroup_get_mountpoint_and_root(pline, &mountpoint, NULL); + if (mret != 0 || mountpoint == NULL) { + ERROR("Failed parsing mountpoint from \"%s\"\n", pline); + goto list_out; + } + + nret = cgroup_add_layer(&layers, clist, mountpoint); + if (nret != 0) { + ERROR("Failed to add hierarchies"); + goto list_out; + } + + continue; +list_out: + free_list(clist); + free(mountpoint); + } +out: + free_list(klist); + free_list(nlist); + if (fp != NULL) { + fclose(fp); + } + free(pline); + return layers; +} + +/* cgroup enabled */ +static bool cgroup_enabled(const char *mountpoint, const char *name) +{ + char path[PATH_MAX] = { 0 }; + int nret; + + nret = sprintf_s(path, sizeof(path), "%s/%s", mountpoint, name); + if (nret < 0) { + ERROR("Path is too long"); + return false; + } + return util_file_exists(path); +} + +static char *cgroup_get_pagesize(const char *pline) +{ + size_t headlen; + char *pos2 = NULL; + const char *pos = pline; + + headlen = strlen("Hugepagesize"); + if (strncmp(pos, "Hugepagesize", headlen) != 0) { + return NULL; + } + + pos2 = strchr(pos + headlen, ':'); + if (pos2 == NULL) { + ERROR("Invalid Hugepagesize format \"%s\"", pline); + return NULL; + } + *pos2 = '\0'; + pos2++; + return util_string_delchar(pos2, ' '); +} + +/* get default huge page size */ +char *get_default_huge_page_size(void) +{ + int ret = 0; + int64_t sizenum = 0; + size_t length = 0; + FILE *fp = NULL; + char *pagesize = NULL; + char *humansize = NULL; + char *pline = NULL; + + fp = util_fopen("/proc/meminfo", "r"); + if (fp == NULL) { + ERROR("Failed to open \"/proc/meminfo\"\n"); + return NULL; + } + + while (getline(&pline, &length, fp) != -1) { + pagesize = cgroup_get_pagesize(pline); + if (pagesize != NULL) { + break; + } + } + if (pagesize == NULL) { + ERROR("Failed to get hugepage size"); + goto out; + } + + util_trim_newline(pagesize); + + ret = util_parse_byte_size_string(pagesize, &sizenum); + if (ret != 0) { + ERROR("Invalid page size: %s", pagesize); + goto out; + } + + humansize = util_human_size((uint64_t)sizenum); +out: + fclose(fp); + free(pagesize); + free(pline); + return humansize; +} + +/* get default total mem size */ +uint64_t get_default_total_mem_size(void) +{ + FILE *fp = NULL; + size_t len = 0; + char *line = NULL; + char *p = NULL; + uint64_t sysmem_limit = 0; + + fp = util_fopen("/proc/meminfo", "r"); + if (fp == NULL) { + ERROR("Failed to open /proc/meminfo: %s", strerror(errno)); + return sysmem_limit; + } + + while (getline(&line, &len, fp) != -1) { + p = strchr(line, ' '); + if (p == NULL) { + goto out; + } + *p = '\0'; + p++; + if (strcmp(line, "MemTotal:") == 0) { + while (*p == ' ' || *p == '\t') { + p++; + } + if (*p == '\0') { + goto out; + } + sysmem_limit = strtoull(p, NULL, 0); + break; + } + } + +out: + fclose(fp); + free(line); + return sysmem_limit * SIZE_KB; +} + +/* get default operating system */ +char *get_operating_system(void) +{ + size_t len = 0; + FILE *fp; + char *prettyname = NULL; + char *pretty_name = "PRETTY_NAME="; + char *line = NULL; + + fp = fopen(etcOsRelease, "r"); + if (fp == NULL) { + INFO("Failed to open %s :%s", etcOsRelease, strerror(errno)); + fp = fopen(altOsRelease, "r"); + if (fp == NULL) { + ERROR("Failed to open %s :%s", altOsRelease, strerror(errno)); + goto out; + } + } + + while (getline(&line, &len, fp) != -1) { + if ((strncmp(line, pretty_name, strlen(pretty_name))) == 0) { + prettyname = util_strdup_s(line + strlen(pretty_name)); + break; + } + } + + prettyname = util_trim_quotation(prettyname); + +out: + if (fp != NULL) { + fclose(fp); + } + free(line); + if (prettyname != NULL) { + return prettyname; + } + return util_strdup_s("Linux"); +} + +static void cgroup_do_log(bool quiet, bool do_log, const char *msg) +{ + if (!quiet && do_log) { + WARN("%s", msg); + } +} + +static char *find_cgroup_subsystem_mountpoint(struct layer **layers, const char *subsystem) +{ + struct layer **it = NULL; + + for (it = layers; it && *it; it++) { + char **cit = NULL; + + for (cit = (*it)->controllers; cit && *cit; cit++) { + if (strcmp(*cit, subsystem) == 0) { + return (*it)->mountpoint; + } + } + } + return NULL; +} + +/* check cgroup mem */ +static void check_cgroup_mem(struct layer **layers, bool quiet, cgroup_mem_info_t *meminfo) +{ + char *mountpoint = NULL; + + mountpoint = find_cgroup_subsystem_mountpoint(layers, "memory"); + if (mountpoint == NULL) { + cgroup_do_log(quiet, true, "Your kernel does not support cgroup memory limit"); + return; + } + + meminfo->limit = true; + + meminfo->swap = cgroup_enabled(mountpoint, CGROUP_MEMORY_SWAP); + cgroup_do_log(quiet, !(meminfo->swap), "Your kernel does not support swap memory limit"); + + meminfo->reservation = cgroup_enabled(mountpoint, CGROUP_MEMORY_RESERVATION); + cgroup_do_log(quiet, !(meminfo->reservation), "Your kernel does not support memory reservation"); + + meminfo->oomkilldisable = cgroup_enabled(mountpoint, CGROUP_MEMORY_OOM_CONTROL); + cgroup_do_log(quiet, !(meminfo->oomkilldisable), "Your kernel does not support oom control"); + + meminfo->swappiness = cgroup_enabled(mountpoint, CGROUP_MEMORY_SWAPPINESS); + cgroup_do_log(quiet, !(meminfo->swappiness), "Your kernel does not support memory swappiness"); + + meminfo->kernel = cgroup_enabled(mountpoint, CGROUP_KENEL_MEMORY_LIMIT); + cgroup_do_log(quiet, !(meminfo->kernel), "Your kernel does not support kernel memory limit"); +} + +/* check cgroup cpu */ +static void check_cgroup_cpu(struct layer **layers, bool quiet, cgroup_cpu_info_t *cpuinfo) +{ + char *mountpoint = NULL; + + mountpoint = find_cgroup_subsystem_mountpoint(layers, "cpu"); + if (mountpoint == NULL) { + cgroup_do_log(quiet, true, "Unable to find cpu cgroup in mounts"); + return; + } + + cpuinfo->cpu_rt_period = cgroup_enabled(mountpoint, CGROUP_CPU_RT_PERIOD); + cgroup_do_log(quiet, !(cpuinfo->cpu_rt_period), "Your kernel does not support cgroup rt period"); + + cpuinfo->cpu_rt_runtime = cgroup_enabled(mountpoint, CGROUP_CPU_RT_RUNTIME); + cgroup_do_log(quiet, !(cpuinfo->cpu_rt_runtime), "Your kernel does not support cgroup rt runtime"); + + cpuinfo->cpu_shares = cgroup_enabled(mountpoint, CGROUP_CPU_SHARES); + cgroup_do_log(quiet, !(cpuinfo->cpu_shares), "Your kernel does not support cgroup cpu shares"); + + cpuinfo->cpu_cfs_period = cgroup_enabled(mountpoint, CGROUP_CPU_PERIOD); + cgroup_do_log(quiet, !(cpuinfo->cpu_cfs_period), "Your kernel does not support cgroup cfs period"); + + cpuinfo->cpu_cfs_quota = cgroup_enabled(mountpoint, CGROUP_CPU_QUOTA); + cgroup_do_log(quiet, !(cpuinfo->cpu_cfs_quota), "Your kernel does not support cgroup cfs quota"); +} + +/* check cgroup blkio info */ +static void check_cgroup_blkio_info(struct layer **layers, bool quiet, cgroup_blkio_info_t *blkioinfo) +{ + char *mountpoint = NULL; + + mountpoint = find_cgroup_subsystem_mountpoint(layers, "blkio"); + if (mountpoint == NULL) { + cgroup_do_log(quiet, true, "Unable to find blkio cgroup in mounts"); + return; + } + + blkioinfo->blkio_weight = cgroup_enabled(mountpoint, CGROUP_BLKIO_WEIGHT); + cgroup_do_log(quiet, !(blkioinfo->blkio_weight), "Your kernel does not support cgroup blkio weight"); + + blkioinfo->blkio_weight_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_WEIGHT_DEVICE); + cgroup_do_log(quiet, !(blkioinfo->blkio_weight_device), "Your kernel does not support cgroup blkio weight_device"); + + blkioinfo->blkio_read_bps_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_READ_BPS_DEVICE); + cgroup_do_log(quiet, !(blkioinfo->blkio_read_bps_device), + "Your kernel does not support cgroup blkio throttle.read_bps_device"); + + blkioinfo->blkio_write_bps_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_WRITE_BPS_DEVICE); + cgroup_do_log(quiet, !(blkioinfo->blkio_write_bps_device), + "Your kernel does not support cgroup blkio throttle.write_bps_device"); + + blkioinfo->blkio_read_iops_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_READ_IOPS_DEVICE); + cgroup_do_log(quiet, !(blkioinfo->blkio_read_iops_device), + "Your kernel does not support cgroup blkio throttle.read_iops_device"); + + blkioinfo->blkio_write_iops_device = cgroup_enabled(mountpoint, CGROUP_BLKIO_WRITE_IOPS_DEVICE); + cgroup_do_log(quiet, !(blkioinfo->blkio_write_iops_device), + "Your kernel does not support cgroup blkio throttle.write_iops_device"); +} + +/* check cgroup cpuset info */ +static void check_cgroup_cpuset_info(struct layer **layers, bool quiet, cgroup_cpuset_info_t *cpusetinfo) +{ + size_t file_size = 0; + errno_t nret = EOK; + char *mountpoint = NULL; + char cpuset_cpus_path[PATH_MAX] = { 0 }; + char cpuset_mems_path[PATH_MAX] = { 0 }; + + mountpoint = find_cgroup_subsystem_mountpoint(layers, "cpuset"); + if (mountpoint == NULL) { + cgroup_do_log(quiet, true, ("Unable to find cpuset cgroup in mounts")); + return; + } + + nret = sprintf_s(cpuset_cpus_path, sizeof(cpuset_cpus_path), "%s/%s", mountpoint, CGROUP_CPUSET_CPUS); + if (nret < 0) { + ERROR("Path is too long"); + goto error; + } + + cpusetinfo->cpus = read_file(cpuset_cpus_path, &file_size); + if (cpusetinfo->cpus == NULL) { + ERROR("Failed to read the file: %s", cpuset_cpus_path); + goto error; + } + + nret = sprintf_s(cpuset_mems_path, sizeof(cpuset_mems_path), "%s/%s", mountpoint, CGROUP_CPUSET_MEMS); + if (nret < 0) { + ERROR("Path is too long"); + goto error; + } + + cpusetinfo->mems = read_file(cpuset_mems_path, &file_size); + if (cpusetinfo->mems == NULL) { + ERROR("Failed to read the file: %s", cpuset_mems_path); + goto error; + } + cpusetinfo->cpus = util_trim_space(cpusetinfo->cpus); + cpusetinfo->mems = util_trim_space(cpusetinfo->mems); + cpusetinfo->cpuset = true; + return; +error: + free(cpusetinfo->cpus); + cpusetinfo->cpus = NULL; + free(cpusetinfo->mems); + cpusetinfo->mems = NULL; +} + +/* check cgroup pids */ +static void check_cgroup_pids(bool quiet, cgroup_pids_info_t *pidsinfo) +{ + int ret = 0; + char *pidsmp = NULL; + + ret = find_cgroup_mountpoint_and_root("pids", &pidsmp, NULL); + if (ret != 0 || pidsmp == NULL) { + if (!quiet) { + WARN("Unable to find pids cgroup in mounts"); + } + goto out; + } + + pidsinfo->pidslimit = true; +out: + free(pidsmp); +} + +/* check cgroup files */ +static void check_cgroup_files(bool quiet, cgroup_files_info_t *filesinfo) +{ + int ret = 0; + char *filesmp = NULL; + + ret = find_cgroup_mountpoint_and_root("files", &filesmp, NULL); + if (ret != 0 || filesmp == NULL) { + if (!quiet) { + WARN("Unable to find pids cgroup in mounts"); + } + goto out; + } + + filesinfo->fileslimit = true; +out: + free(filesmp); +} + +/* find cgroup mountpoint and root */ +int find_cgroup_mountpoint_and_root(const char *subsystem, char **mountpoint, char **root) +{ + int ret = 0; + FILE *fp = NULL; + size_t length = 0; + char *pline = NULL; + + fp = util_fopen("/proc/self/mountinfo", "r"); + if (fp == NULL) { + ERROR("Failed to open \"/proc/self/mountinfo\"\n"); + ret = -1; + goto free_out; + } + + while (getline(&pline, &length, fp) != -1) { + char *dup = NULL; + char *p = NULL; + char *tok = NULL; + char *mp = NULL; + char *rt = NULL; + char *saveptr = NULL; + char *sep = ","; + int mret; + + mret = cgroup_get_mountpoint_and_root(pline, &mp, &rt); + if (mret != 0 || mp == NULL || rt == NULL) { + goto mp_out; + } + + p = mp; + p += strlen("/sys/fs/cgroup/"); + dup = util_strdup_s(p); + if (dup == NULL) { + ERROR("Out of memory"); + free(mp); + ret = -1; + goto free_out; + } + + for (tok = strtok_r(dup, sep, &saveptr); tok; tok = strtok_r(NULL, sep, &saveptr)) { + if (strcmp(tok, subsystem) != 0) { + continue; + } + if (mountpoint != NULL) { + *mountpoint = mp; + } else { + free(mp); + } + if (root != NULL) { + *root = rt; + } else { + free(rt); + } + free(dup); + goto free_out; + } + free(dup); +mp_out: + free(mp); + free(rt); + continue; + } +free_out: + if (fp != NULL) { + fclose(fp); + } + free(pline); + return ret; +} + +/* check cgroup hugetlb */ +static void check_cgroup_hugetlb(struct layer **layers, bool quiet, cgroup_hugetlb_info_t *hugetlbinfo) +{ + int nret; + char *mountpoint = NULL; + char *defaultpagesize = NULL; + char hugetlbpath[64] = { 0x00 }; + + mountpoint = find_cgroup_subsystem_mountpoint(layers, "hugetlb"); + if (mountpoint == NULL) { + cgroup_do_log(quiet, true, "Your kernel does not support cgroup hugetlb limit"); + return; + } + defaultpagesize = get_default_huge_page_size(); + if (defaultpagesize == NULL) { + WARN("Your kernel does not support cgroup hugetlb limit"); + return; + } + nret = sprintf_s(hugetlbpath, sizeof(hugetlbpath), "hugetlb.%s.limit_in_bytes", defaultpagesize); + if (nret < 0) { + WARN("Failed to print hugetlb path"); + goto free_out; + } + hugetlbinfo->hugetlblimit = cgroup_enabled(mountpoint, hugetlbpath); + cgroup_do_log(quiet, !hugetlbinfo->hugetlblimit, ("Your kernel does not support hugetlb limit")); + +free_out: + free(defaultpagesize); +} + +/* get huge page sizes */ +static char **get_huge_page_sizes() +{ + int index; + int ret = 0; + char *hugetlbmp = NULL; + char **hps = NULL; + DIR *dir = NULL; + struct dirent *info_archivo = NULL; + + ret = find_cgroup_mountpoint_and_root("hugetlb", &hugetlbmp, NULL); + if (ret != 0 || hugetlbmp == NULL) { + ERROR("Hugetlb cgroup not supported"); + return NULL; + } + + dir = opendir(hugetlbmp); + if (dir == NULL) { + ERROR("Failed to open hugetlb cgroup directory: %s", hugetlbmp); + goto free_out; + } + info_archivo = readdir(dir); + for (; info_archivo != NULL; info_archivo = readdir(dir)) { + char *contain = NULL; + char *dup = NULL; + char *pos = NULL; + char *dot2 = NULL; + + contain = strstr(info_archivo->d_name, "limit_in_bytes"); + if (contain == NULL) { + continue; + } + + dup = util_strdup_s(info_archivo->d_name); + if (dup == NULL) { + goto free_out; + } + + pos = dup; + pos = strchr(pos, '.'); + if (pos == NULL) { + goto dup_free; + } + *pos = '\0'; + pos++; + dot2 = strchr(pos, '.'); + if (dot2 == NULL) { + goto dup_free; + } + *dot2 = '\0'; + + index = add_null_to_list((void ***)&hps); + if (index < 0) { + free(dup); + free_list(hps); + hps = NULL; + goto free_out; + } + hps[index] = util_strdup_s(pos); +dup_free: + free(dup); + continue; + } +free_out: + + free(hugetlbmp); + if (dir != NULL) { + closedir(dir); + } + return hps; +} + +/* is huge pagesize valid */ +static bool is_huge_pagesize_valid(const char *pagesize) +{ + int nret; + bool bret = false; + size_t hps_len; + char **hps = NULL; + char **it = NULL; + char hpsbuf[BUFSIZ] = { 0 }; + + hps = get_huge_page_sizes(); + if (hps == NULL) { + ERROR("Hugetlb cgroup not supported"); + goto free_out; + } + hps_len = util_array_len(hps); + if (hps_len == 0) { + ERROR("Hugetlb cgroup not supported"); + goto free_out; + } + + for (it = hps; *it; it++) { + nret = sprintf_s(hpsbuf, sizeof(hpsbuf), "%s ", *it); + if (nret < 0) { + ERROR("hps buf is too short"); + goto free_out; + } + if (strcmp(*it, pagesize) == 0) { + bret = true; + } + } + hpsbuf[strlen(hpsbuf) - 1] = '\0'; +free_out: + if (!bret) { + ERROR("Invalid hugepage size: %s, should be one of [%s]", pagesize, hpsbuf); + lcrd_set_error_message("Invalid hugepage size: %s, should be one of [%s]", pagesize, hpsbuf); + if (g_lcrd_errmsg == NULL) { + ERROR("Out of memory"); + } + } + free_list(hps); + return bret; +} + +// isHugeLimitValid check whether input hugetlb limit legal +// it will check whether the limit size is times of size +static void is_hugelimit_valid(const char *pagesize, uint64_t limit) +{ + int ret; + int64_t sizeint = 0; + + ret = util_parse_byte_size_string(pagesize, &sizeint); + if (ret < 0 || !sizeint) { + WARN("Invalid pagesize: %s", pagesize); + return; + } + if (limit % (uint64_t)sizeint != 0) { + WARN("HugeTlb limit should be times of hugepage size. " + "cgroup will down round to the nearest multiple"); + } +} + +// check whether hugetlb pagesize and limit legal +char *validate_hugetlb(const char *pagesize, uint64_t limit) +{ + char *newpagesize = NULL; + int64_t sizeint = 0; + + if (pagesize != NULL && strlen(pagesize)) { + int nret = util_parse_byte_size_string(pagesize, &sizeint); + if (nret < 0) { + ERROR("Invalid pagesize: %s", pagesize); + return NULL; + } + newpagesize = util_human_size((uint64_t)sizeint); + if (newpagesize == NULL) { + ERROR("Invalid pagesize: %s", pagesize); + return NULL; + } + bool valid = is_huge_pagesize_valid(newpagesize); + if (!valid) { + free(newpagesize); + return NULL; + } + } else { + newpagesize = get_default_huge_page_size(); + if (newpagesize == NULL) { + ERROR("Failed to get system hugepage size"); + return NULL; + } + } + + is_hugelimit_valid(newpagesize, limit); + + return newpagesize; +} + +/* free sysinfo */ +void free_sysinfo(sysinfo_t *sysinfo) +{ + if (sysinfo == NULL) { + return; + } + + free(sysinfo->cpusetinfo.cpus); + sysinfo->cpusetinfo.cpus = NULL; + + free(sysinfo->cpusetinfo.mems); + sysinfo->cpusetinfo.mems = NULL; + + free(sysinfo); +} + +/* get sys info */ +sysinfo_t *get_sys_info(bool quiet) +{ + struct layer **layers = NULL; + sysinfo_t *sysinfo = NULL; + bool ret = true; + + sysinfo = util_common_calloc_s(sizeof(sysinfo_t)); + if (sysinfo == NULL) { + ERROR("Out of memory"); + return NULL; + } + + layers = cgroup_layers_find(); + if (layers == NULL) { + ERROR("Failed to parse cgroup information"); + ret = false; + goto out; + } + + check_cgroup_mem(layers, quiet, &sysinfo->cgmeminfo); + check_cgroup_cpu(layers, quiet, &sysinfo->cgcpuinfo); + check_cgroup_hugetlb(layers, quiet, &sysinfo->hugetlbinfo); + check_cgroup_blkio_info(layers, quiet, &sysinfo->blkioinfo); + check_cgroup_cpuset_info(layers, quiet, &sysinfo->cpusetinfo); + check_cgroup_pids(quiet, &sysinfo->pidsinfo); + check_cgroup_files(quiet, &sysinfo->filesinfo); +out: + free_layer(layers); + if (!ret) { + free_sysinfo(sysinfo); + sysinfo = NULL; + } + return sysinfo; +} + +/* free mount info */ +void free_mount_info(mountinfo_t *info) +{ + if (info == NULL) { + return; + } + + free(info->root); + info->root = NULL; + + free(info->mountpoint); + info->mountpoint = NULL; + + free(info->opts); + info->opts = NULL; + + free(info->optional); + info->optional = NULL; + + free(info->fstype); + info->fstype = NULL; + + free(info->source); + info->source = NULL; + + free(info->vfsopts); + info->vfsopts = NULL; + + free(info); +} + +mountinfo_t *get_mount_info(const char *pline) +{ + size_t length; + int ret = 0; + mountinfo_t *info = NULL; + char **list = NULL; + + info = util_common_calloc_s(sizeof(mountinfo_t)); + if (info == NULL) { + ERROR("Out of memory"); + return NULL; + } + + list = util_string_split(pline, ' '); + if (list == NULL) { + ERROR("Out of memory"); + ret = -1; + goto free_out; + } + length = util_array_len(list); + if (length < 8) { + ERROR("Invalid mountinfo '%s'", pline); + ret = -1; + goto free_out; + } + + info->mountpoint = util_strdup_s(list[4]); + + if (strcmp(list[6], "-")) { + info->optional = util_strdup_s(list[6]); + } + +free_out: + util_free_array(list); + if (ret != 0) { + free_mount_info(info); + info = NULL; + } + return info; +} + +/* free mounts info */ +void free_mounts_info(mountinfo_t **minfos) +{ + mountinfo_t **it = NULL; + + if (minfos == NULL) { + return; + } + + for (it = minfos; it && *it; it++) { + free_mount_info(*it); + *it = NULL; + } + free(minfos); +} + +/* find mount info */ +mountinfo_t *find_mount_info(mountinfo_t **minfos, const char *dir) +{ + mountinfo_t **it = NULL; + + for (it = minfos; it && *it; it++) { + if ((*it)->mountpoint && !strcmp((*it)->mountpoint, dir)) { + return *it; + } + } + return NULL; +} + +/* getmountsinfo */ +mountinfo_t **getmountsinfo(void) +{ + mountinfo_t **minfos = NULL; + int ret = 0; + FILE *fp = NULL; + size_t length; + char *pline = NULL; + + fp = util_fopen("/proc/self/mountinfo", "r"); + if (fp == NULL) { + ERROR("Failed to open \"/proc/self/mountinfo\"\n"); + return NULL; + } + + while (getline(&pline, &length, fp) != -1) { + int index; + mountinfo_t *info; + + info = get_mount_info(pline); + if (info == NULL) { + ret = -1; + goto free_out; + } + + index = add_null_to_list((void ***)&minfos); + if (index < 0) { + free_mount_info(info); + ret = -1; + goto free_out; + } + minfos[index] = info; + } +free_out: + + fclose(fp); + free(pline); + if (ret != 0) { + free_mounts_info(minfos); + minfos = NULL; + } + return minfos; +} diff --git a/src/services/execution/spec/sysinfo.h b/src/services/execution/spec/sysinfo.h new file mode 100644 index 0000000..f53a8df --- /dev/null +++ b/src/services/execution/spec/sysinfo.h @@ -0,0 +1,137 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container sysinfo definition + ******************************************************************************/ +#ifndef __SYSINFO_H +#define __SYSINFO_H + +#include +#include +#include + +#define etcOsRelease "/etc/os-release" +#define altOsRelease "/usr/lib/os-release" + +typedef struct { + bool limit; + bool swap; + bool reservation; + bool oomkilldisable; + bool swappiness; + bool kernel; +} cgroup_mem_info_t; + +typedef struct { + bool cpu_rt_period; + bool cpu_rt_runtime; + bool cpu_shares; + bool cpu_cfs_period; + bool cpu_cfs_quota; +} cgroup_cpu_info_t; + +typedef struct { + bool hugetlblimit; +} cgroup_hugetlb_info_t; + +typedef struct { + bool blkio_weight; + bool blkio_weight_device; + bool blkio_read_bps_device; + bool blkio_write_bps_device; + bool blkio_read_iops_device; + bool blkio_write_iops_device; +} cgroup_blkio_info_t; + +typedef struct { + bool cpuset; + char *cpus; + char *mems; +} cgroup_cpuset_info_t; + +typedef struct { + bool pidslimit; +} cgroup_pids_info_t; + +typedef struct { + bool fileslimit; +} cgroup_files_info_t; + +typedef struct { + cgroup_mem_info_t cgmeminfo; + cgroup_cpu_info_t cgcpuinfo; + cgroup_hugetlb_info_t hugetlbinfo; + cgroup_blkio_info_t blkioinfo; + cgroup_cpuset_info_t cpusetinfo; + cgroup_pids_info_t pidsinfo; + cgroup_files_info_t filesinfo; +} sysinfo_t; + +typedef struct { + // ID is a unique identifier of the mount (may be reused after umount). + int id; + + // Parent indicates the ID of the mount parent (or of self for the top of the + // mount tree). + int parent; + + // Major indicates one half of the device ID which identifies the device class. + int major; + + // Minor indicates one half of the device ID which identifies a specific + // instance of device. + int minor; + + // Root of the mount within the filesystem. + char *root; + + // Mountpoint indicates the mount point relative to the process's root. + char *mountpoint; + + // Opts represents mount-specific options. + char *opts; + + // Optional represents optional fields. + char *optional; + + // Fstype indicates the type of filesystem, such as EXT3. + char *fstype; + + // Source indicates filesystem specific information or "none". + char *source; + + // VfsOpts represents per super block options. + char *vfsopts; +} mountinfo_t; + +void free_sysinfo(sysinfo_t *sysinfo); + +// check whether hugetlb pagesize and limit legal +char *validate_hugetlb(const char *pagesize, uint64_t limit); + +int find_cgroup_mountpoint_and_root(const char *subsystem, char **mountpoint, char **root); + +sysinfo_t *get_sys_info(bool quiet); + +char *get_default_huge_page_size(void); + +uint64_t get_default_total_mem_size(void); + +char *get_operating_system(void); + +mountinfo_t **getmountsinfo(void); + +mountinfo_t *find_mount_info(mountinfo_t **minfos, const char *dir); + +void free_mounts_info(mountinfo_t **minfos); + +#endif /* __SYSINFO_H */ diff --git a/src/services/execution/spec/verify.c b/src/services/execution/spec/verify.c new file mode 100644 index 0000000..b2b186a --- /dev/null +++ b/src/services/execution/spec/verify.c @@ -0,0 +1,1973 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container verify functions + ******************************************************************************/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "constants.h" +#include "error.h" +#include "liblcrd.h" +#include "log.h" +#include "sysinfo.h" +#include "specs.h" +#include "verify.h" +#include "lcrd_config.h" + +/* verify hook timeout */ +static int verify_hook_timeout(int t) +{ + if (t < 0) { + ERROR("Hook spec timeout invalid"); + lcrd_set_error_message("Invalid timeout: %d", t); + return -1; + } + + return 0; +} + +/* verify hook path */ + +static int verify_hook_root_and_no_other(const struct stat *st, const char *path) +{ + /* validate file owner */ + if (st->st_uid != 0) { + ERROR("Hook file %s isn't right,file owner should be root", path); + lcrd_set_error_message("Hook file %s isn't right,file owner should be root", path); + return -1; + } + + /* validate file if can be written by other user */ + if (st->st_mode & S_IWOTH) { + ERROR("Hook path %s isn't right,file should not be written by non-root", path); + lcrd_set_error_message("%s should not be written by non-root", path); + return -1; + } + return 0; +} + +static int verify_hook_path(const char *path) +{ + int ret = 0; + struct stat st; + + /* validate absolute path */ + ret = util_validate_absolute_path(path); + if (ret != 0) { + ERROR("Hook path %s must be an absolute path", path); + lcrd_set_error_message("%s is not an absolute path", path); + goto out; + } + + ret = stat(path, &st); + /* validate file exits */ + if (ret < 0) { + ERROR("Hook path %s isn't exist", path); + lcrd_set_error_message("Cann't find path: %s", path); + ret = -1; + goto out; + } + + if (verify_hook_root_and_no_other(&st, path) != 0) { + ret = -1; + goto out; + } + +out: + return ret; +} + +static int verify_hook_conf(const defs_hook *p) +{ + int ret = 0; + /* validate hookpath */ + ret = verify_hook_path(p->path); + if (ret != 0) { + goto out; + } + /* validate timeout */ + ret = verify_hook_timeout(p->timeout); +out: + return ret; +} + +static inline bool is_mem_limit_minimum(int64_t limit) +{ + /* It's not kernel limit, we want this 4M limit to supply a reasonable functional container */ +#define LINUX_MIN_MEMORY 4194304 + + return limit != 0 && limit < LINUX_MIN_MEMORY; +} + +/* check memroy limit and memory swap */ +static int verify_mem_limit_swap(const sysinfo_t *sysinfo, int64_t limit, int64_t swap, bool update) +{ + int ret = 0; + + /* check the minimum memory limit */ + if (is_mem_limit_minimum(limit)) { + ERROR("Minimum memory limit allowed is 4MB"); + lcrd_set_error_message("Minimum memory limit allowed is 4MB"); + ret = -1; + goto out; + } + + if (limit > 0 && !(sysinfo->cgmeminfo.limit)) { + ERROR("Your kernel does not support memory limit capabilities. Limitation discarded."); + lcrd_set_error_message( + "Your kernel does not support memory limit capabilities. Limitation discarded."); + ret = -1; + goto out; + } + + if (limit > 0 && swap != 0 && !(sysinfo->cgmeminfo.swap)) { + ERROR("Your kernel does not support swap limit capabilities, memory limited without swap."); + lcrd_set_error_message( + "Your kernel does not support swap limit capabilities, memory limited without swap."); + ret = -1; + goto out; + } + + if (limit > 0 && swap > 0 && swap < limit) { + ERROR("Minimum memoryswap limit should be larger than memory limit, see usage."); + lcrd_set_error_message("Minimum memoryswap limit should be larger than memory limit"); + ret = -1; + goto out; + } + + if (limit == 0 && swap > 0 && !update) { + ERROR("You should always set the Memory limit when using Memoryswap limit, see usage."); + lcrd_set_error_message("You should set the memory limit when using memoryswap limit"); + ret = -1; + goto out; + } + +out: + return ret; +} + +static inline bool is_swappiness_invalid(uint64_t swapiness) +{ + return (int64_t)swapiness < -1 || swapiness > 100; +} + +/* verify memory swappiness */ +static int verify_memory_swappiness(const sysinfo_t *sysinfo, uint64_t swapiness) +{ + int ret = 0; + + if ((int64_t)swapiness != -1 && !(sysinfo->cgmeminfo.swappiness)) { + ERROR("Your kernel does not support memory swappiness capabilities, memory swappiness discarded."); + lcrd_set_error_message( + "Your kernel does not support memory swappiness capabilities, memory swappiness discarded."); + ret = -1; + goto out; + } + + if (is_swappiness_invalid(swapiness)) { + ERROR("Invalid value: %lld, valid memory swappiness range is 0-100", (long long)swapiness); + lcrd_set_error_message("Invalid value: %lld, valid memory swappiness range is 0-100", (long long)swapiness); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* verify memory reservation */ +static int verify_memory_reservation(const sysinfo_t *sysinfo, int64_t limit, int64_t reservation) +{ + int ret = 0; + + if (reservation > 0 && !(sysinfo->cgmeminfo.reservation)) { + ERROR("Your kernel does not support memory soft limit capabilities. Limitation discarded"); + lcrd_set_error_message("Your kernel does not support memory soft limit capabilities. Limitation discarded"); + ret = -1; + goto out; + } + + /* check the minimum memory limit */ + if (is_mem_limit_minimum(reservation)) { + ERROR("Minimum memory reservation allowed is 4MB"); + lcrd_set_error_message("Minimum memory reservation allowed is 4MB"); + ret = -1; + goto out; + } + + if (limit > 0 && reservation > 0 && limit < reservation) { + ERROR("Minimum memory limit should be larger than memory reservation limit, see usage."); + lcrd_set_error_message("Minimum memory limit should be larger than memory reservation limit, see usage."); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* check kernel version */ +static bool check_kernel_version(const char *version) +{ + struct utsname uts; + int ret; + + ret = uname(&uts); + if (ret < 0) { + WARN("Can not get kernel version: %s", strerror(errno)); + } else { + if (strverscmp(uts.release, version) < 0) { + return false; + } + } + return true; +} + +/* verify memory kernel */ +static int verify_memory_kernel(const sysinfo_t *sysinfo, int64_t kernel) +{ + int ret = 0; + + if (kernel > 0 && !(sysinfo->cgmeminfo.kernel)) { + ERROR("Your kernel does not support kernel memory limit capabilities. Limitation discarded."); + lcrd_set_error_message("Your kernel does not support kernel memory limit capabilities. Limitation discarded."); + ret = -1; + goto out; + } + + if (is_mem_limit_minimum(kernel)) { + ERROR("Minimum kernel memory limit allowed is 4MB"); + lcrd_set_error_message("Minimum kernel memory limit allowed is 4MB"); + ret = -1; + goto out; + } + + if (kernel > 0 && !check_kernel_version("4.0.0")) { + WARN("You specified a kernel memory limit on a kernel older than 4.0. " + "Kernel memory limits are experimental on older kernels, " + "it won't work as expected and can cause your system to be unstable."); + } + +out: + return ret; +} + +/* verify pids limit */ +static int verify_pids_limit(const sysinfo_t *sysinfo, int64_t pids_limit) +{ + int ret = 0; + + if (pids_limit != 0 && !(sysinfo->pidsinfo.pidslimit)) { + ERROR("Your kernel does not support pids limit capabilities, pids limit discarded."); + lcrd_set_error_message("Your kernel does not support pids limit capabilities, pids limit discarded."); + ret = -1; + } + return ret; +} + +/* verify files limit */ +static int verify_files_limit(const sysinfo_t *sysinfo, int64_t files_limit) +{ + int ret = 0; + + if (files_limit != 0 && !(sysinfo->filesinfo.fileslimit)) { + ERROR("Your kernel does not support files limit capabilities, files limit discarded."); + lcrd_set_error_message("Your kernel does not support files limit capabilities, files limit discarded."); + ret = -1; + } + return ret; +} + + +/* verify oom control */ +static int verify_oom_control(const sysinfo_t *sysinfo, bool oomdisable) +{ + int ret = 0; + + if (oomdisable && !(sysinfo->cgmeminfo.oomkilldisable)) { + ERROR("Your kernel does not support OomKillDisable, OomKillDisable discarded"); + lcrd_set_error_message("Your kernel does not support OomKillDisable, OomKillDisable discarded"); + ret = -1; + } + + return ret; +} + +/* verify resources memory */ +static int verify_resources_memory(const sysinfo_t *sysinfo, const oci_runtime_config_linux_resources_memory *memory) +{ + int ret = 0; + + ret = verify_mem_limit_swap(sysinfo, memory->limit, memory->swap, false); + if (ret != 0) { + goto out; + } + + ret = verify_memory_swappiness(sysinfo, memory->swappiness); + if (ret != 0) { + goto out; + } + + ret = verify_memory_reservation(sysinfo, memory->limit, memory->reservation); + if (ret != 0) { + goto out; + } + + ret = verify_memory_kernel(sysinfo, memory->kernel); + if (ret != 0) { + goto out; + } + + ret = verify_oom_control(sysinfo, memory->disable_oom_killer); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/* verify resources pids */ +static int verify_resources_pids(const sysinfo_t *sysinfo, const oci_runtime_config_linux_resources_pids *pids) +{ + int ret = 0; + + ret = verify_pids_limit(sysinfo, pids->limit); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +// ValidateResources performs platform specific validation of the resource settings +// cpu-rt-runtime and cpu-rt-period can not be greater than their parent, cpu-rt-runtime requires sys_nice +static int verify_cpu_realtime(const sysinfo_t *sysinfo, int64_t realtime_period, int64_t realtime_runtime) +{ + int ret = 0; + + if (realtime_period > 0 && !(sysinfo->cgcpuinfo.cpu_rt_period)) { + ERROR("Invalid --cpu-rt-period: Your kernel does not support cgroup rt period"); + lcrd_set_error_message("Invalid --cpu-rt-period: Your kernel does not support cgroup rt period"); + ret = -1; + goto out; + } + + if (realtime_runtime > 0 && !(sysinfo->cgcpuinfo.cpu_rt_runtime)) { + ERROR("Invalid --cpu-rt-runtime: Your kernel does not support cgroup rt runtime"); + lcrd_set_error_message("Invalid --cpu-rt-period: Your kernel does not support cgroup rt runtime"); + ret = -1; + goto out; + } + + if (realtime_period != 0 && realtime_runtime != 0 && realtime_runtime > realtime_period) { + ERROR("Invalid --cpu-rt-runtime: rt runtime cannot be higher than rt period"); + lcrd_set_error_message("Invalid --cpu-rt-runtime: rt runtime cannot be higher than rt period"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* verify cpu shares */ +static int verify_cpu_shares(const sysinfo_t *sysinfo, int64_t cpu_shares) +{ + int ret = 0; + + if (cpu_shares > 0 && !(sysinfo->cgcpuinfo.cpu_shares)) { + ERROR("Your kernel does not support cgroup cpu shares. Shares discarded."); + lcrd_set_error_message( + "Your kernel does not support cgroup cpu shares. Shares discarded."); + ret = -1; + } + + return ret; +} + +static int verify_cpu_cfs_period(const sysinfo_t *sysinfo, int64_t cpu_cfs_period) +{ + int ret = 0; + + if (cpu_cfs_period > 0 && !(sysinfo->cgcpuinfo.cpu_cfs_period)) { + ERROR("Your kernel does not support CPU cfs period. Period discarded."); + lcrd_set_error_message("Your kernel does not support CPU cfs period. Period discarded."); + ret = -1; + goto out; + } +out: + return ret; +} + +static inline bool is_cpu_cfs_quota_invalid(int64_t cpu_cfs_quota) +{ + return cpu_cfs_quota > 0 && cpu_cfs_quota < 1000; +} + +static int verify_cpu_cfs_quota(const sysinfo_t *sysinfo, int64_t cpu_cfs_quota) +{ + int ret = 0; + + if (cpu_cfs_quota > 0 && !(sysinfo->cgcpuinfo.cpu_cfs_quota)) { + ERROR("Your kernel does not support CPU cfs quato. Quota discarded."); + lcrd_set_error_message("Your kernel does not support CPU cfs quato. Quota discarded."); + ret = -1; + goto out; + } + + if (is_cpu_cfs_quota_invalid(cpu_cfs_quota)) { + ERROR("CPU cfs quota can not be less than 1ms (i.e. 1000)"); + lcrd_set_error_message("CPU cfs quota can not be less than 1ms (i.e. 1000)"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* verify cpu cfs scheduler */ +static int verify_cpu_cfs_scheduler(const sysinfo_t *sysinfo, int64_t cpu_cfs_period, int64_t cpu_cfs_quota) +{ + int ret = 0; + + ret = verify_cpu_cfs_period(sysinfo, cpu_cfs_period); + if (ret != 0) { + goto out; + } + + ret = verify_cpu_cfs_quota(sysinfo, cpu_cfs_quota); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/* max cpu */ +int max_cpu(const char *cores) +{ + int max = -1; + char *str = NULL; + char *tmp = NULL; + char *chr = NULL; + + str = util_strdup_s(cores); + if (str == NULL) { + goto out; + } + tmp = str; + chr = strchr(tmp, ','); + for (; chr != NULL || *tmp != '\0'; chr = strchr(tmp, ',')) { + char *subchr = NULL; + int value = 0; + if (chr != NULL) { + *chr++ = '\0'; + } + subchr = strchr(tmp, '-'); + if (subchr != NULL) { + *subchr++ = '\0'; + } else { + subchr = tmp; + } + if (util_safe_int(subchr, &value) || value < 0) { + max = -1; + goto out; + } + + if (value > max) { + max = value; + } + if (chr != NULL) { + tmp = chr; + } else { + break; + } + } +out: + free(str); + return max; +} + +/* check cpu */ +static bool check_cpu(const char *provided, const char *available) +{ + int max_available = 0; + int max_request = 0; + if (provided == NULL) { + return true; + } + max_available = max_cpu(available); + max_request = max_cpu(provided); + if (max_available == -1 || max_request == -1) { + ERROR("failed to get the number of cpus"); + return false; + } + if (max_request > max_available) { + ERROR("invalid maxRequest is %d, max available: %d", max_request, max_available); + lcrd_set_error_message("invalid maxRequest is %d, max available: %d", max_request, max_available); + return false; + } + return true; +} + +/* parse unit list */ +int parse_unit_list(const char *val, bool *available_list) +{ + int ret = -1; + char *str = NULL; + char *tmp = NULL; + char *chr = NULL; + if (val == NULL) { + return 0; + } + str = util_strdup_s(val); + tmp = str; + chr = strchr(tmp, ','); + for (; chr != NULL || *tmp != '\0'; chr = strchr(tmp, ',')) { + char *subchr = NULL; + if (chr != NULL) { + *chr++ = '\0'; + } + subchr = strchr(tmp, '-'); + if (subchr == NULL) { + int value = 0; + if (util_safe_int(tmp, &value) || value < 0) { + goto out; + } + available_list[value] = true; + } else { + int min = 0; + int max = 0; + int i = 0; + *subchr++ = '\0'; + if (util_safe_int(tmp, &min) || min < 0) { + goto out; + } + if (util_safe_int(subchr, &max) || max < 0) { + goto out; + } + for (i = min; i <= max; i++) { + available_list[i] = true; + } + } + if (chr != NULL) { + tmp = chr; + } else { + break; + } + } + ret = 0; +out: + free(str); + return ret; +} + +/* is cpuset list available */ +static bool is_cpuset_list_available(const char *provided, const char *available) +{ + int cpu_num = 0; + int i = 0; + bool ret = false; + bool *parsed_provided = NULL; + bool *parsed_available = NULL; + + cpu_num = get_nprocs(); + if (cpu_num <= 0) { + ERROR("failed to get the number of processors configured by the operating system!"); + goto out; + } + if ((size_t)cpu_num > SIZE_MAX / sizeof(bool)) { + ERROR("invalid cpu num"); + goto out; + } + parsed_provided = util_common_calloc_s(sizeof(bool) * (unsigned int)cpu_num); + if (parsed_provided == NULL) { + ERROR("memory alloc failed!"); + goto out; + } + parsed_available = util_common_calloc_s(sizeof(bool) * (unsigned int)cpu_num); + if (parsed_available == NULL) { + ERROR("memory alloc failed!"); + goto out; + } + + if (!check_cpu(provided, available)) { + goto out; + } + + if (parse_unit_list(provided, parsed_provided) < 0 || + parse_unit_list(available, parsed_available) < 0) { + goto out; + } + for (i = 0; i < cpu_num; i++) { + if (!parsed_provided[i] || parsed_available[i]) { + continue; + } + goto out; + } + ret = true; +out: + free(parsed_provided); + free(parsed_available); + return ret; +} + +/* is cpuset cpus available */ +bool is_cpuset_cpus_available(const sysinfo_t *sysinfo, const char *cpus) +{ + bool ret = false; + ret = is_cpuset_list_available(cpus, sysinfo->cpusetinfo.cpus); + if (!ret) { + ERROR("Checking cpuset.cpus got invalid format: %s.", cpus); + lcrd_set_error_message("Checking cpuset.cpus got invalid format: %s.", cpus); + } + return ret; +} + +/* is cpuset mems available */ +bool is_cpuset_mems_available(const sysinfo_t *sysinfo, const char *mems) +{ + bool ret = false; + ret = is_cpuset_list_available(mems, sysinfo->cpusetinfo.mems); + if (!ret) { + ERROR("Checking cpuset.mems got invalid format: %s.", mems); + lcrd_set_error_message("Checking cpuset.mems got invalid format: %s.", mems); + } + return ret; +} + +// cpuset subsystem checks and adjustments +static int verify_resources_cpuset(const sysinfo_t *sysinfo, const char *cpus, const char *mems) +{ + int ret = 0; + bool cpus_available = false; + bool mems_available = false; + + if (cpus != NULL && !(sysinfo->cpusetinfo.cpuset)) { + ERROR("Your kernel does not support cpuset. Cpuset discarded."); + lcrd_set_error_message("Your kernel does not support cpuset. Cpuset discarded."); + ret = -1; + goto out; + } + + if (mems != NULL && !(sysinfo->cpusetinfo.cpuset)) { + ERROR("Your kernel does not support cpuset. Cpuset discarded."); + lcrd_set_error_message("Your kernel does not support cpuset. Cpuset discarded."); + ret = -1; + goto out; + } + + cpus_available = is_cpuset_cpus_available(sysinfo, cpus); + if (!cpus_available) { + ERROR("Requested CPUs are not available - requested %s, available: %s.", cpus, sysinfo->cpusetinfo.cpus); + lcrd_set_error_message("Requested CPUs are not available - requested %s, available: %s.", cpus, + sysinfo->cpusetinfo.cpus); + ret = -1; + goto out; + } + + mems_available = is_cpuset_mems_available(sysinfo, mems); + if (!mems_available) { + ERROR("Requested memory nodes are not available - requested %s, available: %s.", + mems, sysinfo->cpusetinfo.mems); + lcrd_set_error_message("Requested memory nodes are not available - requested %s, available: %s.", + mems, sysinfo->cpusetinfo.mems); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* verify resources cpu */ +static int verify_resources_cpu(const sysinfo_t *sysinfo, const oci_runtime_config_linux_resources_cpu *cpu) +{ + int ret = 0; + + ret = verify_cpu_realtime(sysinfo, (int64_t)(cpu->realtime_period), cpu->realtime_runtime); + if (ret != 0) { + goto out; + } + + ret = verify_cpu_shares(sysinfo, (int64_t)(cpu->shares)); + if (ret != 0) { + goto out; + } + + ret = verify_cpu_cfs_scheduler(sysinfo, (int64_t)(cpu->period), cpu->quota); + if (ret != 0) { + goto out; + } + + ret = verify_resources_cpuset(sysinfo, cpu->cpus, cpu->mems); + if (ret != 0) { + goto out; + } +out: + return ret; +} + +static inline bool is_blkio_weight_invalid(int weight) +{ + return weight > 0 && (weight < 10 || weight > 1000); +} + +/* verify blkio weight */ +static int verify_blkio_weight(const sysinfo_t *sysinfo, int weight) +{ + int ret = 0; + + if (weight > 0 && !(sysinfo->blkioinfo.blkio_weight)) { + ERROR("Your kernel does not support Block I/O weight. Weight discarded."); + lcrd_set_error_message("Your kernel does not support Block I/O weight. Weight discarded."); + ret = -1; + goto out; + } + if (is_blkio_weight_invalid(weight)) { + ERROR("Range of blkio weight is from 10 to 1000."); + lcrd_set_error_message("Range of blkio weight is from 10 to 1000."); + ret = -1; + goto out; + } + +out: + return ret; +} + +static inline bool is_hostconfig_blkio_weight_invalid(uint16_t weight) +{ + return weight > 0 && (weight < 10 || weight > 1000); +} + +/* verify hostconfig blkio weight */ +static int verify_hostconfig_blkio_weight(const sysinfo_t *sysinfo, uint16_t weight) +{ + int ret = 0; + + if (weight > 0 && !(sysinfo->blkioinfo.blkio_weight)) { + ERROR("Your kernel does not support Block I/O weight. Weight in host config discarded."); + lcrd_set_error_message("Your kernel does not support Block I/O weight. Weight in host config discarded."); + ret = -1; + goto out; + } + if (is_hostconfig_blkio_weight_invalid(weight)) { + ERROR("Range of blkio weight is from 10 to 1000."); + lcrd_set_error_message("Range of blkio weight is from 10 to 1000."); + ret = -1; + goto out; + } + +out: + return ret; +} + +/* verify blkio device */ +static int verify_blkio_device(const sysinfo_t *sysinfo, size_t weight_device_len) +{ + int ret = 0; + + if (weight_device_len > 0 && !(sysinfo->blkioinfo.blkio_weight_device)) { + ERROR("Your kernel does not support Block I/O weight_device."); + lcrd_set_error_message("Your kernel does not support Block I/O weight_device."); + ret = -1; + } + + return ret; +} + +/* verify oom score adj */ +static int verify_oom_score_adj(int oom_score_adj) +{ + int ret = 0; + if (oom_score_adj < OOM_SCORE_ADJ_MIN || oom_score_adj > OOM_SCORE_ADJ_MAX) { + ERROR("Invalid value %d, range for oom score adj is [-1000, 1000].", oom_score_adj); + lcrd_set_error_message("Invalid value %d, range for oom score adj is [-1000, 1000].", oom_score_adj); + ret = -1; + } + return ret; +} + +#ifdef ENABLE_OCI_IMAGE +static bool is_storage_opts_valid(const json_map_string_string *storage_opts) +{ + size_t i; + + for (i = 0; i < storage_opts->len; i++) { + if (strcmp(storage_opts->keys[i], "size") != 0) { + // Only check key here, check value by image driver + ERROR("Unknown storage option: %s", storage_opts->keys[i]); + lcrd_set_error_message("Unknown storage option: %s", storage_opts->keys[i]); + return false; + } + } + return true; +} + +/* verify storage options */ +static int verify_storage_opts(const host_config *hc) +{ + int ret = 0; + char *driver = NULL; + json_map_string_string *storage_opts = NULL; + + if (hc != NULL) { + storage_opts = hc->storage_opt; + } + + driver = conf_get_lcrd_storage_driver(); + if (driver == NULL) { + ERROR("Failed to get storage driver"); + return -1; + } + + if (storage_opts == NULL || storage_opts->len == 0 || strcmp(driver, "overlay2") != 0) { + goto cleanup; + } + + if (storage_opts->len > 0) { + char *backing_fs = NULL; + backing_fs = conf_get_lcrd_storage_driver_backing_fs(); + if (backing_fs == NULL) { + ERROR("No backing fs detected"); + ret = -1; + goto cleanup; + } + if (strcmp(backing_fs, "xfs") == 0) { + WARN("Filesystem quota for overlay2 over xfs is not totally support"); + } + free(backing_fs); + } + + if (!is_storage_opts_valid(storage_opts)) { + ret = -1; + goto cleanup; + } + +cleanup: + free(driver); + return ret; +} +#endif + +/* verify blkio rw bps device */ +static int verify_blkio_rw_bps_device(const sysinfo_t *sysinfo, size_t throttle_read_bps_device_len, + size_t throttle_write_bps_device_len) +{ + int ret = 0; + + if (throttle_read_bps_device_len > 0 && !(sysinfo->blkioinfo.blkio_read_bps_device)) { + ERROR("Your kernel does not support Block read limit in bytes per second"); + lcrd_set_error_message("Your kernel does not support Block read limit in bytes per second"); + ret = -1; + goto out; + } + + if (throttle_write_bps_device_len > 0 && !(sysinfo->blkioinfo.blkio_write_bps_device)) { + ERROR("Your kernel does not support Block write limit in bytes per second"); + lcrd_set_error_message("Your kernel does not support Block write limit in bytes per second"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* verify blkio rw iops device */ +static int verify_blkio_rw_iops_device(const sysinfo_t *sysinfo, size_t throttle_read_iops_device_len, + size_t throttle_write_iops_device_len) +{ + int ret = 0; + + if (throttle_read_iops_device_len > 0 && !(sysinfo->blkioinfo.blkio_read_iops_device)) { + ERROR("Your kernel does not support Block read limit in IO per second"); + lcrd_set_error_message("Your kernel does not support Block read limit in IO per second"); + ret = -1; + goto out; + } + + if (throttle_write_iops_device_len > 0 && !(sysinfo->blkioinfo.blkio_write_iops_device)) { + ERROR("Your kernel does not support Block write limit in IO per second"); + lcrd_set_error_message("Your kernel does not support Block write limit in IO per second"); + ret = -1; + goto out; + } +out: + return ret; +} + +/* verify resources blkio */ +static int verify_resources_blkio(const sysinfo_t *sysinfo, const oci_runtime_config_linux_resources_block_io *blkio) +{ + int ret = 0; + + ret = verify_blkio_weight(sysinfo, blkio->weight); + if (ret != 0) { + goto out; + } + + ret = verify_blkio_device(sysinfo, blkio->weight_device_len); + if (ret != 0) { + goto out; + } + + ret = verify_blkio_rw_bps_device(sysinfo, blkio->throttle_read_bps_device_len, + blkio->throttle_write_bps_device_len); + if (ret != 0) { + goto out; + } + + ret = verify_blkio_rw_iops_device(sysinfo, blkio->throttle_read_iops_device_len, + blkio->throttle_write_iops_device_len); + if (ret != 0) { + goto out; + } +out: + return ret; +} + +static bool check_hugetlbs_repeated(size_t newlen, const char *pagesize, + const oci_runtime_config_linux_resources_hugepage_limits_element *hugetlb, + oci_runtime_config_linux_resources_hugepage_limits_element **newtlb) +{ + bool repeated = false; + size_t j; + + for (j = 0; j < newlen; j++) { + if (newtlb[j] != NULL && newtlb[j]->page_size != NULL && !strcmp(newtlb[j]->page_size, pagesize)) { + WARN("hugetlb-limit setting of %s is repeated, former setting %llu will be replaced with %llu", + pagesize, newtlb[j]->limit, hugetlb->limit); + newtlb[j]->limit = hugetlb->limit; + repeated = true; + goto out; + } + } + +out: + return repeated; +} + +static void free_hugetlbs_array(oci_runtime_config_linux_resources_hugepage_limits_element **hugetlb, + size_t hugetlb_len) +{ + size_t i; + + if (hugetlb == NULL) { + return; + } + + for (i = 0; i < hugetlb_len; i++) { + if (hugetlb[i] != NULL) { + free_oci_runtime_config_linux_resources_hugepage_limits_element(hugetlb[i]); + hugetlb[i] = NULL; + } + } + free(hugetlb); +} + +/* verify resources hugetlbs */ +static int verify_resources_hugetlbs(const sysinfo_t *sysinfo, + oci_runtime_config_linux_resources_hugepage_limits_element ***hugetlb, + size_t *hugetlb_len) +{ + int ret = 0; + oci_runtime_config_linux_resources_hugepage_limits_element **newhugetlb = NULL; + size_t newlen = 0; + size_t i; + + if (!sysinfo->hugetlbinfo.hugetlblimit) { + ERROR("Your kernel does not support hugetlb limit. --hugetlb-limit discarded."); + lcrd_set_error_message( + "Your kernel does not support hugetlb limit. --hugetlb-limit discarded."); + ret = -1; + goto out; + } + + for (i = 0; i < *hugetlb_len; i++) { + char *pagesize = NULL; + size_t newsize, oldsize; + oci_runtime_config_linux_resources_hugepage_limits_element **tmphugetlb; + + pagesize = validate_hugetlb((*hugetlb)[i]->page_size, (*hugetlb)[i]->limit); + if (pagesize == NULL) { + ret = -1; + goto out; + } + + if (check_hugetlbs_repeated(newlen, pagesize, (*hugetlb)[i], newhugetlb)) { + free(pagesize); + continue; + } + + // append new hugetlb + if (newlen > SIZE_MAX / sizeof(oci_runtime_config_linux_resources_hugepage_limits_element *) - 1) { + free(pagesize); + ERROR("Too many new hugetlb to append!"); + ret = -1; + goto out; + } + newsize = sizeof(oci_runtime_config_linux_resources_hugepage_limits_element *) * (newlen + 1); + oldsize = newsize - sizeof(oci_runtime_config_linux_resources_hugepage_limits_element *); + ret = mem_realloc((void **)&tmphugetlb, newsize, newhugetlb, oldsize); + if (ret < 0) { + free(pagesize); + ERROR("Out of memory"); + ret = -1; + goto out; + } + newhugetlb = tmphugetlb; + newhugetlb[newlen] = util_common_calloc_s(sizeof(oci_runtime_config_linux_resources_hugepage_limits_element)); + if (newhugetlb[newlen] == NULL) { + free(pagesize); + ERROR("Out of memory"); + ret = -1; + goto out; + } + newhugetlb[newlen]->limit = (*hugetlb)[i]->limit; + newhugetlb[newlen]->page_size = pagesize; + newlen++; + } +out: + if (ret != 0 && newhugetlb != NULL) { + free_hugetlbs_array(newhugetlb, newlen); + } else if (ret == 0) { + free_hugetlbs_array(*hugetlb, *hugetlb_len); + *hugetlb = newhugetlb; + *hugetlb_len = newlen; + } + return ret; +} + +/* adapt memory swap */ +static int adapt_memory_swap(const sysinfo_t *sysinfo, const int64_t *limit, int64_t *swap) +{ + if (*limit > 0 && *swap == 0 && sysinfo->cgmeminfo.swap) { + if (*limit > (INT64_MAX / 2)) { + ERROR("Memory swap out of range!"); + lcrd_set_error_message("Memory swap out of range!"); + return -1; + } + *swap = (*limit) * 2; + } + return 0; +} + +/* adapt resources memory */ +static int adapt_resources_memory(const sysinfo_t *sysinfo, oci_runtime_config_linux_resources_memory *memory) +{ + return adapt_memory_swap(sysinfo, &(memory->limit), &(memory->swap)); +} + +/* verify linux resources */ +static int verify_linux_resources(const sysinfo_t *sysinfo, oci_runtime_config_linux_resources *resources) +{ + int ret = 0; + + // memory + if (resources->memory != NULL) { + ret = verify_resources_memory(sysinfo, resources->memory); + if (ret != 0) { + goto out; + } + } + // pids + if (resources->pids != NULL) { + ret = verify_resources_pids(sysinfo, resources->pids); + if (ret != 0) { + goto out; + } + } + // cpu + if (resources->cpu != NULL) { + ret = verify_resources_cpu(sysinfo, resources->cpu); + if (ret != 0) { + goto out; + } + } + // hugetlb + if (resources->hugepage_limits_len && resources->hugepage_limits != NULL) { + ret = verify_resources_hugetlbs(sysinfo, &(resources->hugepage_limits), &(resources->hugepage_limits_len)); + if (ret != 0) { + goto out; + } + } + // blkio + if (resources->block_io != NULL) { + ret = verify_resources_blkio(sysinfo, resources->block_io); + if (ret != 0) { + goto out; + } + } +out: + return ret; +} + +/* adapt linux resources */ +static int adapt_linux_resources(const sysinfo_t *sysinfo, oci_runtime_config_linux_resources *resources) +{ + int ret = 0; + + // memory + if (resources->memory != NULL) { + ret = adapt_resources_memory(sysinfo, resources->memory); + if (ret != 0) { + goto out; + } + } +out: + return ret; +} + +static bool verify_oci_linux_sysctl(const oci_runtime_config_linux *l) +{ + size_t i = 0; + + if (l->sysctl == NULL) { + return true; + } + for (i = 0; i < l->sysctl->len; i++) { + if (strcmp("kernel.pid_max", l->sysctl->keys[i]) == 0) { + if (!pid_max_kernel_namespaced()) { + lcrd_set_error_message("Sysctl '%s' is not kernel namespaced, it cannot be changed", + l->sysctl->keys[i]); + return false; + } else { + return true; + } + } + if (!check_sysctl_valid(l->sysctl->keys[i])) { + lcrd_set_error_message("Sysctl %s=%s is not whitelist", + l->sysctl->keys[i], + l->sysctl->values[i]); + return false; + } + } + return true; +} + +/* verify oci linux */ +static int verify_oci_linux(const sysinfo_t *sysinfo, const oci_runtime_config_linux *l) +{ + int ret = 0; + + // Resources + if (l->resources != NULL) { + ret = verify_linux_resources(sysinfo, l->resources); + if (ret != 0) { + goto out; + } + } + if (!verify_oci_linux_sysctl(l)) { + ret = -1; + goto out; + } +out: + return ret; +} + +static int verify_oci_hook_prestart(const oci_runtime_spec_hooks *h) +{ + size_t i; + for (i = 0; i < h->prestart_len; i++) { + int ret = 0; + ret = verify_hook_conf(h->prestart[i]); + if (ret != 0) { + return ret; + } + } + return 0; +} + +static int verify_oci_hook_poststart(const oci_runtime_spec_hooks *h) +{ + size_t i; + for (i = 0; i < h->poststart_len; i++) { + int ret = 0; + ret = verify_hook_conf(h->poststart[i]); + if (ret != 0) { + return ret; + } + } + return 0; +} + +static int verify_oci_hook_poststop(const oci_runtime_spec_hooks *h) +{ + size_t i; + for (i = 0; i < h->poststop_len; i++) { + int ret = 0; + ret = verify_hook_conf(h->poststop[i]); + if (ret != 0) { + return ret; + } + } + return 0; +} + +/* verify oci hook */ +int verify_oci_hook(const oci_runtime_spec_hooks *h) +{ + int ret = 0; + + // Prestart + ret = verify_oci_hook_prestart(h); + if (ret != 0) { + goto out; + } + + // Poststart + ret = verify_oci_hook_poststart(h); + if (ret != 0) { + goto out; + } + + // Poststop + ret = verify_oci_hook_poststop(h); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +/* adapt oci linux */ +static int adapt_oci_linux(const sysinfo_t *sysinfo, oci_runtime_config_linux *l) +{ + int ret = 0; + + // Resources + if (l->resources != NULL) { + ret = adapt_linux_resources(sysinfo, l->resources); + if (ret != 0) { + goto out; + } + } +out: + return ret; +} + +/* get source mount */ +static int get_source_mount(const char *src, char **srcpath, char **optional) +{ + mountinfo_t **minfos = NULL; + mountinfo_t *info = NULL; + int ret = 0; + char real_path[PATH_MAX + 1] = {0}; + char *dirc = NULL; + char *dname = NULL; + + if (realpath(src, real_path) == NULL) { + ERROR("Failed to get real path for %s : %s", src, strerror(errno)); + return -1; + } + + minfos = getmountsinfo(); + if (minfos == NULL) { + ERROR("Failed to get mounts info"); + ret = -1; + goto out; + } + + info = find_mount_info(minfos, real_path); + if (info != NULL) { + *srcpath = util_strdup_s(real_path); + *optional = info->optional ? util_strdup_s(info->optional) : NULL; + goto out; + } + + dirc = util_strdup_s(real_path); + dname = dirc; + while (strcmp(dirc, "/")) { + dname = dirname(dname); + info = find_mount_info(minfos, dname); + if (info != NULL) { + *srcpath = util_strdup_s(dname); + *optional = info->optional ? util_strdup_s(info->optional) : NULL; + goto out; + } + } + ERROR("Could not find source mount of %s", src); + ret = -1; +out: + free(dirc); + free_mounts_info(minfos); + return ret; +} + +static inline bool is_optional_shared(const char *optional) +{ + return optional != NULL && strncmp(optional, "shared:", strlen("shared:")) == 0; +} + +/* ensure shared */ +static int ensure_shared(const char *src) +{ + int ret = 0; + char *srcpath = NULL; + char *optional = NULL; + + ret = get_source_mount(src, &srcpath, &optional); + if (ret != 0) { + goto out; + } + if (is_optional_shared(optional)) { + goto out; + } + ERROR("Path %s is mounted on %s but it is not a shared mount", src, srcpath); + ret = -1; +out: + free(srcpath); + free(optional); + return ret; +} + +static inline bool is_optional_slave(const char *optional) +{ + return optional != NULL && strncmp(optional, "master:", strlen("master:")) == 0; +} + +/* ensure shared or slave */ +static int ensure_shared_or_slave(const char *src) +{ + int ret = 0; + char *srcpath = NULL; + char *optional = NULL; + + ret = get_source_mount(src, &srcpath, &optional); + if (ret != 0) { + goto out; + } + if (is_optional_shared(optional) || is_optional_slave(optional)) { + goto out; + } + ERROR("Path %s is mounted on %s but it is not a shared or slave mount", src, srcpath); + ret = -1; +out: + + free(srcpath); + free(optional); + return ret; +} + +static inline bool is_propagation_shared(const char *propagation) +{ + return strcmp(propagation, "shared") == 0 || strcmp(propagation, "rshared") == 0; +} + +static void set_mount_propagation_shared(const oci_runtime_spec *container) +{ + if (container->linux->rootfs_propagation == NULL) { + container->linux->rootfs_propagation = util_strdup_s("shared"); + return; + } + + if (!is_propagation_shared(container->linux->rootfs_propagation)) { + free(container->linux->rootfs_propagation); + container->linux->rootfs_propagation = util_strdup_s("shared"); + } +} + +static inline bool is_propagation_slave(const char *propagation) +{ + return strcmp(propagation, "slave") == 0 || strcmp(propagation, "rslave") == 0; +} + +static void set_mount_propagation_slave(const oci_runtime_spec *container) +{ + if (container->linux->rootfs_propagation == NULL) { + container->linux->rootfs_propagation = util_strdup_s("rslave"); + return; + } + + if (!is_propagation_shared(container->linux->rootfs_propagation) && + !is_propagation_slave(container->linux->rootfs_propagation)) { + free(container->linux->rootfs_propagation); + container->linux->rootfs_propagation = util_strdup_s("rslave"); + } +} + +static int make_mount_propagation(const oci_runtime_spec *container, const char *source, const char *option) +{ + int ret; + + if (is_propagation_shared(option)) { + ret = ensure_shared(source); + if (ret != 0) { + return ret; + } + set_mount_propagation_shared(container); + } else if (is_propagation_slave(option)) { + ret = ensure_shared_or_slave(source); + if (ret != 0) { + return ret; + } + set_mount_propagation_slave(container); + } + + return 0; +} + +/* set mounts */ +static int set_mounts(const oci_runtime_spec *container) +{ + int ret = 0; + size_t i, k; + defs_mount **m = NULL; + + if (container == NULL) { + return -1; + } + + m = container->mounts; + for (i = 0; i < container->mounts_len; i++) { + for (k = 0; k < m[i]->options_len; k++) { + ret = make_mount_propagation(container, m[i]->source, m[i]->options[k]); + if (ret != 0) { + goto out; + } + } + } + +out: + return ret; +} + +/* verify custom mount */ +static int verify_custom_mount(defs_mount **mounts, size_t len) +{ + int ret = 0; + size_t i = 0; + defs_mount *iter = NULL; + + for (; i < len; ++i) { + iter = *(mounts + i); + if (iter == NULL || strcmp(iter->type, "bind")) { + continue; + } + + if (!util_file_exists(iter->source) && + util_mkdir_p(iter->source, CONFIG_DIRECTORY_MODE)) { + ERROR("Failed to create directory '%s': %s", iter->source, strerror(errno)); + lcrd_try_set_error_message("Failed to create directory '%s': %s", iter->source, strerror(errno)); + ret = -1; + goto out; + } + } + +out: + return ret; +} + +static int verify_container_linux(const oci_runtime_spec *container, const sysinfo_t *sysinfo) +{ + int ret = 0; + + /* verify and adapt container settings */ + if (container->linux != NULL) { + ret = verify_oci_linux(sysinfo, container->linux); + if (ret != 0) { + goto out; + } + ret = adapt_oci_linux(sysinfo, container->linux); + if (ret != 0) { + goto out; + } + } + +out: + return ret; +} + +static int verify_container_mounts(const oci_runtime_spec *container) +{ + int ret = 0; + + /* verify custom mount info, ensure source path exist */ + if (container->mounts != NULL && container->mounts_len > 0) { + ret = verify_custom_mount(container->mounts, container->mounts_len); + if (ret != 0) { + goto out; + } + ret = set_mounts(container); + if (ret != 0) { + goto out; + } + } + +out: + return ret; +} + +/* verify container settings */ +int verify_container_settings(const oci_runtime_spec *container) +{ + int ret = 0; + sysinfo_t *sysinfo = NULL; + + sysinfo = get_sys_info(true); + if (sysinfo == NULL) { + ERROR("Can not get system info"); + ret = -1; + goto out; + } + + if (!util_valid_host_name(container->hostname)) { + ERROR("Invalid container hostname %s", container->hostname); + lcrd_set_error_message("Invalid container hostname (%s), only %s and less than 64 bytes are allowed.", + container->hostname, HOST_NAME_REGEXP); + ret = -1; + goto out; + } + + /* verify and adapt container settings */ + ret = verify_container_linux(container, sysinfo); + if (ret != 0) { + goto out; + } + + ret = verify_container_mounts(container); + if (ret != 0) { + goto out; + } + + /* verify hook settings */ + if (container->hooks != NULL && verify_oci_hook(container->hooks)) { + ERROR("Verify hook file failed"); + ret = -1; + goto out; + } + +out: + free_sysinfo(sysinfo); + return ret; +} + +static void free_hugetlb_array(host_config_hugetlbs_element **hugetlb, size_t len) +{ + size_t i; + + if (hugetlb == NULL) { + return; + } + + for (i = 0; i < len; i++) { + free_host_config_hugetlbs_element(hugetlb[i]); + hugetlb[i] = NULL; + } + free(hugetlb); +} + +static int append_hugetlb_array(host_config_hugetlbs_element ***hugetlb, size_t len) +{ + size_t newsize; + size_t oldsize; + host_config_hugetlbs_element **tmphugetlb; + int ret; + + if (len > SIZE_MAX / sizeof(host_config_hugetlbs_element *) - 1) { + return -1; + } + + newsize = sizeof(host_config_hugetlbs_element *) * (len + 1); + oldsize = newsize - sizeof(host_config_hugetlbs_element *); + ret = mem_realloc((void **)&tmphugetlb, newsize, *hugetlb, oldsize); + if (ret < 0) { + return -1; + } + + *hugetlb = tmphugetlb; + (*hugetlb)[len] = util_common_calloc_s(sizeof(host_config_hugetlbs_element)); + if ((*hugetlb)[len] == NULL) { + return -1; + } + + return 0; +} + +static int add_hugetbl_element(host_config_hugetlbs_element ***hugetlb, size_t *len, + const host_config_hugetlbs_element *element) +{ + char *pagesize = NULL; + size_t j; + int ret = 0; + + pagesize = validate_hugetlb(element->page_size, element->limit); + if (pagesize == NULL) { + return -1; + } + + for (j = 0; j < *len; j++) { + if (strcmp((*hugetlb)[j]->page_size, pagesize) == 0) { + WARN("Hostconfig: hugetlb-limit setting of %s is repeated, " + "former setting % llu will be replaced with % llu", + pagesize, (*hugetlb)[j]->limit, element->limit); + (*hugetlb)[j]->limit = element->limit; + goto out; + } + } + + // append new hugetlb + ret = append_hugetlb_array(hugetlb, *len); + if (ret < 0) { + ERROR("Out of memory"); + goto out; + } + (*hugetlb)[*len]->page_size = pagesize; + (*hugetlb)[*len]->limit = element->limit; + (*len)++; + return 0; + +out: + free(pagesize); + return ret; +} + +/* verify host config hugetlbs */ +static int verify_host_config_hugetlbs(const sysinfo_t *sysinfo, host_config_hugetlbs_element ***hugetlb, + size_t *hugetlb_len) +{ + int ret; + host_config_hugetlbs_element **newhugetlb = NULL; + size_t newlen = 0; + size_t i = 0; + + if (*hugetlb == NULL || *hugetlb_len == 0) { + return 0; + } + + if (!sysinfo->hugetlbinfo.hugetlblimit) { + ERROR("Your kernel does not support hugetlb limit. --hugetlb-limit discarded."); + lcrd_set_error_message("Your kernel does not support hugetlb limit. --hugetlb-limit discarded."); + ret = -1; + goto out; + } + + for (i = 0; i < *hugetlb_len; i++) { + ret = add_hugetbl_element(&newhugetlb, &newlen, (*hugetlb)[i]); + if (ret != 0) { + goto out; + } + } + + free_hugetlb_array(*hugetlb, *hugetlb_len); + *hugetlb = newhugetlb; + *hugetlb_len = newlen; + return 0; + +out: + free_hugetlb_array(newhugetlb, newlen); + return ret; +} + +static int host_config_settings_memory(const sysinfo_t *sysinfo, const host_config *hostconfig, bool update) +{ + int ret = 0; + + ret = verify_mem_limit_swap(sysinfo, hostconfig->memory, hostconfig->memory_swap, update); + if (ret != 0) { + goto out; + } + + ret = verify_memory_reservation(sysinfo, hostconfig->memory, hostconfig->memory_reservation); + if (ret != 0) { + goto out; + } + + ret = verify_memory_kernel(sysinfo, hostconfig->kernel_memory); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +static int host_config_settings_cpu(const sysinfo_t *sysinfo, const host_config *hostconfig) +{ + int ret = 0; + + ret = verify_cpu_realtime(sysinfo, hostconfig->cpu_realtime_period, hostconfig->cpu_realtime_runtime); + if (ret != 0) { + goto out; + } + + ret = verify_cpu_shares(sysinfo, hostconfig->cpu_shares); + if (ret != 0) { + goto out; + } + + ret = verify_cpu_cfs_scheduler(sysinfo, hostconfig->cpu_period, hostconfig->cpu_quota); + if (ret != 0) { + goto out; + } + + // cpuset + ret = verify_resources_cpuset(sysinfo, hostconfig->cpuset_cpus, hostconfig->cpuset_mems); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +static int host_config_settings_blkio(const sysinfo_t *sysinfo, const host_config *hostconfig) +{ + int ret = 0; + + ret = verify_hostconfig_blkio_weight(sysinfo, hostconfig->blkio_weight); + if (ret != 0) { + goto out; + } + + ret = verify_blkio_device(sysinfo, hostconfig->blkio_weight_device_len); + if (ret != 0) { + goto out; + } + +out: + return ret; +} + +static inline bool is_restart_policy_always(const char *policy) +{ + return strcmp(policy, "always") == 0; +} + +static inline bool is_restart_policy_on_reboot(const char *policy) +{ + return strcmp(policy, "on-reboot") == 0; +} + +static inline bool is_restart_policy_no(const char *policy) +{ + return strcmp(policy, "no") == 0; +} + +static inline bool is_restart_policy_on_failure(const char *policy) +{ + return strcmp(policy, "on-failure") == 0; +} + +static int verify_restart_policy_name(const host_config_restart_policy *rp, const host_config *hostconfig) +{ + if (is_restart_policy_always(rp->name) || is_restart_policy_no(rp->name) || is_restart_policy_on_reboot(rp->name)) { + if (rp->maximum_retry_count != 0) { + ERROR("Maximum retry count cannot be used with restart policy '%s'", rp->name); + lcrd_set_error_message("Maximum retry count cannot be used with restart policy '%s'", rp->name); + return -1; + } + } else if (is_restart_policy_on_failure(rp->name)) { + if (rp->maximum_retry_count < 0) { + ERROR("Maximum retry count cannot be negative"); + lcrd_set_error_message("Maximum retry count cannot be negative"); + return -1; + } + } else { + ERROR("Invalid restart policy '%s'", rp->name); + lcrd_set_error_message("Invalid restart policy '%s'", rp->name); + return -1; + } + + if (hostconfig->auto_remove && !is_restart_policy_no(rp->name)) { + ERROR("Can't create 'AutoRemove' container with restart policy"); + lcrd_set_error_message("Can't create 'AutoRemove' container with restart policy"); + return -1; + } + + return 0; +} + +static int host_config_settings_restart_policy(const host_config *hostconfig) +{ + host_config_restart_policy *rp = NULL; + + if (hostconfig == NULL || hostconfig->restart_policy == NULL) { + return 0; + } + + rp = hostconfig->restart_policy; + if (rp->name == NULL || rp->name[0] == '\0') { + if (rp->maximum_retry_count != 0) { + ERROR("Maximum retry count cannot be used with empty restart policy"); + lcrd_set_error_message("Maximum retry count cannot be used with empty restart policy"); + return -1; + } + return 0; + } + + return verify_restart_policy_name(rp, hostconfig); +} + +static int host_config_settings_with_sysinfo(host_config *hostconfig, bool update) +{ + int ret = 0; + sysinfo_t *sysinfo = NULL; + + sysinfo = get_sys_info(true); + if (sysinfo == NULL) { + ERROR("Can not get system info"); + return -1; + } + + ret = verify_host_config_hugetlbs(sysinfo, &(hostconfig->hugetlbs), &(hostconfig->hugetlbs_len)); + if (ret != 0) { + goto out; + } + + // memory + ret = host_config_settings_memory(sysinfo, hostconfig, update); + if (ret != 0) { + goto out; + } + + ret = verify_pids_limit(sysinfo, hostconfig->pids_limit); + if (ret != 0) { + goto out; + } + + ret = verify_files_limit(sysinfo, hostconfig->files_limit); + if (ret != 0) { + goto out; + } + + // cpu & cpuset + ret = host_config_settings_cpu(sysinfo, hostconfig); + if (ret != 0) { + goto out; + } + + // blkio + ret = host_config_settings_blkio(sysinfo, hostconfig); + if (ret != 0) { + goto out; + } + +out: + free_sysinfo(sysinfo); + return ret; +} + +/* verify host config settings */ +int verify_host_config_settings(host_config *hostconfig, bool update) +{ + int ret = 0; + + if (hostconfig == NULL) { + return 0; + } + + // restart policy + ret = host_config_settings_restart_policy(hostconfig); + if (ret != 0) { + goto out; + } + + ret = host_config_settings_with_sysinfo(hostconfig, update); + if (ret != 0) { + goto out; + } + + // oom score adj + ret = verify_oom_score_adj(hostconfig->oom_score_adj); + if (ret != 0) { + goto out; + } + +#ifdef ENABLE_OCI_IMAGE + // storage options + ret = verify_storage_opts(hostconfig); + if (ret != 0) { + goto out; + } +#endif + +out: + return ret; +} + +/* verify container settings start */ +int verify_container_settings_start(const char *rootpath, const char *id) +{ + int ret = 0; + oci_runtime_spec *container = read_oci_config(rootpath, id); + if (container == NULL) { + ERROR("Failed to read oci config"); + ret = -1; + goto out; + } + + /* verify custom mount info, ensure source path exist */ + if (container->mounts != NULL && container->mounts_len > 0) { + ret = verify_custom_mount(container->mounts, container->mounts_len); + } +out: + free_oci_runtime_spec(container); + return ret; +} + +static inline bool is_less_than_one_second(int64_t timeout) +{ + return timeout != 0 && timeout < Time_Second; +} + +int verify_health_check_parameter(const container_custom_config *custom_spec) +{ + int ret = 0; + + if (custom_spec == NULL || custom_spec->health_check == NULL) { + return ret; + } + + if (is_less_than_one_second(custom_spec->health_check->interval)) { + ERROR("Interval in Healthcheck cannot be less than one second"); + lcrd_set_error_message("Interval in Healthcheck cannot be less than one second"); + ret = -1; + goto out; + } + if (is_less_than_one_second(custom_spec->health_check->timeout)) { + ERROR("Timeout in Healthcheck cannot be less than one second"); + lcrd_set_error_message("Timeout in Healthcheck cannot be less than one second"); + ret = -1; + goto out; + } + if (is_less_than_one_second(custom_spec->health_check->start_period)) { + ERROR("StartPeriod in Healthcheck cannot be less than one second"); + lcrd_set_error_message("StartPeriod in Healthcheck cannot be less than one second"); + ret = -1; + goto out; + } + if (custom_spec->health_check->retries < 0) { + ERROR("--health-retries cannot be negative"); + lcrd_set_error_message("--health-retries cannot be negative"); + ret = -1; + goto out; + } + +out: + return ret; +} + diff --git a/src/services/execution/spec/verify.h b/src/services/execution/spec/verify.h new file mode 100644 index 0000000..040f5d3 --- /dev/null +++ b/src/services/execution/spec/verify.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container verify definition + ******************************************************************************/ +#ifndef __VERIFY_H +#define __VERIFY_H + +#include "oci_runtime_spec.h" +#include "host_config.h" + +int verify_container_settings(const oci_runtime_spec *container); + +int verify_oci_hook(const oci_runtime_spec_hooks *h); + +int verify_container_settings_start(const char *rootpath, const char *id); + +int verify_host_config_settings(host_config *hostconfig, bool update); + +int verify_health_check_parameter(const container_custom_config *custom_spec); + + +#endif /* __VERIFY_H */ diff --git a/src/services/graphdriver/CMakeLists.txt b/src/services/graphdriver/CMakeLists.txt new file mode 100644 index 0000000..9aff0a0 --- /dev/null +++ b/src/services/graphdriver/CMakeLists.txt @@ -0,0 +1,14 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_graphdriver_srcs) +add_subdirectory(overlay2) + +set(GRAPHDRIVER_SRCS + ${local_graphdriver_srcs} + ${OVERLAY2_SRCS} + PARENT_SCOPE + ) +set(GRAPHDRIVER_INCS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/overlay2 + PARENT_SCOPE + ) diff --git a/src/services/graphdriver/driver.c b/src/services/graphdriver/driver.c new file mode 100644 index 0000000..99525eb --- /dev/null +++ b/src/services/graphdriver/driver.c @@ -0,0 +1,254 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide image functions + ******************************************************************************/ +#include "driver.h" + +#include +#include +#include +#include + +#include "driver_overlay2.h" +#include "utils.h" +#include "liblcrd.h" +#include "log.h" +#include "isula_imtool_interface.h" +#include "lcrd_config.h" + +/* overlay2 */ + +#define DRIVER_OVERLAY2_NAME "overlay2" +static const struct graphdriver_ops g_overlay2_ops = { + .init = overlay2_init, + .parse_options = overlay2_parse_options, + .is_quota_options = overlay2_is_quota_options, +}; + +static struct graphdriver g_drivers[] = { + {.name = DRIVER_OVERLAY2_NAME, .ops = &g_overlay2_ops} +}; + +static const size_t g_numdrivers = sizeof(g_drivers) / sizeof(struct graphdriver); + +struct graphdriver *graphdriver_init(const char *name, char **storage_opts, size_t storage_opts_len) +{ + size_t i = 0; + + if (name == NULL || storage_opts == NULL) { + return NULL; + } + + for (i = 0; i < g_numdrivers; i++) { + if (strcmp(name, g_drivers[i].name) == 0) { + if (g_drivers[i].ops->init(&g_drivers[i])) { + return NULL; + } + if (g_drivers[i].ops->parse_options(&g_drivers[i], (const char **)storage_opts, storage_opts_len)) { + return NULL; + } + return &g_drivers[i]; + } + } + + lcrd_set_error_message("Invalid storage driver name: '%s'", name); + return NULL; +} + +struct graphdriver *graphdriver_get(const char *name) +{ + size_t i = 0; + + if (name == NULL) { + return NULL; + } + + for (i = 0; i < g_numdrivers; i++) { + if (strcmp(name, g_drivers[i].name) == 0) { + return &g_drivers[i]; + } + } + + lcrd_set_error_message("Invalid storage driver name: '%s'", name); + return NULL; +} + +// format: [status xx: val] +static int get_graphdriver_status_line_value(const char *line, char **start, char **end) +{ + char *pstart = NULL; + char *pend = NULL; + + pstart = strchr(line, ':'); + if (pstart == NULL) { + ERROR("Invalid output: %s", line); + return -1; + } + pstart++; + if (*pstart != ' ') { + ERROR("Invalid output: %s", line); + return -1; + } + pstart++; + + pend = strchr(pstart, '\n'); + if (pend == NULL) { + ERROR("Invalid output: %s", pstart); + return -1; + } + *pend++ = '\0'; + + *start = pstart; + *end = pend; + return 0; +} + +struct graphdriver_status *graphdriver_get_status(void) +{ + struct graphdriver_status *status = NULL; + bool command_ret = false; + char *stdout_buffer = NULL; + char *stderr_buffer = NULL; + char *pstart = NULL; + char *pend = NULL; + int ret = -1; + int nret = -1; + + command_ret = util_exec_cmd(execute_storage_status, NULL, NULL, &stdout_buffer, &stderr_buffer); + if (!command_ret) { + if (stderr_buffer != NULL) { + ERROR("Failed to get storage status with error: %s", stderr_buffer); + lcrd_set_error_message("Failed to get storage status with error: %s", stderr_buffer); + } else { + ERROR("Failed to exec storage status command"); + lcrd_set_error_message("Failed to storage status command"); + } + goto free_out; + } + + if (stdout_buffer == NULL) { + ERROR("Failed to get storage status because can not get stdoutput"); + lcrd_set_error_message("Failed to get storage status because can not get stdoutput"); + goto free_out; + } + + status = util_common_calloc_s(sizeof(struct graphdriver_status)); + if (status == NULL) { + ERROR("Out of memory"); + goto free_out; + } + + // Backing Filesystem: extfs + if (get_graphdriver_status_line_value(stdout_buffer, &pstart, &pend) != 0) { + goto free_out; + } + status->backing_fs = util_strdup_s(pstart); + + // Supports d_type: true + if (get_graphdriver_status_line_value(pend, &pstart, &pend) != 0) { + goto free_out; + } + nret = util_str_to_bool(pstart, &status->supports_d_type); + if (nret < 0) { + ERROR("Invalid output: %s", pstart); + goto free_out; + } + + // Native Overlay Diff: true + if (get_graphdriver_status_line_value(pend, &pstart, &pend) != 0) { + goto free_out; + } + nret = util_str_to_bool(pstart, &status->native_overlay_diff); + if (nret < 0) { + ERROR("Invalid output: %s", pstart); + goto free_out; + } + + ret = 0; +free_out: + free(stderr_buffer); + free(stdout_buffer); + if (ret != 0) { + free_graphdriver_status(status); + return NULL; + } + return status; +} + +int update_graphdriver_status(struct graphdriver **driver) +{ + struct graphdriver_status *status = NULL; + int ret = 0; + + if (driver == NULL) { + return -1; + } + + status = graphdriver_get_status(); + if (status == NULL) { + ERROR("Can not get driver status"); + return -1; + } + if (*driver == NULL) { + *driver = util_common_calloc_s(sizeof(struct graphdriver)); + if (*driver == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + } + free((*driver)->backing_fs); + (*driver)->backing_fs = util_strdup_s(status->backing_fs); +out: + free_graphdriver_status(status); + return ret; +} + +void graphdriver_umount_mntpoint(void) +{ + char *root = NULL; + char *driver_name = NULL; + char mp[PATH_MAX] = { 0 }; + int nret = 0; + + root = conf_get_graph_rootpath(); + driver_name = conf_get_lcrd_storage_driver(); + if (root == NULL || driver_name == NULL) { + WARN("No root or driver name specified"); + goto cleanup; + } + if (strcmp(driver_name, "overlay2") == 0) { + driver_name[strlen(driver_name) - 1] = '\0'; + } + nret = sprintf_s(mp, sizeof(mp), "%s/%s", root, driver_name); + if (nret < 0) { + WARN("Failed to print string"); + goto cleanup; + } + if (umount(mp) < 0 && errno != EINVAL) { + WARN("Can not umount: %s: %s", mp, strerror(errno)); + } +cleanup: + free(root); + free(driver_name); +} + +void free_graphdriver_status(struct graphdriver_status *status) +{ + if (status == NULL) { + return; + } + free(status->backing_fs); + free(status); +} + diff --git a/src/services/graphdriver/driver.h b/src/services/graphdriver/driver.h new file mode 100644 index 0000000..94a355a --- /dev/null +++ b/src/services/graphdriver/driver.h @@ -0,0 +1,64 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2019-04-02 + * Description: provide graphdriver function definition + ******************************************************************************/ +#ifndef __GRAPHDRIVER_H +#define __GRAPHDRIVER_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct graphdriver; + +struct graphdriver_ops { + int (*init)(struct graphdriver *driver); + + int (*parse_options)(struct graphdriver *driver, const char **options, size_t len); + + bool (*is_quota_options)(struct graphdriver *driver, const char *option); +}; + +struct graphdriver { + const struct graphdriver_ops *ops; + const char *name; + char *backing_fs; +}; + +struct graphdriver_status { + char *backing_fs; + bool supports_d_type; + bool native_overlay_diff; +}; + +struct graphdriver *graphdriver_init(const char *name, char **storage_opts, size_t storage_opts_len); + +struct graphdriver *graphdriver_get(const char *name); + +struct graphdriver_status *graphdriver_get_status(void); + +int update_graphdriver_status(struct graphdriver **driver); + +void graphdriver_umount_mntpoint(void); + +void free_graphdriver_status(struct graphdriver_status *status); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/graphdriver/overlay2/CMakeLists.txt b/src/services/graphdriver/overlay2/CMakeLists.txt new file mode 100644 index 0000000..ceed16b --- /dev/null +++ b/src/services/graphdriver/overlay2/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_overlay2_srcs) + +set(OVERLAY2_SRCS + ${local_overlay2_srcs} + PARENT_SCOPE + ) diff --git a/src/services/graphdriver/overlay2/driver_overlay2.c b/src/services/graphdriver/overlay2/driver_overlay2.c new file mode 100644 index 0000000..59d1d62 --- /dev/null +++ b/src/services/graphdriver/overlay2/driver_overlay2.c @@ -0,0 +1,81 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2019-04-02 + * Description: provide overlay2 function definition + ******************************************************************************/ +#include "driver_overlay2.h" +#include + +#include "liblcrd.h" +#include "utils.h" + +#define QUOTA_SIZE_OPTION "overlay2.size" +#define QUOTA_BASESIZE_OPTIONS "overlay2.basesize" + +int overlay2_init(struct graphdriver *driver) +{ + return 0; +} + +bool overlay2_is_quota_options(struct graphdriver *driver, const char *option) +{ + return strncmp(option, QUOTA_SIZE_OPTION, strlen(QUOTA_SIZE_OPTION)) == 0 || + strncmp(option, QUOTA_BASESIZE_OPTIONS, strlen(QUOTA_BASESIZE_OPTIONS)) == 0; +} + +int overlay2_parse_options(struct graphdriver *driver, const char **options, size_t options_len) +{ + size_t i = 0; + + for (i = 0; options != NULL && i < options_len; i++) { + char *dup = NULL; + char *p = NULL; + char *val = NULL; + int ret = 0; + + dup = util_strdup_s(options[i]); + if (dup == NULL) { + lcrd_set_error_message("Out of memory"); + return -1; + } + p = strchr(dup, '='); + if (!p) { + lcrd_set_error_message("Unable to parse key/value option: '%s'", dup); + free(dup); + return -1; + } + *p = '\0'; + val = p + 1; + if (strcasecmp(dup, QUOTA_SIZE_OPTION) == 0 || strcasecmp(dup, QUOTA_BASESIZE_OPTIONS) == 0) { + int64_t converted = 0; + ret = util_parse_byte_size_string(val, &converted); + if (ret != 0) { + lcrd_set_error_message("Invalid size: '%s': %s", val, strerror(-ret)); + } + } else if (strcasecmp(dup, "overlay2.override_kernel_check") == 0) { + bool converted_bool = 0; + ret = util_str_to_bool(val, &converted_bool); + if (ret != 0) { + lcrd_set_error_message("Invalid bool: '%s': %s", val, strerror(-ret)); + } + } else { + lcrd_set_error_message("Overlay2: unknown option: '%s'", dup); + ret = -1; + } + free(dup); + if (ret != 0) { + return ret; + } + } + + return 0; +} diff --git a/src/services/graphdriver/overlay2/driver_overlay2.h b/src/services/graphdriver/overlay2/driver_overlay2.h new file mode 100644 index 0000000..2986eb4 --- /dev/null +++ b/src/services/graphdriver/overlay2/driver_overlay2.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2019-04-02 + * Description: provide overlay2 function definition + ******************************************************************************/ +#ifndef __GRAPHDRIVER_OVERLAY2_H +#define __GRAPHDRIVER_OVERLAY2_H + +#include "driver.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int overlay2_init(struct graphdriver *driver); + +int overlay2_parse_options(struct graphdriver *driver, const char **options, size_t options_len); + +bool overlay2_is_quota_options(struct graphdriver *driver, const char *option); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/services/image/CMakeLists.txt b/src/services/image/CMakeLists.txt new file mode 100644 index 0000000..e6e53cb --- /dev/null +++ b/src/services/image/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_image_srcs) + +set(IMAGE_SRCS + ${local_image_srcs} + PARENT_SCOPE + ) diff --git a/src/services/image/image_cb.c b/src/services/image/image_cb.c new file mode 100644 index 0000000..0ea4919 --- /dev/null +++ b/src/services/image/image_cb.c @@ -0,0 +1,727 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide image functions + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "image_cb.h" +#include "utils.h" +#include "error.h" +#include "liblcrd.h" +#include "log.h" +#include "image.h" +#include "securec.h" +#include "engine.h" +#include "lcrd_config.h" +#include "mediatype.h" + +static int docker_load_image(const char *file, const char *tag, const char *type) +{ + int ret = 0; + im_load_request *request = NULL; + im_load_response *response = NULL; + + if (file == NULL || type == NULL) { + ERROR("Invalid input arguments"); + ret = -1; + goto out; + } + + request = util_common_calloc_s(sizeof(im_load_request)); + if (request == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + if (tag != NULL) { + request->tag = util_strdup_s(tag); + } + request->file = util_strdup_s(file); + request->type = util_strdup_s(type); + + ret = im_load_image(request, &response); + if (ret != 0) { + ret = -1; + goto out; + } + +out: + free_im_load_request(request); + free_im_load_response(response); + return ret; +} + +/* image load cb*/ +static int image_load_cb(const image_load_image_request *request, + image_load_image_response **response) +{ + int ret = -1; + uint32_t cc = LCRD_SUCCESS; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + return EINVALIDARGS; + } + + DAEMON_CLEAR_ERRMSG(); + *response = util_common_calloc_s(sizeof(image_load_image_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto out; + } + + if (request->file == NULL || request->type == NULL) { + ERROR("input arguments error"); + cc = LCRD_ERR_INPUT; + goto out; + } + + EVENT("Image Event: {Object: %s, Type: Loading}", + request->file); + + ret = docker_load_image(request->file, request->tag, request->type); + if (ret != 0) { + ERROR("Failed to load docker image %s with tag %s and type %s", + request->file, request->tag, request->type); + cc = EINVALIDARGS; + goto out; + } + + EVENT("Image Event: {Object: %s, Type: Loaded}", + request->file); +out: + + if (*response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + } + + return (ret < 0) ? ECOMMON : ret; +} + +static int docker_login(const char *username, const char *password, const char *server, const char *type) +{ + int ret = 0; + im_login_request *request = NULL; + im_login_response *response = NULL; + + request = util_common_calloc_s(sizeof(im_login_request)); + if (request == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + request->username = util_strdup_s(username); + request->password = util_strdup_s(password); + request->server = util_strdup_s(server); + request->type = util_strdup_s(type); + + ret = im_login(request, &response); + if (ret != 0) { + ret = -1; + goto out; + } + +out: + free_im_login_request(request); + free_im_login_response(response); + return ret; +} + +/* login cb */ +static int login_cb(const image_login_request *request, + image_login_response **response) +{ + int ret = -1; + uint32_t cc = LCRD_SUCCESS; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + cc = LCRD_ERR_INPUT; + goto out; + } + + DAEMON_CLEAR_ERRMSG(); + *response = util_common_calloc_s(sizeof(image_login_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto out; + } + + if (request->username == NULL || request->password == NULL || + request->type == NULL || request->server == NULL) { + ERROR("input arguments error"); + cc = LCRD_ERR_INPUT; + goto out; + } + + EVENT("Image Event: {Object: %s, Type: Logining}", request->server); + + ret = docker_login(request->username, request->password, request->server, request->type); + if (ret != 0) { + ERROR("Failed to login %s", request->server); + cc = EINVALIDARGS; + goto out; + } + + EVENT("Image Event: {Object: %s, Type: Logined}", request->server); +out: + + if (response != NULL && *response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + } + + return (ret < 0) ? ECOMMON : ret; +} + +static int docker_logout(const char *server, const char *type) +{ + int ret = 0; + im_logout_request *request = NULL; + im_logout_response *response = NULL; + + request = util_common_calloc_s(sizeof(im_logout_request)); + if (request == NULL) { + ERROR("Memory out"); + ret = -1; + goto out; + } + request->server = util_strdup_s(server); + request->type = util_strdup_s(type); + + ret = im_logout(request, &response); + if (ret != 0) { + ret = -1; + goto out; + } + +out: + free_im_logout_request(request); + free_im_logout_response(response); + return ret; +} + +/* logout cb */ +static int logout_cb(const image_logout_request *request, + image_logout_response **response) +{ + int ret = -1; + uint32_t cc = LCRD_SUCCESS; + + if (request == NULL || response == NULL) { + ERROR("Invalid input arguments"); + cc = LCRD_ERR_INPUT; + goto out; + } + + DAEMON_CLEAR_ERRMSG(); + *response = util_common_calloc_s(sizeof(image_logout_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto out; + } + + if (request->type == NULL || request->server == NULL) { + ERROR("input arguments error"); + cc = LCRD_ERR_INPUT; + goto out; + } + + EVENT("Image Event: {Object: %s, Type: Logouting}", request->server); + + ret = docker_logout(request->server, request->type); + if (ret != 0) { + ERROR("Failed to logout %s", request->server); + cc = EINVALIDARGS; + goto out; + } + + EVENT("Image Event: {Object: %s, Type: Logouted}", request->server); +out: + + if (response != NULL && *response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + } + + return (ret < 0) ? ECOMMON : ret; +} + +/* delete image info*/ +static int delete_image_info(const char *image_ref, bool force) +{ + int ret = 0; + im_remove_request *im_request = NULL; + im_remove_response *im_response = NULL; + + if (image_ref == NULL) { + ERROR("invalid NULL param"); + return EINVALIDARGS; + } + + im_request = util_common_calloc_s(sizeof(im_remove_request)); + if (im_request == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + im_request->image.image = util_strdup_s(image_ref); + im_request->force = force; + + ret = im_rm_image(im_request, &im_response); + if (ret != 0) { + if (im_response != NULL && im_response->errmsg != NULL) { + ERROR("Remove image %s failed:%s", image_ref, im_response->errmsg); + lcrd_try_set_error_message("Remove image %s failed:%s", image_ref, im_response->errmsg); + } else { + ERROR("Remove image %s failed", image_ref); + lcrd_try_set_error_message("Remove image %s failed", image_ref); + } + ret = -1; + goto out; + } + +out: + free_im_remove_request(im_request); + free_im_remove_response(im_response); + + return ret; +} + +/* image remove cb */ +static int image_remove_cb(const image_delete_image_request *request, + image_delete_image_response **response) +{ + int ret = -1; + char *image_ref = NULL; + uint32_t cc = LCRD_SUCCESS; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || request->image_name == NULL || response == NULL) { + ERROR("Invalid input arguments"); + return EINVALIDARGS; + } + + image_ref = request->image_name; + + *response = util_common_calloc_s(sizeof(image_delete_image_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto out; + } + + if (!util_valid_image_name(image_ref)) { + ERROR("Invalid image name %s", image_ref); + cc = LCRD_ERR_INPUT; + lcrd_try_set_error_message("Invalid image name:%s", + image_ref); + goto out; + } + + EVENT("Image Event: {Object: %s, Type: Deleting}", image_ref); + + ret = delete_image_info(image_ref, request->force); + if (ret != 0) { + cc = LCRD_ERR_EXEC; + goto out; + } + + EVENT("Image Event: {Object: %s, Type: Deleted}", image_ref); + +out: + if (*response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + } + + return (ret < 0) ? ECOMMON : ret; +} + +static bool valid_repo_tags(char * const * const repo_tags, size_t repo_index) +{ + if (repo_tags != NULL && repo_tags[repo_index] != NULL) { + return true; + } + + return false; +} + +static int trans_one_image(image_list_images_response *response, size_t image_index, + const imagetool_image *im_image, size_t repo_index) +{ + int ret = 0; + image_image *out_image = NULL; + + out_image = util_common_calloc_s(sizeof(image_image)); + if (out_image == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + response->images[image_index] = out_image; + response->images_len++; + + if (valid_repo_tags(im_image->repo_tags, repo_index)) { + out_image->name = util_strdup_s(im_image->repo_tags[repo_index]); + } + + out_image->target = util_common_calloc_s(sizeof(image_descriptor)); + if (out_image->target == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + /* This digest is image id. */ + out_image->target->digest = util_full_digest(im_image->id); + out_image->target->size = (int64_t)im_image->size; + + out_image->created_at = util_common_calloc_s(sizeof(timestamp)); + if (out_image->created_at == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + if (im_image->created != NULL) { + if (time_tz_to_seconds_nanos(im_image->created, + &(out_image->created_at->seconds), + &(out_image->created_at->nanos))) { + ERROR("Failed to translate created to seconds and nanos"); + ret = -1; + goto out; + } + } + +out: + + return ret; +} + +static size_t calc_images_display_num(const imagetool_images_list *images) +{ + size_t images_num = 0; + size_t i = 0; + const imagetool_image *im_image = NULL; + + for (i = 0; i < images->images_len; i++) { + size_t j = 0; + im_image = images->images[i]; + for (j = 0; j < im_image->repo_tags_len || (j == 0 && im_image->repo_tags_len == 0); j++) { + images_num++; + } + } + + return images_num; +} + +static int trans_im_list_images(const im_list_response *im_list, image_list_images_response *response) +{ + int ret = 0; + size_t i = 0; + size_t j = 0; + size_t images_num = 0; + size_t images_display_num = 0; + size_t image_index = 0; + imagetool_image *im_image = NULL; + + if (im_list == NULL || im_list->images == NULL) { + return -1; + } + + images_num = im_list->images->images_len; + if (images_num == 0) { + return 0; + } + + /* If one image have several repo tags, display them all. Image with no + * repo will also be displayed */ + images_display_num = calc_images_display_num(im_list->images); + if (images_display_num >= (SIZE_MAX / sizeof(image_image *))) { + INFO("Too many images, out of memory"); + ret = -1; + lcrd_try_set_error_message("Get too many images info, out of memory"); + goto out; + } + + response->images = util_common_calloc_s(sizeof(image_image *) * images_display_num); + if (response->images == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + for (i = 0; i < images_num; i++) { + im_image = im_list->images->images[i]; + for (j = 0; j < im_image->repo_tags_len || (j == 0 && im_image->repo_tags_len == 0); j++) { + ret = trans_one_image(response, image_index, im_image, j); + if (ret < 0) { + goto out; + } + image_index++; + } + } + +out: + return ret; +} + +/* image list cb */ +int image_list_cb(const image_list_images_request *request, + image_list_images_response **response) +{ + int ret = -1; + uint32_t cc = LCRD_SUCCESS; + im_list_request *im_request = NULL; + im_list_response *im_response = NULL; + + if (response == NULL) { + ERROR("Invalid input arguments"); + return EINVALIDARGS; + } + + DAEMON_CLEAR_ERRMSG(); + + im_request = util_common_calloc_s(sizeof(im_list_request)); + if (im_request == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto out; + } + + *response = util_common_calloc_s(sizeof(image_list_images_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto out; + } + + ret = im_list_images(im_request, &im_response); + if (ret) { + if (im_response != NULL && im_response->errmsg != NULL) { + ERROR("List images failed:%s", im_response->errmsg); + lcrd_try_set_error_message("List images failed:%s", im_response->errmsg); + } else { + ERROR("List images failed"); + lcrd_try_set_error_message("List images failed"); + } + cc = LCRD_ERR_EXEC; + goto out; + } + + ret = trans_im_list_images(im_response, *response); + if (ret) { + ERROR("Failed to translate list images info"); + cc = LCRD_ERR_EXEC; + goto out; + } + +out: + + free_im_list_request(im_request); + free_im_list_response(im_response); + + if (*response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + } + + return (ret < 0) ? ECOMMON : ret; +} + +static int inspect_image_with_valid_name(const char *image_ref, char **inspected_json) +{ + int ret = 0; + im_inspect_request *im_request = NULL; + im_inspect_response *im_response = NULL; + + if (image_ref == NULL) { + ERROR("invalid NULL param"); + return EINVALIDARGS; + } + + im_request = util_common_calloc_s(sizeof(im_inspect_request)); + if (im_request == NULL) { + ERROR("Out of memory"); + ret = -1; + goto out; + } + + im_request->image.image = util_strdup_s(image_ref); + + ret = im_inspect_image(im_request, &im_response); + if (ret != 0) { + if (im_response != NULL && im_response->errmsg != NULL) { + ERROR("Inspect image %s failed:%s", image_ref, im_response->errmsg); + lcrd_try_set_error_message("Inspect image %s failed:%s", image_ref, im_response->errmsg); + } else { + ERROR("Inspect image %s failed", image_ref); + lcrd_try_set_error_message("Inspect image %s failed", image_ref); + } + ret = -1; + goto out; + } + + *inspected_json = im_response->im_inspect_json ? util_strdup_s(im_response->im_inspect_json) : NULL; + +out: + free_im_inspect_request(im_request); + free_im_inspect_response(im_response); + + return ret; +} + +/* When inspect none image, we respone following string according hasen's request. */ +#define INSPECT_NONE_IMAGE_RESP \ + "{ \ + \"ContainerConfig\": { \ + \"Env\": [ \ + \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\" \ + ], \ + \"Entrypoint\": null \ + } \ +}" + +/* + * RETURN VALUE: + * 0: inspect success + * -1: no such image with "id" +*/ +static int inspect_image_helper(const char *image_ref, char **inspected_json) +{ + int ret = 0; + + if (strcmp(image_ref, "none") == 0 || strcmp(image_ref, "none:latest") == 0) { + *inspected_json = util_strdup_s(INSPECT_NONE_IMAGE_RESP); + INFO("Inspect image %s success", image_ref); + goto out; + } + + if (!util_valid_image_name(image_ref)) { + ERROR("Inspect invalid name %s", image_ref); + lcrd_set_error_message("Inspect invalid name %s", image_ref); + ret = -1; + goto out; + } + + if (inspect_image_with_valid_name(image_ref, inspected_json) != 0) { + ERROR("No such image or container or accelerator:%s", image_ref); + lcrd_set_error_message("No such image or container or accelerator:%s", image_ref); + ret = -1; + goto out; + } + +out: + return ret; +} + +static int image_inspect_cb(const image_inspect_request *request, image_inspect_response **response) +{ + char *name = NULL; + char *image_json = NULL; + uint32_t cc = LCRD_SUCCESS; + + DAEMON_CLEAR_ERRMSG(); + + if (request == NULL || response == NULL) { + ERROR("Invalid NULL input"); + return -1; + } + + *response = util_common_calloc_s(sizeof(image_inspect_response)); + if (*response == NULL) { + ERROR("Out of memory"); + cc = LCRD_ERR_MEMOUT; + goto pack_response; + } + + name = request->id; + + if (name == NULL) { + ERROR("receive NULL Request id"); + cc = LCRD_ERR_INPUT; + goto pack_response; + } + + set_log_prefix(name); + + INFO("Inspect :%s", name); + + if (inspect_image_helper(name, &image_json) != 0) { + cc = LCRD_ERR_EXEC; + } + +pack_response: + if (*response != NULL) { + (*response)->cc = cc; + if (g_lcrd_errmsg != NULL) { + (*response)->errmsg = util_strdup_s(g_lcrd_errmsg); + DAEMON_CLEAR_ERRMSG(); + } + (*response)->image_json = image_json; + } + + free_log_prefix(); + malloc_trim(0); + return (cc == LCRD_SUCCESS) ? 0 : -1; +} + +/* image callback init */ +void image_callback_init(service_image_callback_t *cb) +{ + if (cb == NULL) { + ERROR("Invalid input arguments"); + return; + } + + cb->load = image_load_cb; + cb->remove = image_remove_cb; + cb->list = image_list_cb; + cb->inspect = image_inspect_cb; + cb->login = login_cb; + cb->logout = logout_cb; +} + diff --git a/src/services/image/image_cb.h b/src/services/image/image_cb.h new file mode 100644 index 0000000..544886c --- /dev/null +++ b/src/services/image/image_cb.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide image function definition + ********************************************************************************/ + +#ifndef __IMAGE_CB_H_ +#define __IMAGE_CB_H_ + +#include "callback.h" + +#ifdef __cplusplus +extern "C" { +#endif +int image_list_cb(const image_list_images_request *request, + image_list_images_response **response); + +void image_callback_init(service_image_callback_t *cb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/sha256/CMakeLists.txt b/src/sha256/CMakeLists.txt new file mode 100644 index 0000000..19e6cc6 --- /dev/null +++ b/src/sha256/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_sha256_srcs) + +set(SHA256_SRCS + ${local_sha256_srcs} + PARENT_SCOPE + ) diff --git a/src/sha256/sha256.c b/src/sha256/sha256.c new file mode 100644 index 0000000..e303e57 --- /dev/null +++ b/src/sha256/sha256.c @@ -0,0 +1,266 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide container sha256 functions + *******************************************************************************/ + +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sha256.h" +#include "securec.h" +#include "log.h" +#include "utils.h" + +#define BLKSIZE 32768 + +static bool stream_check_eof(void *stream, bool isgzip) +{ + if (isgzip) { + if (gzeof(stream)) { + return true; + } + } else { + if (feof(stream)) { + return true; + } + } + return false; +} + +static bool stream_check_error(void *stream, bool isgzip) +{ + const char *gzerr = NULL; + if (isgzip) { + int errnum; + gzerr = gzerror(stream, &errnum); + } else if (ferror(stream)) { + return true; + } + if (gzerr != NULL && strcmp(gzerr, "") != 0) { + return true; + } + return false; +} + +int stream_read(void *stream, int fd, bool isgzip) +{ + size_t n = 0; + char *buffer = NULL; + + buffer = util_common_calloc_s(BLKSIZE + 72); + if (buffer == NULL) { + ERROR("Malloc BLKSIZE memory error"); + return -1; + } + + /* Read block. Take care for partial reads. */ + while (1) { + if (isgzip) { + n = (size_t)gzread((gzFile)stream, buffer, BLKSIZE); + } else { + n = fread(buffer, 1, BLKSIZE, (FILE *)stream); + } + + if (n == BLKSIZE) { + if (write(fd, buffer, BLKSIZE) == -1) { + ERROR("Write pipe failed: %s", strerror(errno)); + goto free_out; + } + n = 0; + continue; + } + + if (n == 0) { + if (stream_check_error(stream, isgzip)) { + goto free_out; + } + goto end_send; + } + + if (stream_check_eof(stream, isgzip)) { + goto end_send; + } + } + +end_send: + /* Process any remaining bytes. */ + if (n > 0) { + if (write(fd, buffer, n) == -1) { + ERROR("Write pipe_for_write failed: %s", strerror(errno)); + goto free_out; + } + } + free(buffer); + return 0; + +free_out: + free(buffer); + return -1; +} + +static int sha256sum_calculate_parent_handle(pid_t pid, int pipe_for_read[], int pipe_for_write[], bool isfile, + bool isgzip, void *stream, char *buffer_out, size_t len) +{ + ssize_t size_pipe_read; + + close(pipe_for_read[1]); + pipe_for_read[1] = -1; + close(pipe_for_write[0]); + pipe_for_write[0] = -1; + + if (isfile) { + int ret = stream_read((gzFile)stream, pipe_for_write[1], isgzip); + if (ret != 0) { + close(pipe_for_read[0]); + pipe_for_read[0] = -1; + close(pipe_for_write[1]); + pipe_for_write[1] = -1; + ERROR("Read buffer error"); + return -1; + } + } else if (write(pipe_for_write[1], (char *)stream, len) == -1) { + close(pipe_for_read[0]); + pipe_for_read[0] = -1; + close(pipe_for_write[1]); + pipe_for_write[1] = -1; + ERROR("Write pipe_for_write failed: %s", strerror(errno)); + return -1; + } + + close(pipe_for_write[1]); + pipe_for_write[1] = -1; + if (wait_for_pid(pid)) { + char buffer_errmsg[BUFSIZ] = { 0 }; + size_t size_read = (size_t)read(pipe_for_read[0], buffer_errmsg, BUFSIZ); + if (size_read != 0) { + ERROR("Sha256sum run error: %s", buffer_errmsg); + } + close(pipe_for_read[0]); + pipe_for_read[0] = -1; + return -1; + } + + size_pipe_read = read(pipe_for_read[0], buffer_out, SHA256_SIZE); + close(pipe_for_read[0]); + pipe_for_read[0] = -1; + if (size_pipe_read <= 0) { + ERROR("Read sha256 buffer failed"); + return -1; + } + buffer_out[SHA256_SIZE] = '\0'; + return 0; +} + +int sha256sum_calculate(void *stream, char *buffer_out, size_t len, bool isfile, + bool isgzip) +{ + int ret = 0; + pid_t pid; + int pipe_for_read[2] = { -1, -1 }; + int pipe_for_write[2] = { -1, -1 }; + + if (!stream || !buffer_out) { + ERROR("Param Error"); + ret = -1; + goto out; + } + if (pipe2(pipe_for_read, O_CLOEXEC) != 0) { + ERROR("Failed to create pipe"); + ret = -1; + goto out; + } + + if (pipe2(pipe_for_write, O_CLOEXEC) != 0) { + ERROR("Failed to create pipe"); + ret = -1; + goto out; + } + + pid = fork(); + if (pid == (pid_t) - 1) { + ERROR("Failed to fork()"); + ret = -1; + close(pipe_for_read[0]); + close(pipe_for_read[1]); + close(pipe_for_write[0]); + close(pipe_for_write[1]); + goto out; + } + + if (pid == (pid_t)0) { + // child process, dup2 pipe_for_read[1] to stdout, + // pipe_for_write[1] to stdin + close(pipe_for_read[0]); + close(pipe_for_write[1]); + if (dup2(pipe_for_read[1], 1) < 0) { + fprintf(stdout, "Dup fd error: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + if (dup2(pipe_for_write[0], 0)) { + fprintf(stdout, "Dup fd error: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + if (util_check_inherited(true, -1)) { + fprintf(stdout, "Failed to close fds."); + exit(EXIT_FAILURE); + } + execlp("sha256sum", "sha256sum", NULL); + + fprintf(stdout, "Failed to exec sha256sum program"); + exit(EXIT_FAILURE); + } + + ret = sha256sum_calculate_parent_handle(pid, pipe_for_read, pipe_for_write, + isfile, isgzip, stream, buffer_out, len); + +out: + return ret; +} + +char *sha256_digest(void *stream, bool isgzip) +{ + int ret = 0; + int cnt; + char buffer_out[SHA256_SIZE + 1]; + char *digest = NULL; + + if (stream == NULL) { + return NULL; + } + + ret = sha256sum_calculate(stream, buffer_out, 0, true, isgzip); + if (ret != 0) { + return NULL; + } + + digest = (char *)util_common_calloc_s(SHA256_SIZE + 1); + if (digest == NULL) { + return NULL; + } + digest[SHA256_SIZE] = 0; + + /* translate from binary to hex string */ + for (cnt = 0; cnt < SHA256_SIZE; ++cnt) { + digest[cnt] = buffer_out[cnt]; + } + return digest; +} diff --git a/src/sha256/sha256.h b/src/sha256/sha256.h new file mode 100644 index 0000000..fc60fe0 --- /dev/null +++ b/src/sha256/sha256.h @@ -0,0 +1,50 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2017-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2017-11-22 + * Description: provide container sha256 definition + ******************************************************************************/ +#ifndef SHA256_H +#define SHA256_H 1 + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { SHA224_SIZE = 224 / 8 }; +enum { SHA224_ALIGN = 4 }; +enum { SHA256_SIZE = 256 / 4 }; +enum { SHA256_ALIGN = 4 }; + +/* read file stream buffer */ +extern int fstream_read(FILE *stream, int fd); + +/* read gzfile stream buffer. */ +extern int gzstream_read(gzFile gzstream, int fd); + +extern int sha256sum_calculate(void *stream, char *buffer_out, size_t len, + bool isfile, + bool isgzip); +/* Compute SHA256 (SHA224) message digest for bytes read from STREAM. + The result is a 64 characters string without prefix "sha256:" */ +char *sha256_digest(void *stream, bool isgzip); + + +# ifdef __cplusplus +} +# endif + +#endif diff --git a/src/sysctl_tools.c b/src/sysctl_tools.c new file mode 100644 index 0000000..d090e31 --- /dev/null +++ b/src/sysctl_tools.c @@ -0,0 +1,119 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide sysctl functions + ********************************************************************************/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#include +#include + +#include "sysctl_tools.h" +#include "utils.h" + +int get_sysctl(const char *sysctl, char **err) +{ + int ret = 0; + int val = -1; + int fd = -1; + ssize_t rsize; + char fullpath[PATH_MAX] = { 0 }; + char buff[MAX_BUFFER_SIZE] = { 0 }; + + ret = sprintf_s(fullpath, PATH_MAX, "%s/%s", SYSCTL_BASE, sysctl); + if (ret < 0) { + *err = util_strdup_s("Out of memory"); + goto free_out; + } + ret = -1; + fd = util_open(fullpath, O_RDONLY, 0); + if (fd < 0) { + if (asprintf(err, "Open %s failed: %s", sysctl, strerror(errno)) < 0) { + *err = util_strdup_s("Out of memory"); + } + goto free_out; + } + rsize = util_read_nointr(fd, buff, MAX_BUFFER_SIZE); + if (rsize <= 0) { + if (asprintf(err, "Read file failed: %s", strerror(errno)) < 0) { + *err = util_strdup_s("Out of memory"); + } + goto free_out; + } + ret = util_safe_int(buff, &val); + if (ret != 0) { + if (asprintf(err, "Parse value : %s failed", buff) < 0) { + *err = util_strdup_s("Out of memory"); + } + goto free_out; + } + +free_out: + if (fd >= 0) { + close(fd); + } + if (ret != 0 && !*err) { + *err = util_strdup_s("Out of memory"); + } + return val; +} + +int set_sysctl(const char *sysctl, int new_value, char **err) +{ + int ret = 0; + int val = -1; + int fd = -1; + ssize_t rsize; + char fullpath[PATH_MAX] = { 0 }; + char buff[LCRD_NUMSTRLEN64] = { 0 }; + + ret = sprintf_s(fullpath, PATH_MAX, "%s/%s", SYSCTL_BASE, sysctl); + if (ret < 0) { + *err = util_strdup_s("Out of memory"); + goto free_out; + } + ret = sprintf_s(buff, LCRD_NUMSTRLEN64, "%d", new_value); + if (ret < 0) { + *err = util_strdup_s("Out of memory"); + goto free_out; + } + ret = -1; + fd = util_open(fullpath, O_WRONLY, 0); + if (fd < 0) { + if (asprintf(err, "Open %s failed: %s", sysctl, strerror(errno)) < 0) { + *err = util_strdup_s("Out of memory"); + } + goto free_out; + } + rsize = util_write_nointr(fd, buff, strlen(buff)); + if (rsize <= 0) { + if (asprintf(err, "Write new value failed: %s", strerror(errno)) < 0) { + *err = util_strdup_s("Out of memory"); + } + goto free_out; + } + + ret = 0; +free_out: + if (fd >= 0) { + close(fd); + } + if (ret != 0 && !*err) { + *err = util_strdup_s("Out of memory"); + } + return val; +} diff --git a/src/sysctl_tools.h b/src/sysctl_tools.h new file mode 100644 index 0000000..e448f2e --- /dev/null +++ b/src/sysctl_tools.h @@ -0,0 +1,31 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide sysctl function definition + ********************************************************************************/ +#ifndef _ISULA_SYSCTL_TOOLS_ +#define _ISULA_SYSCTL_TOOLS_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define SYSCTL_BASE "/proc/sys" + +int get_sysctl(const char *sysctl, char **err); + +int set_sysctl(const char *sysctl, int new_value, char **err); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/tar/CMakeLists.txt b/src/tar/CMakeLists.txt new file mode 100644 index 0000000..e83bb86 --- /dev/null +++ b/src/tar/CMakeLists.txt @@ -0,0 +1,7 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} local_tar_srcs) + +set(TAR_SRCS + ${local_tar_srcs} + PARENT_SCOPE + ) diff --git a/src/tar/lcrdtar.c b/src/tar/lcrdtar.c new file mode 100644 index 0000000..aaead68 --- /dev/null +++ b/src/tar/lcrdtar.c @@ -0,0 +1,916 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide tar functions + ********************************************************************************/ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include /* Obtain O_* constant definitions */ +#include +#include +#include +#include +#include "stdbool.h" +#include +#include +#include +#include +#include +#include +#include + +#include "lcrdtar.h" +#include "utils.h" +#include "path.h" +#include "log.h" +#include "error.h" +#include "json_common.h" + +#define TAR_MAX_OPTS 50 +#define TAR_CMD "tar" +#define TAR_TRANSFORM_OPT "--transform" +#define TAR_CREATE_OPT "-c" +#define TAR_EXACT_OPT "-x" +#define TAR_CHDIR_OPT "-C" +#define TAR_GZIP_OPT "-z" + +static void set_char_to_separator(char *p) +{ + *p = '/'; +} + +void free_archive_copy_info(struct archive_copy_info *info) +{ + if (info == NULL) { + return; + } + free(info->path); + info->path = NULL; + free(info->rebase_name); + info->rebase_name = NULL; + free(info); +} + +/* + * compress file. + * param filename: archive file to compres. + * return: zero if compress success, non-zero if not. + */ +int gzip(const char *filename, size_t len) +{ + int pipefd[2] = { -1, -1 }; + int status = 0; + pid_t pid = 0; + + if (filename == NULL) { + return -1; + } + if (len == 0) { + return -1; + } + + if (pipe2(pipefd, O_CLOEXEC) != 0) { + ERROR("Failed to create pipe\n"); + return -1; + } + + pid = fork(); + if (pid == -1) { + ERROR("Failed to fork()\n"); + close(pipefd[0]); + close(pipefd[1]); + return -1; + } + + if (pid == 0) { + // child process, dup2 pipefd[1] to stderr + close(pipefd[0]); + dup2(pipefd[1], 2); + + if (!util_valid_cmd_arg(filename)) { + fprintf(stderr, "Invalid filename: %s\n", filename); + exit(EXIT_FAILURE); + } + + execlp("gzip", "gzip", "-f", filename, NULL); + + fprintf(stderr, "Failed to exec gzip"); + exit(EXIT_FAILURE); + } + + ssize_t size_read = 0; + char buffer[BUFSIZ] = { 0 }; + + close(pipefd[1]); + + if (waitpid(pid, &status, 0) != pid) { + close(pipefd[0]); + return -1; + } + + size_read = read(pipefd[0], buffer, BUFSIZ); + close(pipefd[0]); + + if (size_read) { + ERROR("Received error:\n%s", buffer); + } + return status; +} + +struct archive_context { + int stdin_fd; + int stdout_fd; + int stderr_fd; + pid_t pid; +}; + +static ssize_t archive_context_read(void *context, void *buf, size_t len) +{ + struct archive_context *ctx = (struct archive_context *)context; + if (ctx == NULL) { + return -1; + } + if (ctx->stdout_fd >= 0) { + return util_read_nointr(ctx->stdout_fd, buf, len); + } + return 0; +} + +static ssize_t archive_context_write(const void *context, const void *buf, size_t len) +{ + struct archive_context *ctx = (struct archive_context *)context; + if (ctx == NULL) { + return -1; + } + if (ctx->stdin_fd >= 0) { + return util_write_nointr(ctx->stdin_fd, buf, len); + } + return 0; +} + +static int close_wait_pid(struct archive_context *ctx, int *status) +{ + int ret = 0; + + // close stdin and stdout first, this will make sure the process of tar exit. + if (ctx->stdin_fd >= 0) { + close(ctx->stdin_fd); + } + + if (ctx->stdout_fd >= 0) { + close(ctx->stdout_fd); + } + + if (ctx->pid > 0) { + if (waitpid(ctx->pid, status, 0) != ctx->pid) { + ERROR("Failed to wait pid %u", ctx->pid); + ret = -1; + } + } + + return ret; +} + +static int archive_context_close(void *context, char **err) +{ + int ret = 0; + int status = 0; + char *reason = NULL; + ssize_t size_read = 0; + char buffer[BUFSIZ + 1] = { 0 }; + struct archive_context *ctx = (struct archive_context *)context; + char *marshaled = NULL; + + if (ctx == NULL) { + return 0; + } + + ret = close_wait_pid(ctx, &status); + + if (WIFSIGNALED((unsigned int)status)) { + status = WTERMSIG(status); + reason = "signaled"; + } else if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + reason = "exited"; + } else { + reason = "unknown"; + } + + if (ctx->stderr_fd >= 0) { + size_read = util_read_nointr(ctx->stderr_fd, buffer, BUFSIZ); + if (size_read > 0) { + reason = buffer; + char *err_info = NULL; + marshaled = json_marshal_string(buffer, (size_t)size_read, NULL, &err_info); + if (marshaled == NULL) { + ERROR("Can not marshal json buffer: %s", err_info); + } else { + reason = marshaled; + } + free(err_info); + } + close(ctx->stderr_fd); + } + + if (size_read > 0 || status != 0) { + format_errorf(err, "tar exited with status %d: %s", status, reason); + ret = -1; + } + + free(marshaled); + free(ctx); + return ret; +} + +static int split_path_dir_entry(const char *path, char **dir, char **base) +{ + char cleaned[PATH_MAX + 3] = { 0 }; + char *dup = NULL; + + if (cleanpath(path, cleaned, PATH_MAX) == NULL) { + ERROR("Failed to clean path"); + return -1; + } + + if (specify_current_dir(path)) { + set_char_to_separator(&cleaned[strlen(cleaned)]); + cleaned[strlen(cleaned)] = '.'; + } + dup = util_strdup_s(cleaned); + if (dir != NULL) { + *dir = util_strdup_s(dirname(dup)); + } + if (base != NULL) { + *base = util_strdup_s(basename(cleaned)); + } + free(dup); + return 0; +} + +static int get_rebase_name(const char *path, const char *real_path, + char **resolved_path, char **rebase_name) +{ + int nret; + int ret = -1; + char resolved[PATH_MAX + 3] = { 0 }; + char *path_base = NULL; + char *resolved_base = NULL; + + nret = sprintf_s(resolved, PATH_MAX, "%s", real_path); + if (nret < 0) { + ERROR("Failed to print string"); + return -1; + } + + if (specify_current_dir(path) && !specify_current_dir(real_path)) { + set_char_to_separator(&resolved[strlen(resolved)]); + resolved[strlen(resolved)] = '.'; + } + + if (has_trailing_path_separator(path) && !has_trailing_path_separator(resolved)) { + resolved[strlen(resolved)] = '/'; + } + + nret = split_dir_and_base_name(path, NULL, &path_base); + if (nret != 0) { + ERROR("split %s failed", path); + goto cleanup; + } + nret = split_dir_and_base_name(resolved, NULL, &resolved_base); + if (nret != 0) { + ERROR("split %s failed", resolved); + goto cleanup; + } + + if (strcmp(path_base, resolved_base) != 0) { + // path is a symlink + *rebase_name = path_base; + path_base = NULL; + } + + *resolved_path = util_strdup_s(resolved); + ret = 0; + +cleanup: + free(path_base); + free(resolved_base); + return ret; +} + +int resolve_host_source_path(const char *path, bool follow_link, + char **resolved_path, char **rebase_name, char **err) +{ + int ret = -1; + int nret = 0; + char real_path[PATH_MAX] = { 0 }; + char resolved[PATH_MAX] = { 0 }; + char *dirpath = NULL; + char *basepath = NULL; + char *tmp_path_base = NULL; + char *tmp_resolved_base = NULL; + + *resolved_path = NULL; + *rebase_name = NULL; + + if (follow_link) { + if (realpath(path, real_path) == NULL) { + ERROR("Can not get real path of %s: %s", real_path, strerror(errno)); + format_errorf(err, "Can not get real path of %s: %s", real_path, strerror(errno)); + return -1; + } + nret = get_rebase_name(path, real_path, resolved_path, rebase_name); + if (nret < 0) { + ERROR("Failed to get rebase name"); + return -1; + } + } else { + nret = filepath_split(path, &dirpath, &basepath); + if (nret < 0) { + ERROR("Can not split path %s", path); + format_errorf(err, "Can not split path %s", path); + goto cleanup; + } + if (realpath(dirpath, real_path) == NULL) { + ERROR("Can not get real path of %s: %s", dirpath, strerror(errno)); + format_errorf(err, "Can not get real path of %s: %s", dirpath, strerror(errno)); + goto cleanup; + } + nret = sprintf_s(resolved, sizeof(resolved), "%s/%s", real_path, basepath); + if (nret < 0) { + ERROR("Path is too long"); + goto cleanup; + } + *resolved_path = util_strdup_s(resolved); + nret = split_dir_and_base_name(path, NULL, &tmp_path_base); + if (nret != 0) { + ERROR("split %s failed", path); + goto cleanup; + } + + nret = split_dir_and_base_name(resolved, NULL, &tmp_resolved_base); + if (nret != 0) { + ERROR("split %s failed", resolved); + goto cleanup; + } + + if (has_trailing_path_separator(path) && strcmp(tmp_path_base, tmp_resolved_base) != 0) { + *rebase_name = tmp_path_base; + tmp_path_base = NULL; + } + } + ret = 0; +cleanup: + free(dirpath); + free(basepath); + free(tmp_path_base); + free(tmp_resolved_base); + return ret; +} + +struct archive_copy_info *copy_info_source_path(const char *path, bool follow_link, char **err) +{ + int nret; + struct archive_copy_info *info = NULL; + struct stat st; + char *resolved_path = NULL; + char *rebase_name = NULL; + + info = util_common_calloc_s(sizeof(struct archive_copy_info)); + if (info == NULL) { + ERROR("Out of memory"); + return NULL; + } + + nret = resolve_host_source_path(path, follow_link, &resolved_path, &rebase_name, err); + if (nret < 0) { + goto cleanup; + } + + nret = lstat(resolved_path, &st); + if (nret < 0) { + ERROR("lstat %s: %s", resolved_path, strerror(errno)); + format_errorf(err, "lstat %s: %s", resolved_path, strerror(errno)); + goto cleanup; + } + + info->path = resolved_path; + resolved_path = NULL; + info->exists = true; + info->isdir = S_ISDIR(st.st_mode); + info->rebase_name = rebase_name; + rebase_name = NULL; + + return info; +cleanup: + free(resolved_path); + free(rebase_name); + free(info); + return NULL; +} + +static int copy_info_destination_path_ret(struct archive_copy_info *info, + struct stat st, char **err, int ret, const char *path) +{ + int i; + int max_symlink_iter = 10; + char *iter_path = NULL; + + iter_path = util_strdup_s(path); + for (i = 0; i <= max_symlink_iter && ret == 0 && S_ISLNK(st.st_mode); i++) { + char target[PATH_MAX + 1] = { 0 }; + char *parent = NULL; + + ret = (int)readlink(iter_path, target, PATH_MAX); + if (ret < 0) { + ERROR("Failed to read link of %s: %s", iter_path, strerror(errno)); + format_errorf(err, "Failed to read link of %s: %s", iter_path, strerror(errno)); + goto cleanup; + } + // is not absolutely path + if (target[0] != '\0') { + if (split_path_dir_entry(iter_path, &parent, NULL) < 0) { + goto cleanup; + } + free(iter_path); + iter_path = util_path_join(parent, target); + if (iter_path == NULL) { + ERROR("Failed to join path"); + free(parent); + goto cleanup; + } + } else { + free(iter_path); + iter_path = util_strdup_s(target); + } + ret = lstat(iter_path, &st); + free(parent); + } + + if (i > max_symlink_iter) { + ERROR("Too many symlinks in: %s", path); + format_errorf(err, "Too many symlinks in: %s", path); + goto cleanup; + } + + if (ret != 0) { + char *dst_parent = NULL; + if (errno != ENOENT) { + ERROR("Can not stat %s: %s", iter_path, strerror(errno)); + format_errorf(err, "Can not stat %s: %s", iter_path, strerror(errno)); + goto cleanup; + } + + if (split_path_dir_entry(iter_path, &dst_parent, NULL) < 0) { + goto cleanup; + } + + if (!util_dir_exists(dst_parent)) { + ERROR("Path %s is not exists or not a directory", dst_parent); + format_errorf(err, "Path %s is not exists or not a directory", dst_parent); + free(dst_parent); + goto cleanup; + } + free(dst_parent); + info->path = iter_path; + return 0; + } + + info->path = iter_path; + info->exists = true; + info->isdir = S_ISDIR(st.st_mode); + return 0; +cleanup: + free(iter_path); + return -1; +} + +struct archive_copy_info *copy_info_destination_path(const char *path, char **err) +{ + struct archive_copy_info *info = NULL; + struct stat st; + int ret = 0; + int nret = -1; + + info = util_common_calloc_s(sizeof(struct archive_copy_info)); + if (info == NULL) { + ERROR("Out of memory"); + return NULL; + } + + ret = lstat(path, &st); + if (ret == 0 && !S_ISLNK(st.st_mode)) { + info->path = util_strdup_s(path); + info->exists = true; + info->isdir = S_ISDIR(st.st_mode); + return info; + } + + nret = copy_info_destination_path_ret(info, st, err, ret, path); + if (nret == 0) { + return info; + } else { + goto cleanup; + } +cleanup: + free(info); + return NULL; +} + +static bool asserts_directory(const char *path) +{ + return has_trailing_path_separator(path) || specify_current_dir(path); +} + +static char *format_transform_of_tar(const char *srcbase, const char *dstbase) +{ + char *transform = NULL; + const char *src_escaped = srcbase; + const char *dst_escaped = dstbase; + int nret; + size_t len; + + if (srcbase == NULL || dstbase == NULL) { + return NULL; + } + + // escape "/" by "." to avoid generating leading / in tar archive which is dangerous to host when untar. + // this means tar or untar with leading / is forbidden and may got error, take care of this when coding. + if (strcmp(srcbase, "/") == 0) { + src_escaped = "."; + } + + if (strcmp(dstbase, "/") == 0) { + dst_escaped = "."; + } + + len = strlen(src_escaped) + strlen(dst_escaped) + 5; + if (len > PATH_MAX) { + ERROR("Invalid path length"); + return NULL; + } + + transform = util_common_calloc_s(len); + if (transform == NULL) { + ERROR("Out of memory"); + return NULL; + } + nret = sprintf_s(transform, len, "s/%s/%s/", src_escaped, dst_escaped); + if (nret < 0) { + ERROR("Failed to print string"); + free(transform); + return NULL; + } + return transform; +} + +char *prepare_archive_copy(const struct archive_copy_info *srcinfo, const struct archive_copy_info *dstinfo, + char **transform, char **err) +{ + char *dstdir = NULL; + char *srcbase = NULL; + char *dstbase = NULL; + + if (split_path_dir_entry(dstinfo->path, &dstdir, &dstbase) < 0) { + goto cleanup; + } + if (split_path_dir_entry(srcinfo->path, NULL, &srcbase) < 0) { + goto cleanup; + } + + if (dstinfo->exists && dstinfo->isdir) { + // dst exists and is a directory, untar src content directly + free(dstdir); + dstdir = util_strdup_s(dstinfo->path); + } else if (dstinfo->exists && srcinfo->isdir) { + // dst exists and is a file, src content is a directory, report error + format_errorf(err, "cannot copy directory to file"); + free(dstdir); + dstdir = NULL; + } else if (dstinfo->exists) { + // dst exists and is a file, src is a file, rename basename of src name to dest's basename. + if (srcinfo->rebase_name != NULL) { + free(srcbase); + srcbase = util_strdup_s(srcinfo->rebase_name); + } + *transform = format_transform_of_tar(srcbase, dstbase); + } else if (srcinfo->isdir) { + // dst does not exist and src is a directory, untar the content to parent of dest, + // and rename basename of src name to dest's basename. + if (srcinfo->rebase_name != NULL) { + free(srcbase); + srcbase = util_strdup_s(srcinfo->rebase_name); + } + *transform = format_transform_of_tar(srcbase, dstbase); + } else if (asserts_directory(dstinfo->path)) { + // dst does not exist and is want to be created as a directory, but src is not a directory, report error. + format_errorf(err, "no such directory, can not copy file"); + free(dstdir); + dstdir = NULL; + } else { + // dst does not exist and is not want to be created as a directory, and the src is not a directory, + // create the dst file and renamed src content to basename of dst. + if (srcinfo->rebase_name != NULL) { + free(srcbase); + srcbase = util_strdup_s(srcinfo->rebase_name); + } + *transform = format_transform_of_tar(srcbase, dstbase); + } + +cleanup: + free(srcbase); + free(dstbase); + return dstdir; +} + +static void close_pipe_fd(int pipe_fd[]) +{ + if (pipe_fd[0] != -1) { + close(pipe_fd[0]); + pipe_fd[0] = -1; + } + if (pipe_fd[1] != -1) { + close(pipe_fd[1]); + pipe_fd[1] = -1; + } +} + +int archive_untar(const struct io_read_wrapper *content, bool compression, const char *dstdir, + const char *transform, char **err) +{ + int stdin_pipe[2] = { -1, -1 }; + int stderr_pipe[2] = { -1, -1 }; + int ret = -1; + int cret = 0; + pid_t pid; + struct archive_context *ctx = NULL; + char *buf = NULL; + size_t buf_len = ARCHIVE_BLOCK_SIZE; + ssize_t read_len; + const char *params[TAR_MAX_OPTS] = { NULL }; + + buf = util_common_calloc_s(buf_len); + if (buf == NULL) { + ERROR("Out of memory"); + return -1; + } + + if (pipe(stderr_pipe) != 0) { + ERROR("Failed to create pipe: %s", strerror(errno)); + goto cleanup; + } + if (pipe(stdin_pipe) != 0) { + ERROR("Failed to create pipe: %s", strerror(errno)); + goto cleanup; + } + + pid = fork(); + if (pid == (pid_t) - 1) { + ERROR("Failed to fork: %s", strerror(errno)); + goto cleanup; + } + + if (pid == (pid_t)0) { + int i = 0; + // child process, dup2 stderr[1] to stderr, stdout[0] to stdin. + close(stderr_pipe[0]); + dup2(stderr_pipe[1], 2); + close(stdin_pipe[1]); + dup2(stdin_pipe[0], 0); + + params[i++] = TAR_CMD; + params[i++] = TAR_EXACT_OPT; + if (compression) { + params[i++] = TAR_GZIP_OPT; + } + params[i++] = TAR_CHDIR_OPT; + params[i++] = dstdir; + if (transform != NULL) { + params[i++] = TAR_TRANSFORM_OPT; + params[i++] = transform; + } + + execvp(TAR_CMD, (char * const *)params); + + fprintf(stderr, "Failed to exec tar: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + close(stderr_pipe[1]); + stderr_pipe[1] = -1; + close(stdin_pipe[0]); + stdin_pipe[0] = -1; + + ctx = util_common_calloc_s(sizeof(struct archive_context)); + if (ctx == NULL) { + goto cleanup; + } + + ctx->pid = pid; + ctx->stdin_fd = stdin_pipe[1]; + stdin_pipe[1] = -1; + ctx->stdout_fd = -1; + ctx->stderr_fd = stderr_pipe[0]; + stderr_pipe[0] = -1; + + read_len = content->read(content->context, buf, buf_len); + while (read_len > 0) { + ssize_t writed_len = archive_context_write(ctx, buf, (size_t)read_len); + if (writed_len < 0) { + DEBUG("Tar may exited: %s", strerror(errno)); + break; + } + read_len = content->read(content->context, buf, buf_len); + } + + ret = 0; + +cleanup: + free(buf); + cret = archive_context_close(ctx, err); + ret = (cret != 0) ? cret : ret; + close_pipe_fd(stderr_pipe); + close_pipe_fd(stdin_pipe); + + return ret; +} + +int archive_copy_to(const struct io_read_wrapper *content, bool compression, const struct archive_copy_info *srcinfo, + const char *dstpath, char **err) +{ + int ret = -1; + struct archive_copy_info *dstinfo = NULL; + char *dstdir = NULL; + char *transform = NULL; + + dstinfo = copy_info_destination_path(dstpath, err); + if (dstinfo == NULL) { + ERROR("Can not get destination info: %s", dstpath); + return -1; + } + + dstdir = prepare_archive_copy(srcinfo, dstinfo, &transform, err); + if (dstdir == NULL) { + ERROR("Can not prepare archive copy"); + goto cleanup; + } + + ret = archive_untar(content, compression, dstdir, transform, err); + +cleanup: + free_archive_copy_info(dstinfo); + free(dstdir); + free(transform); + return ret; +} + +static void close_archive_pipes_fd(int *pipes, size_t pipe_size) +{ + size_t i = 0; + + for (i = 0; i < pipe_size; i++) { + if (pipes[i] >= 0) { + close(pipes[i]); + pipes[i] = -1; + } + } +} + +/* + * Archive file or directory. + * param src : file or directory to compression. + * param compression : using gzip compression or not + * param exclude_base : exclude source basename in the archived file or not + * return : zero if archive success, non-zero if not. + */ +int archive_path(const char *srcdir, const char *srcbase, const char *rebase_name, + bool compression, struct io_read_wrapper *archive_reader) +{ + int stderr_pipe[2] = { -1, -1 }; + int stdout_pipe[2] = { -1, -1 }; + int ret = -1; + pid_t pid; + struct archive_context *ctx = NULL; + char *transform = NULL; + const char *params[TAR_MAX_OPTS] = { NULL }; + + transform = format_transform_of_tar(srcbase, rebase_name); + + if (pipe(stderr_pipe) != 0) { + ERROR("Failed to create pipe: %s", strerror(errno)); + goto free_out; + } + if (pipe(stdout_pipe) != 0) { + ERROR("Failed to create pipe: %s", strerror(errno)); + goto free_out; + } + + pid = fork(); + if (pid == (pid_t) - 1) { + ERROR("Failed to fork: %s", strerror(errno)); + goto free_out; + } + + if (pid == (pid_t)0) { + int i = 0; + // child process, dup2 stderr[1] to stderr, stdout[1] to stdout. + close(stderr_pipe[0]); + close(stdout_pipe[0]); + dup2(stderr_pipe[1], 2); + dup2(stdout_pipe[1], 1); + + params[i++] = TAR_CMD; + params[i++] = TAR_CREATE_OPT; + if (compression) { + params[i++] = TAR_GZIP_OPT; + } + params[i++] = TAR_CHDIR_OPT; + params[i++] = srcdir; + if (transform != NULL) { + params[i++] = TAR_TRANSFORM_OPT; + params[i++] = transform; + } + params[i++] = srcbase; + + execvp(TAR_CMD, (char * const *)params); + + fprintf(stderr, "Failed to exec tar: %s", strerror(errno)); + exit(EXIT_FAILURE); + } + + close(stderr_pipe[1]); + stderr_pipe[1] = -1; + close(stdout_pipe[1]); + stdout_pipe[1] = -1; + + ctx = util_common_calloc_s(sizeof(struct archive_context)); + if (ctx == NULL) { + goto free_out; + } + + ctx->stdin_fd = -1; + ctx->stdout_fd = stdout_pipe[0]; + stdout_pipe[0] = -1; + ctx->stderr_fd = stderr_pipe[0]; + stderr_pipe[0] = -1; + ctx->pid = pid; + + archive_reader->close = archive_context_close; + archive_reader->context = ctx; + ctx = NULL; + archive_reader->read = archive_context_read; + + ret = 0; +free_out: + free(transform); + close_archive_pipes_fd(stderr_pipe, 2); + close_archive_pipes_fd(stdout_pipe, 2); + free(ctx); + + return ret; +} + +int tar_resource_rebase(const char *path, const char *rebase, struct io_read_wrapper *archive_reader, char **err) +{ + int ret = -1; + int nret; + struct stat st; + char *srcdir = NULL; + char *srcbase = NULL; + + if (lstat(path, &st) < 0) { + ERROR("lstat %s: %s", path, strerror(errno)); + format_errorf(err, "lstat %s: %s", path, strerror(errno)); + return -1; + } + if (split_path_dir_entry(path, &srcdir, &srcbase) < 0) { + ERROR("Can not split path: %s", path); + goto cleanup; + } + + DEBUG("Copying %s from %s", srcbase, srcdir); + nret = archive_path(srcdir, srcbase, rebase, false, archive_reader); + if (nret < 0) { + ERROR("Can not archive path: %s", path); + goto cleanup; + } + ret = 0; +cleanup: + free(srcdir); + free(srcbase); + return ret; +} + +int tar_resource(const struct archive_copy_info *info, struct io_read_wrapper *archive_reader, char **err) +{ + return tar_resource_rebase(info->path, info->rebase_name, archive_reader, err); +} + diff --git a/src/tar/lcrdtar.h b/src/tar/lcrdtar.h new file mode 100644 index 0000000..91d2922 --- /dev/null +++ b/src/tar/lcrdtar.h @@ -0,0 +1,77 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide tar function definition + *********************************************************************************/ + + +#ifndef __LCRD_TAR_H_ +#define __LCRD_TAR_H_ + +#include +#include +#include +#include + +#include +#include +#include "console.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define GZIPHEADERLEN 3 +#define ARCHIVE_BLOCK_SIZE (32 * 1024) + +struct archive_copy_info { + char *path; + bool exists; + bool isdir; + char *rebase_name; +}; + +void free_archive_copy_info(struct archive_copy_info *info); + +struct archive_tar_resource_rebase_opts { + bool compression; + char *include_file; +}; + +/* + * compress file. + * param filename : archive file to compres. + * return: zero if compress success, non-zero if not. + */ +int gzip(const char *filename, size_t len); + +struct archive_copy_info *copy_info_source_path(const char *path, bool follow_link, char **err); + +char *prepare_archive_copy(const struct archive_copy_info *srcinfo, const struct archive_copy_info *dstinfo, + char **transform, char **err); + +int tar_resource(const struct archive_copy_info *info, struct io_read_wrapper *archive_reader, char **err); + +int archive_untar(const struct io_read_wrapper *content, bool compression, const char *dstdir, + const char *transform, char **err); + +int archive_copy_to(const struct io_read_wrapper *content, bool compression, const struct archive_copy_info *srcinfo, + const char *dstpath, char **err); + +int archive_path(const char *srcdir, const char *srcbase, const char *rebase_name, + bool compression, struct io_read_wrapper *archive_reader); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/types_def.c b/src/types_def.c new file mode 100644 index 0000000..6688de8 --- /dev/null +++ b/src/types_def.c @@ -0,0 +1,903 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide typedef functions + ********************************************************************************/ +#include "types_def.h" +#include +#include + +#include "log.h" +#include "utils.h" + +int types_timestamp_cmp_check(const types_timestamp_t *t1, const types_timestamp_t *t2) +{ + if (t1 == NULL && t2 == NULL) { + return 0; + } + if (t1 != NULL && t2 == NULL) { + return 1; + } + if (t1 == NULL && t2 != NULL) { + return -1; + } + + return 2; +} + +int types_timestamp_cmp_nanos(const types_timestamp_t *t1, const types_timestamp_t *t2) +{ + if (t1->has_nanos && t2->has_nanos) { + if (t1->nanos > t2->nanos) { + return 1; + } + if (t1->nanos < t2->nanos) { + return -1; + } + return 0; + } + if (t1->has_nanos) { + return 1; + } + + if (t2->has_nanos) { + return -1; + } + + return 0; +} + +/* types timestamp cmp */ +int types_timestamp_cmp(const types_timestamp_t *t1, const types_timestamp_t *t2) +{ + int ret = 0; + + ret = types_timestamp_cmp_check(t1, t2); + if (ret != 2) { + return ret; + } + + if (t1->has_seconds && t2->has_seconds) { + if (t1->seconds > t2->seconds) { + return 1; + } + if (t1->seconds < t2->seconds) { + return -1; + } + return types_timestamp_cmp_nanos(t1, t2); + } + + if (t1->has_seconds) { + return 1; + } + if (t2->has_seconds) { + return -1; + } + return 0; +} + +/* get timestamp */ +bool get_timestamp(const char *str_time, types_timestamp_t *timestamp) +{ + int64_t seconds = 0; + int32_t nanos = 0; + struct tm tm_day; + + if (memset_s(&tm_day, sizeof(tm_day), 0, sizeof(tm_day)) != EOK) { + ERROR("Failed to memset memory"); + return false; + } + + if (timestamp == NULL || str_time == NULL) { + return false; + } + + if (!get_tm_from_str(str_time, &tm_day, &nanos)) { + return false; + } + + seconds = (int64_t)mktime(&tm_day); + timestamp->has_seconds = true; + timestamp->seconds = seconds; + if (nanos) { + timestamp->has_nanos = true; + timestamp->nanos = nanos; + } + + return true; +} + +/* get time buffer */ +bool get_time_buffer(const types_timestamp_t *timestamp, char *timebuffer, size_t maxsize) +{ + struct tm tm_utc = { 0 }; + struct tm tm_local = { 0 }; + int tm_zone = 0; + int32_t nanos; + int nret = 0; + time_t seconds; + + if (timebuffer == NULL || maxsize == 0 || !timestamp->has_seconds) { + return false; + } + + seconds = (time_t)timestamp->seconds; + localtime_r(&seconds, &tm_local); + strftime(timebuffer, maxsize, "%Y-%m-%dT%H:%M:%S", &tm_local); + + if (timestamp->has_nanos) { + nanos = timestamp->nanos; + } else { + nanos = 0; + } + + gmtime_r(&seconds, &tm_utc); + tm_zone = tm_local.tm_hour - tm_utc.tm_hour; + if (tm_zone < -12) { + tm_zone += 24; + } else if (tm_zone > 12) { + tm_zone -= 24; + } + + if (tm_zone >= 0) { + nret = sprintf_s(timebuffer + strlen(timebuffer), + maxsize - strlen(timebuffer), ".%09d+%02d:00", nanos, tm_zone); + } else { + nret = sprintf_s(timebuffer + strlen(timebuffer), + maxsize - strlen(timebuffer), ".%09d-%02d:00", nanos, -tm_zone); + } + if (nret < 0) { + ERROR("sprintf timebuffer failed"); + return false; + } + + return true; +} + +bool get_now_time_stamp(types_timestamp_t *timestamp) +{ + int err = 0; + struct timespec ts; + + err = clock_gettime(CLOCK_REALTIME, &ts); + if (err != 0) { + ERROR("failed to get time"); + return false; + } + timestamp->has_seconds = true; + timestamp->seconds = (int64_t)ts.tv_sec; + timestamp->has_nanos = true; + timestamp->nanos = (int32_t)ts.tv_nsec; + return true; +} + +/* get now time buffer */ +bool get_now_time_buffer(char *timebuffer, size_t maxsize) +{ + types_timestamp_t timestamp; + + if (get_now_time_stamp(×tamp) == false) { + return false; + } + + return get_time_buffer(×tamp, timebuffer, maxsize); +} + +int get_time_interval(types_timestamp_t first, types_timestamp_t last, int64_t *result) +{ + int64_t seconds_diff = 0; + int64_t nanos_diff = 0; + + if (result == NULL) { + return -1; + } + + seconds_diff = (last.has_seconds ? last.seconds : 0) - (first.has_seconds ? first.seconds : 0); + nanos_diff = (last.has_nanos ? last.nanos : 0) - (first.has_nanos ? first.nanos : 0); + + if (seconds_diff > INT64_MAX / Time_Second || + (seconds_diff == INT64_MAX / Time_Second && nanos_diff > INT64_MAX % Time_Second)) { + return -1; + } + *result = (seconds_diff * Time_Second) + nanos_diff; + + return 0; +} + +static int parsing_time_to_digit(const char *time, size_t *i) +{ + int sum = 0; + + while (time[*i] != '\0' && isdigit(time[*i])) { + sum = sum * 10 + time[*i] - '0'; + (*i)++; + } + return sum; +} + +static void parsing_time_data_year(struct tm *tm, const char *time, size_t *i) +{ + tm->tm_year = parsing_time_to_digit(time, i); +} + +static void parsing_time_data_month(struct tm *tm, const char *time, size_t *i) +{ + tm->tm_mon = parsing_time_to_digit(time, i); +} + +static void parsing_time_data_day(struct tm *tm, const char *time, size_t *i) +{ + tm->tm_mday = parsing_time_to_digit(time, i); +} + +static void parsing_time_data_hour(struct tm *tm, const char *time, size_t *i) +{ + tm->tm_hour = parsing_time_to_digit(time, i); +} + +static void parsing_time_data_min(struct tm *tm, const char *time, size_t *i) +{ + tm->tm_min = parsing_time_to_digit(time, i); +} + +static void parsing_time_data_sec(struct tm *tm, const char *time, size_t *i) +{ + tm->tm_sec = parsing_time_to_digit(time, i); +} + +static void parsing_time_data(const char *time, struct tm *tm) +{ + size_t i = 0; + + parsing_time_data_year(tm, time, &i); + if (time[i] == '\0') { + return; + } + i++; + + parsing_time_data_month(tm, time, &i); + if (time[i] == '\0') { + return; + } + i++; + + parsing_time_data_day(tm, time, &i); + if (time[i] == '\0') { + return; + } + i++; + + parsing_time_data_hour(tm, time, &i); + if (time[i] == '\0') { + return; + } + i++; + + parsing_time_data_min(tm, time, &i); + if (time[i] == '\0') { + return; + } + i++; + + parsing_time_data_sec(tm, time, &i); +} + +bool parsing_time(const char *format, const char *time, struct tm *tm, int32_t *nanos) +{ + size_t len_format = 0; + size_t len_time = 0; + size_t index_nanos = 0; + + if (format == NULL || time == NULL) { + return false; + } + + if (strcmp(format, rFC339NanoLocal) == 0) { + index_nanos = strlen(rFC339Local) + 1; + } + len_format = strlen(format); + len_time = strlen(time); + + if (index_nanos) { + if (len_format < len_time || index_nanos >= len_time) { + return false; + } + } else { + if (len_format != len_time) { + return false; + } + } + + if (index_nanos) { + *nanos = 0; + while (time[index_nanos] != '\0') { + *nanos = *nanos * 10 + time[index_nanos] - '0'; + index_nanos++; + } + while (index_nanos < len_format) { + *nanos *= 10; + index_nanos++; + } + } else { + *nanos = 0; + } + + parsing_time_data(time, tm); + + return true; +} + +static bool is_out_of_range(int value, int lower, int upper) +{ + return (value > upper) || (value < lower); +} + +static bool is_leap_year(int year) +{ + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +} + +int get_valid_days(int mon, int year) +{ + int leap_year = 0; + int valid_days = 31; + + if (is_leap_year(year)) { + leap_year = 1; + } + + switch (mon) { + case 2: + valid_days = (valid_days - 3) + leap_year; + break; + case 4: + case 6: + case 9: + case 11: + valid_days = 30; + break; + default: + break; + } + + return valid_days; +} + +bool fix_date(struct tm *tm) +{ + if (tm == NULL) { + return false; + } + + bool ret = (is_out_of_range(tm->tm_hour, 0, 23)) || (is_out_of_range(tm->tm_min, 0, 59)) || + (is_out_of_range(tm->tm_sec, 0, 59)) || (is_out_of_range(tm->tm_mon, 1, 12)) || + (is_out_of_range(tm->tm_year, 1900, 9999)); + + if (ret) { + ERROR("Normal section out of range"); + return false; + } + + int valid_day = get_valid_days(tm->tm_mon, tm->tm_year); + ret = ret || is_out_of_range(tm->tm_mday, 1, valid_day); + if (ret) { + ERROR("Day out of range"); + return false; + } + tm->tm_year -= 1900; + tm->tm_mon -= 1; + return true; +} + +bool get_tm_from_str(const char *str, struct tm *tm, int32_t *nanos) +{ + char *format = NULL; + + if (str == NULL || tm == NULL || nanos == NULL) { + return false; + } + + if (strings_contains_any(str, ".")) { + format = rFC339NanoLocal; + } else if (strings_contains_any(str, "T")) { + int tcolons = strings_count(str, ':'); + switch (tcolons) { + case 0: + format = "2016-01-02T15"; + break; + case 1: + format = "2016-01-02T15:04"; + break; + case 2: + format = rFC339Local; + break; + default: + ERROR("date format error"); + return false; + } + } else { + format = dateLocal; + } + + if (!parsing_time(format, str, tm, nanos)) { + ERROR("Failed to parse time \"%s\" with format \"%s\"", str, format); + return false; + } + + if (!fix_date(tm)) { + ERROR("\"%s\" is invalid", str); + return false; + } + + return true; +} + +static char *tm_get_zp(const char *tmstr) +{ + char *zp = NULL; + + zp = strrchr(tmstr, '+'); + if (zp == NULL) { + zp = strrchr(tmstr, '-'); + } + return zp; +} + +static inline bool hasnil(const char *str, struct tm *tm, int32_t *nanos, struct types_timezone *tz) +{ + if (str == NULL || tm == NULL || nanos == NULL || tz == NULL) { + return true; + } + return false; +} + +static size_t tz_init_hour(struct types_timezone *tz, const char *zonestr, size_t i) +{ + int positive = 1; + int sum = 0; + + if (zonestr[0] == '-') { + positive = -1; + } + + sum = parsing_time_to_digit(zonestr, &i); + tz->hour = positive * sum; + return i; +} + +static size_t tz_init_min(struct types_timezone *tz, const char *zonestr, size_t i) +{ + int positive = 1; + int sum = 0; + + if (zonestr[0] == '-') { + positive = -1; + } + + sum = parsing_time_to_digit(zonestr, &i); + tz->min = positive * sum; + return i; +} + +static bool tz_init_ok(struct types_timezone *tz, const char *zonestr) +{ + size_t i = 0; + + i = tz_init_hour(tz, zonestr, 1); + if (zonestr[i] == '\0') { + return false; + } + tz_init_min(tz, zonestr, i + 1); + return true; +} + +static bool get_tm_zone_from_str(const char *str, struct tm *tm, int32_t *nanos, struct types_timezone *tz) +{ + char *tmstr = NULL; + char *zp = NULL; + char *zonestr = NULL; + + if (hasnil(str, tm, nanos, tz)) { + ERROR("Get tm and timezone from str input error"); + goto err_out; + } + + tmstr = util_strdup_s(str); + zp = tm_get_zp(tmstr); + if (zp == NULL) { + ERROR("No time zone symbol found in input string"); + goto err_out; + } + zonestr = util_strdup_s(zp); + *zp = '\0'; + + if (!get_tm_from_str(tmstr, tm, nanos)) { + ERROR("Get tm from str failed"); + goto err_out; + } + + if (!tz_init_ok(tz, zonestr)) { + ERROR("init tz failed"); + goto err_out; + } + + free(tmstr); + free(zonestr); + return true; + +err_out: + free(tmstr); + free(zonestr); + return false; +} + +static int64_t get_minmus_time(struct tm *tm1, struct tm *tm2) +{ + int64_t tmseconds1, tmseconds2, result; + + if (tm1 == NULL || tm2 == NULL) { + return -1; + } + + tmseconds1 = (int64_t)mktime(tm1); + tmseconds2 = (int64_t)mktime(tm2); + result = tmseconds1 - tmseconds2; + return result; +} + +int64_t time_seconds_since(const char *in) +{ + int32_t nanos = 0; + int64_t result = 0; + struct tm tm = { 0 }; + struct tm *currentm = NULL; + struct types_timezone tz = { 0 }; + time_t currentime; + + if (in == NULL || !strcmp(in, defaultContainerTime) || !strcmp(in, "-")) { + return 0; + } + + if (!get_tm_zone_from_str(in, &tm, &nanos, &tz)) { + ERROR("Failed to trans time %s", in); + return 0; + } + + time(¤time); + currentm = gmtime(¤time); + if (currentm == NULL) { + ERROR("Get time error"); + return 0; + } + + result = get_minmus_time(currentm, &tm); + result = result + (int64_t)tz.hour * 3600 + (int64_t)tz.min * 60; + + if (result > 0) { + return result; + } else { + return 0; + } +} + +struct time_human_duration_rule_t { + bool (*check_human_duration)(int64_t seconds); + int (*gen_human_duration)(int64_t seconds, char *str, size_t len); +}; + +static bool check_human_duration_less_1_sec(int64_t seconds) +{ + return seconds < 1; +} + +static int gen_human_duration_less_1_sec(int64_t secondes, char *str, size_t len) +{ + return sprintf_s(str, len, "Less than a second"); +} + +static bool check_human_duration_less_60_secs(int64_t seconds) +{ + return seconds < 60; +} + +static int gen_human_duration_less_60_secs(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "%lld seconds", (long long)seconds); +} + +static bool check_human_duration_eq_1_min(int64_t seconds) +{ + return (seconds / 60 == 1); +} + +static int gen_human_duration_eq_1_min(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "About a minute"); +} + +static bool check_human_duration_less_60_mins(int64_t seconds) +{ + return (seconds / 60 < 60); +} + +static int gen_human_duration_less_60_mins(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "%lld minutes", (long long)seconds / 60); +} + +static bool check_human_duration_eq_1_hour(int64_t seconds) +{ + return (seconds / (60 * 60) == 1); +} + +static int gen_human_duration_eq_1_hour(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "About an hour"); +} + +static bool check_human_duration_less_48_hours(int64_t seconds) +{ + return (seconds / (60 * 60) < 48); +} + +static int gen_human_duration_less_48_hours(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "%lld hours", (long long)seconds / (60 * 60)); +} + +static bool check_human_duration_less_7_days(int64_t seconds) +{ + return (seconds / (60 * 60) < 7 * 48); +} + +static int gen_human_duration_less_7_days(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "%lld days", (long long)seconds / (60 * 60 * 24)); +} + +static bool check_human_duration_less_90_days(int64_t seconds) +{ + return (seconds / (60 * 60) < 24 * 30 * 3); +} + +static int gen_human_duration_less_90_days(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "%lld weeks", (long long)seconds / (60 * 60 * 24 * 7)); +} + +static bool check_human_duration_less_2_years(int64_t seconds) +{ + return (seconds / (60 * 60) < 24 * 365 * 2); +} + +static int gen_human_duration_less_2_years(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "%lld months", (long long)seconds / (60 * 60 * 24 * 30)); +} + +static bool check_human_duration_default(int64_t seconds) +{ + return (seconds / (60 * 60) >= 24 * 365 * 2); +} + +static int gen_human_duration_default(int64_t seconds, char *str, size_t len) +{ + return sprintf_s(str, len, "%lld years", (long long)seconds / (60 * 60 * 24 * 365)); +} + +typedef struct time_human_duration_rule_t time_human_duration_rule; + +static time_human_duration_rule const g_time_human_duration_rules[] = { + { + .check_human_duration = check_human_duration_less_1_sec, + .gen_human_duration = gen_human_duration_less_1_sec, + }, + { + .check_human_duration = check_human_duration_less_60_secs, + .gen_human_duration = gen_human_duration_less_60_secs, + }, + { + .check_human_duration = check_human_duration_eq_1_min, + .gen_human_duration = gen_human_duration_eq_1_min, + }, + { + .check_human_duration = check_human_duration_less_60_mins, + .gen_human_duration = gen_human_duration_less_60_mins, + }, + { + .check_human_duration = check_human_duration_eq_1_hour, + .gen_human_duration = gen_human_duration_eq_1_hour, + }, + { + .check_human_duration = check_human_duration_less_48_hours, + .gen_human_duration = gen_human_duration_less_48_hours, + }, + { + .check_human_duration = check_human_duration_less_7_days, + .gen_human_duration = gen_human_duration_less_7_days, + }, + { + .check_human_duration = check_human_duration_less_90_days, + .gen_human_duration = gen_human_duration_less_90_days, + }, + { + .check_human_duration = check_human_duration_less_2_years, + .gen_human_duration = gen_human_duration_less_2_years, + }, + { + .check_human_duration = check_human_duration_default, + .gen_human_duration = gen_human_duration_default, + }, +}; + +static bool time_human_duration(int64_t seconds, char *str, size_t len) +{ +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + int nret = 0; + size_t i; + + if (seconds == 0 || str == NULL || len == 0) { + return false; + } + + for (i = 0; i < ARRAY_SIZE(g_time_human_duration_rules); i++) { + if (g_time_human_duration_rules[i].check_human_duration(seconds)) { + nret = g_time_human_duration_rules[i].gen_human_duration(seconds, str, len); + break; + } + } + + if (nret < 0) { + ERROR("Sprintf buffer failed"); + return false; + } + + return true; +} + +static int time_format_duration_bad(char *out, size_t len) +{ + if (sprintf_s(out, len, "-") < 0) { + return -1; /* format failed, return -1 */ + } + return 1; /* format ok with bad data, return 1 */ +} + +static int time_format_duration_ago(char *out, size_t len) +{ + if (strcmp(out, "-") != 0 && strlen(out) + 5 < len) { + if (strcat_s(out, len, " ago") != EOK) { + ERROR("Strcat string error"); + return -1; + } + } + + return 0; +} + +int time_format_duration(const char *in, char *out, size_t len) +{ + int32_t nanos = 0; + int64_t result = 0; + struct tm tm = { 0 }; + struct tm *currentm = NULL; + struct types_timezone tz = { 0 }; + time_t currentime = { 0 }; + + if (out == NULL) { + return -1; + } + + if (in == NULL || !strcmp(in, defaultContainerTime) || !strcmp(in, "-")) { + return time_format_duration_bad(out, len); + } + + if (!get_tm_zone_from_str(in, &tm, &nanos, &tz)) { + return time_format_duration_bad(out, len); + } + + time(¤time); + currentm = gmtime(¤time); + if (currentm == NULL) { + ERROR("Get time error"); + return -1; + } + + result = get_minmus_time(currentm, &tm); + result = result + (int64_t)tz.hour * 3600 + (int64_t)tz.min * 60; + + if (result < 0 || !time_human_duration(result, out, len)) { + return time_format_duration_bad(out, len); + } + + return time_format_duration_ago(out, len); +} + +int time_tz_to_seconds_nanos(const char *time_tz, int64_t *seconds, int32_t *nanos) +{ + int nret = 0; + struct tm t = { 0 }; + int32_t nano = 0; + char *time_str = NULL; + + if (seconds != NULL) { + *seconds = 0; + } + if (nanos != NULL) { + *nanos = 0; + } + if (time_tz == NULL) { + return 0; + } + + if (!util_valid_time_tz(time_tz)) { + ERROR("invalid time %s", time_tz); + return -1; + } + + /* translate to rfc339NanoLocal */ + time_str = util_strdup_s(time_tz); + time_str[strlen(time_str) - 1] = 0; /* strip last 'Z' */ + + if (!get_tm_from_str(time_str, &t, &nano)) { + ERROR("get tm from string %s failed", time_str); + nret = -1; + goto err_out; + } + + if (seconds != NULL) { + *seconds = timegm(&t); + } + + if (nanos != NULL) { + *nanos = nano; + } + +err_out: + free(time_str); + return nret; +} + +int to_unix_nanos_from_str(const char *str, int64_t *nanos) +{ + struct tm tm = { 0 }; + struct types_timezone tz; + int32_t nano = 0; + types_timestamp_t ts; + + if (nanos == NULL) { + return -1; + } + + *nanos = 0; + if (str == NULL || !strcmp(str, "") || !strcmp(str, defaultContainerTime)) { + return 0; + } + + if (str[strlen(str) - 1] == 'Z') { + int ret = time_tz_to_seconds_nanos(str, &ts.seconds, &ts.nanos); + if (ret != 0) { + ERROR("Invalid time stamp: %s", str); + return -1; + } + *nanos = ts.seconds * Time_Second + ts.nanos; + return 0; + } + + if (!get_tm_zone_from_str(str, &tm, &nano, &tz)) { + ERROR("Transform str to timestamp failed"); + return -1; + } + *nanos = mktime(&tm) * Time_Second + nano; + return 0; +} + diff --git a/src/types_def.h b/src/types_def.h new file mode 100644 index 0000000..a0ab9fa --- /dev/null +++ b/src/types_def.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: tanyifeng + * Create: 2018-11-1 + * Description: provide typedef functions definition + ********************************************************************************/ +#ifndef _TYPES_DEF_H_ +#define _TYPES_DEF_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct types_timestamp { + uint32_t has_seconds; + int64_t seconds; + uint32_t has_nanos; + int32_t nanos; +} types_timestamp_t; + +struct types_timezone { + int hour; + int min; +}; + +int64_t time_seconds_since(const char *in); + +int types_timestamp_cmp(const types_timestamp_t *t1, const types_timestamp_t *t2); + +bool get_timestamp(const char *str_time, types_timestamp_t *timestamp); + +bool get_time_buffer(const types_timestamp_t *timestamp, char *timebuffer, size_t maxsize); + +bool get_now_time_stamp(types_timestamp_t *timestamp); + +bool get_now_time_buffer(char *timebuffer, size_t maxsize); + +int get_time_interval(types_timestamp_t first, types_timestamp_t last, int64_t *result); + +int time_tz_to_seconds_nanos(const char *time_tz, int64_t *seconds, int32_t *nanos); + +int to_unix_nanos_from_str(const char *str, int64_t *nanos); + +bool parsing_time(const char *format, const char *time, struct tm *tm, int32_t *nanos); + +bool fix_date(struct tm *tm); + +bool get_tm_from_str(const char *str, struct tm *tm, int32_t *nanos); + +int time_format_duration(const char *in, char *out, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/websocket/CMakeLists.txt b/src/websocket/CMakeLists.txt new file mode 100644 index 0000000..db71452 --- /dev/null +++ b/src/websocket/CMakeLists.txt @@ -0,0 +1,4 @@ +# get current directory sources files +add_subdirectory(service) +set(WEBSOCKET_SERVICE_SRCS ${WB_SERVICE_SRCS} PARENT_SCOPE) +set(WEBSOCKET_SERVICE_INCS ${WB_SERVICE_INCS} PARENT_SCOPE) diff --git a/src/websocket/service/CMakeLists.txt b/src/websocket/service/CMakeLists.txt new file mode 100644 index 0000000..73c2c83 --- /dev/null +++ b/src/websocket/service/CMakeLists.txt @@ -0,0 +1,5 @@ +# get current directory sources files +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} service_comm_srcs) + +set(WB_SERVICE_SRCS ${service_comm_srcs} PARENT_SCOPE) +set(WB_SERVICE_INCS ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) diff --git a/src/websocket/service/attach_serve.cc b/src/websocket/service/attach_serve.cc new file mode 100644 index 0000000..ee68f0f --- /dev/null +++ b/src/websocket/service/attach_serve.cc @@ -0,0 +1,91 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide container attach functions + ******************************************************************************/ + + +#include "attach_serve.h" +#include "utils.h" + +int AttachServe::Execute(struct lws *wsi, const std::string &token, + int read_pipe_fd) +{ + RequestCache *cache = RequestCache::GetInstance(); + bool found = false; + auto cachedRequest = cache->Consume(token, found); + if (!found) { + ERROR("invalid token :%s", token.c_str()); + return -1; + } + runtime::AttachRequest *request = dynamic_cast(cachedRequest); + if (request == nullptr) { + ERROR("failed to get exec request!"); + return -1; + } + + container_attach_request *container_req = nullptr; + container_attach_response *container_res = nullptr; + + service_callback_t *cb = get_service_callback(); + if (cb == nullptr || cb->container.attach == nullptr) { + return -1; + } + int tret = 0; + tret = RequestFromCri(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request!"); + return -1; + } + struct io_write_wrapper stringWriter = { 0 }; + stringWriter.context = (void *)wsi; + stringWriter.write_func = WsWriteToClient; + stringWriter.close_func = closeWsConnect; + container_req->attach_stderr = false; + int ret = cb->container.attach(container_req, &container_res, read_pipe_fd, &stringWriter, nullptr); + free_container_attach_request(container_req); + free_container_attach_response(container_res); + + if (request != nullptr) { + delete request; + request = nullptr; + } + if (tret != 0) { + ERROR("Failed to translate response to grpc, operation is %s", ret ? "failed" : "success"); + } + + return ret; +} + +int AttachServe::RequestFromCri(const runtime::AttachRequest *grequest, + container_attach_request **request) +{ + container_attach_request *tmpreq = nullptr; + + tmpreq = (container_attach_request *)util_common_calloc_s(sizeof(container_attach_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + if (!grequest->container_id().empty()) { + tmpreq->container_id = util_strdup_s(grequest->container_id().c_str()); + } + tmpreq->attach_stdin = grequest->stdin(); + tmpreq->attach_stdout = grequest->stdout(); + tmpreq->attach_stderr = grequest->stderr(); + + *request = tmpreq; + + return 0; +} + diff --git a/src/websocket/service/attach_serve.h b/src/websocket/service/attach_serve.h new file mode 100644 index 0000000..413c736 --- /dev/null +++ b/src/websocket/service/attach_serve.h @@ -0,0 +1,37 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: Attach streaming service implementation. + * Author: wujing + * Create: 2019-01-02 + ******************************************************************************/ + +#ifndef __ATTACH_SERVE_H_ +#define __ATTACH_SERVE_H_ + +#include "route_callback_register.h" +#include +#include +#include +#include "ws_server.h" + +#include "api.pb.h" +#include "log.h" +#include "callback.h" +#include "request_cache.h" + +class AttachServe : public StreamingServeInterface { +public: + int Execute(struct lws *wsi, const std::string &token, int read_pipe_fd) override; +private: + int RequestFromCri(const runtime::AttachRequest *grequest, + container_attach_request **request); +}; +#endif /*__ATTACH_SERVE_H_*/ diff --git a/src/websocket/service/exec_serve.cc b/src/websocket/service/exec_serve.cc new file mode 100644 index 0000000..751d6e5 --- /dev/null +++ b/src/websocket/service/exec_serve.cc @@ -0,0 +1,118 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide ExecServe functions + ******************************************************************************/ + +#include "exec_serve.h" +#include "console.h" +#include "utils.h" + +int ExecServe::Execute(struct lws *wsi, const std::string &token, + int read_pipe_fd) +{ + RequestCache *cache = RequestCache::GetInstance(); + bool found = false; + auto cachedRequest = cache->Consume(token, found); + if (!found) { + ERROR("invalid token :%s", token.c_str()); + return -1; + } + runtime::ExecRequest *request = dynamic_cast(cachedRequest); + if (request == nullptr) { + ERROR("failed to get exec request!"); + return -1; + } + + container_exec_request *container_req = nullptr; + container_exec_response *container_res = nullptr; + + service_callback_t *cb = get_service_callback(); + if (cb == nullptr || cb->container.exec == nullptr) { + return -1; + } + int tret = RequestFromCri(request, &container_req); + if (tret != 0) { + ERROR("Failed to transform grpc request!"); + return -1; + } + struct io_write_wrapper stringWriter = { 0 }; + stringWriter.context = (void *)wsi; + stringWriter.write_func = WsWriteToClient; + int ret = cb->container.exec(container_req, &container_res, read_pipe_fd, &stringWriter); + if (ret != 0) { + std::string message; + if (container_res != nullptr && container_res->errmsg != nullptr) { + message = container_res->errmsg; + } else { + message = "Failed to call exec container callback. "; + } + WsWriteToClient(wsi, message.c_str(), message.length()); + } + if (container_res != nullptr && container_res->exit_code != 0) { + std::string exit_info = "Exit code :" + std::to_string((int)container_res->exit_code) + "\n"; + WsWriteToClient(wsi, exit_info.c_str(), exit_info.length()); + } + free_container_exec_request(container_req); + free_container_exec_response(container_res); + if (request != nullptr) { + delete request; + request = nullptr; + } + + (void)closeWsConnect((void *)wsi, nullptr); + + return ret; +} + +int ExecServe::RequestFromCri(const runtime::ExecRequest *grequest, + container_exec_request **request) +{ + container_exec_request *tmpreq = nullptr; + + tmpreq = (container_exec_request *)util_common_calloc_s(sizeof(container_exec_request)); + if (tmpreq == nullptr) { + ERROR("Out of memory"); + return -1; + } + + tmpreq->tty = grequest->tty(); + tmpreq->attach_stdin = grequest->stdin(); + tmpreq->attach_stdout = grequest->stdout(); + tmpreq->attach_stderr = grequest->stderr(); + + if (!grequest->container_id().empty()) { + tmpreq->container_id = util_strdup_s(grequest->container_id().c_str()); + } + + if (grequest->cmd_size() > 0) { + if ((size_t)grequest->cmd_size() > SIZE_MAX / sizeof(char *)) { + ERROR("Too many arguments!"); + free_container_exec_request(tmpreq); + return -1; + } + tmpreq->argv = (char **)util_common_calloc_s(sizeof(char *) * grequest->cmd_size()); + if (tmpreq->argv == nullptr) { + ERROR("Out of memory!"); + free_container_exec_request(tmpreq); + return -1; + } + for (int i = 0; i < grequest->cmd_size(); i++) { + tmpreq->argv[i] = util_strdup_s(grequest->cmd(i).c_str()); + } + tmpreq->argv_len = (size_t)grequest->cmd_size(); + } + *request = tmpreq; + return 0; +} + + diff --git a/src/websocket/service/exec_serve.h b/src/websocket/service/exec_serve.h new file mode 100644 index 0000000..2b4ab12 --- /dev/null +++ b/src/websocket/service/exec_serve.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: Exec streaming service implementation. + * Author: wujing + * Create: 2019-01-02 + ******************************************************************************/ + +#ifndef __EXEC_SERVE_H_ +#define __EXEC_SERVE_H_ + +#include +#include +#include +#include +#include +#include "api.grpc.pb.h" +#include "container.grpc.pb.h" + +#include "route_callback_register.h" +#include "log.h" +#include "callback.h" +#include "ws_server.h" +#include "request_cache.h" +#include "api.pb.h" + +class ExecServe : public StreamingServeInterface { +public: + int Execute(struct lws *wsi, const std::string &token, int read_pipe_fd) override; +private: + int RequestFromCri(const runtime::ExecRequest *grequest, + container_exec_request **request); +}; +#endif /*__EXEC_SERVE_H_*/ diff --git a/src/websocket/service/route_callback_register.h b/src/websocket/service/route_callback_register.h new file mode 100644 index 0000000..dd86e73 --- /dev/null +++ b/src/websocket/service/route_callback_register.h @@ -0,0 +1,83 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: Streaming service function registration. + * Author: wujing + * Create: 2019-01-02 + ******************************************************************************/ + +#ifndef __ROUTE_CALLBACK_REGISTER_H_ +#define __ROUTE_CALLBACK_REGISTER_H_ +#include +#include +#include +#include +#include +#include +#include "log.h" +class StreamingServeInterface { +public: + virtual int Execute(struct lws *wsi, const std::string &token, int read_pipe_fd) = 0; +}; + +class RouteCallbackRegister { +public: + bool IsValidMethod(const std::string &method) + { + return static_cast(m_registeredcallbacks.count(method)); + } + + int HandleCallback(struct lws *wsi, const std::string &method, + const std::string &token, + int read_pipe_fd) + { + auto it = m_registeredcallbacks.find(method); + if (it != m_registeredcallbacks.end()) { + std::shared_ptr callback = it->second; + if (callback) { + return callback->Execute(wsi, token, read_pipe_fd); + } + } + ERROR("invalid method!"); + return -1; + } + void RegisterCallback(const std::string &path, + std::shared_ptr callback) + { + m_registeredcallbacks.insert(std::pair>(path, callback)); + } + +private: + std::map> m_registeredcallbacks; +}; + +class StreamTask { +public: + StreamTask(RouteCallbackRegister *invoker, struct lws *wsi, + const std::string &method, + const std::string &token, int read_pipe_fd) + : m_invoker(invoker), m_wsi(wsi), m_method(method), m_token(token), + m_read_pipe_fd(read_pipe_fd) {} + ~StreamTask() = default; + int Run() + { + return m_invoker->HandleCallback(m_wsi, m_method, m_token, m_read_pipe_fd); + } +private: + RouteCallbackRegister *m_invoker{ nullptr }; + struct lws *m_wsi; + std::string m_method; + std::string m_token; + int m_read_pipe_fd; +}; + +#endif /*__ROUTE_CALLBACK_REGISTER_H_*/ + diff --git a/src/websocket/service/stream_server.cc b/src/websocket/service/stream_server.cc new file mode 100644 index 0000000..994a82f --- /dev/null +++ b/src/websocket/service/stream_server.cc @@ -0,0 +1,41 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide websocket server functions + ******************************************************************************/ + +#include "stream_server.h" +#include +#include +#include "ws_server.h" +#include "exec_serve.h" +#include "attach_serve.h" + +void websocket_server_init(Errors &err) +{ + WebsocketServer *server = WebsocketServer::GetInstance(); + server->RegisterCallback(std::string("exec"), std::make_shared()); + server->RegisterCallback(std::string("attach"), std::make_shared()); + server->Start(err); +} + +void websocket_server_wait(void) +{ + WebsocketServer *server = WebsocketServer::GetInstance(); + server->Wait(); +} + +void websocket_server_shutdown(void) +{ + WebsocketServer *server = WebsocketServer::GetInstance(); + server->Shutdown(); +} diff --git a/src/websocket/service/stream_server.h b/src/websocket/service/stream_server.h new file mode 100644 index 0000000..99dbedb --- /dev/null +++ b/src/websocket/service/stream_server.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide websocket stream service definition + ******************************************************************************/ +#ifndef __WEBSOCKET_STREAM_SERVICE_H +#define __WEBSOCKET_STREAM_SERVICE_H +#include "errors.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void websocket_server_init(Errors &err); + +void websocket_server_wait(void); + +void websocket_server_shutdown(void); + +#ifdef __cplusplus +} +#endif + +#endif /*__WEBSOCKET_STREAM_SERVICE_H*/ + diff --git a/src/websocket/service/ws_server.cc b/src/websocket/service/ws_server.cc new file mode 100644 index 0000000..281a019 --- /dev/null +++ b/src/websocket/service/ws_server.cc @@ -0,0 +1,484 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Author: lifeng + * Create: 2018-11-08 + * Description: provide websocket server functions + ******************************************************************************/ + +#include "ws_server.h" +#include +#include +#include +#include +#include +#include +#include "cxxutils.h" +#include "log.h" +#include "utils.h" +#include "request_cache.h" +#include "constants.h" + +struct lws_context *WebsocketServer::m_context = nullptr; +std::atomic WebsocketServer::m_instance; +std::mutex WebsocketServer::m_mutex; +std::map WebsocketServer::m_wsis; +WebsocketServer *WebsocketServer::GetInstance() noexcept +{ + WebsocketServer *server = m_instance.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + if (server == nullptr) { + std::lock_guard lock(m_mutex); + server = m_instance.load(std::memory_order_relaxed); + if (server == nullptr) { + server = new WebsocketServer; + std::atomic_thread_fence(std::memory_order_release); + m_instance.store(server, std::memory_order_relaxed); + } + } + return server; +} + +WebsocketServer::WebsocketServer() +{ + m_force_exit = 0; + m_wsis.clear(); +} + +WebsocketServer::~WebsocketServer() +{ + Shutdown(); +} + +url::URLDatum WebsocketServer::GetWebsocketUrl() +{ + return m_url; +} + +std::map &WebsocketServer::GetWsisData() +{ + return m_wsis; +} + +void WebsocketServer::LockAllWsSession() +{ + m_mutex.lock(); +} + +void WebsocketServer::UnlockAllWsSession() +{ + m_mutex.unlock(); +} + +void WebsocketServer::Shutdown() +{ + m_force_exit = 1; + lws_cancel_service(m_context); +} + +int WebsocketServer::InitRWPipe(int read_fifo[]) +{ + if ((pipe2(read_fifo, O_NONBLOCK | O_CLOEXEC)) < 0) { + ERROR("create read pipe(websocket server to lxc pipe) fail!"); + return -1; + } + return 0; +} + +void WebsocketServer::EmitLog(int level, const char *line) +{ + switch (level) { + case LLL_ERR: + ERROR("ws:%s", line); + break; + default: + DEBUG("ws:%s", line); + break; + } +} + +int WebsocketServer::CreateContext() +{ + unsigned int opts = 0; + int limited; + struct lws_context_creation_info info; + struct rlimit oldLimit, newLimit; + const size_t WS_ULIMIT_FDS = 1024; + char interface[] = "127.0.0.1"; + + m_url.SetScheme("ws"); + m_url.SetHost("localhost:" + std::to_string(m_listenPort)); + if (memset_s(&info, sizeof(struct lws_context_creation_info), 0, sizeof(info)) != EOK) { + ERROR("Failed to set memory!"); + return -1; + } + lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG, WebsocketServer::EmitLog); + + info.port = m_listenPort; + info.iface = interface; + info.protocols = m_protocols; + info.ssl_cert_filepath = nullptr; + info.ssl_private_key_filepath = nullptr; + info.gid = -1; + info.uid = -1; + info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8; + info.max_http_header_pool = MAX_HTTP_HEADER_POOL; + info.extensions = nullptr; + + /* daemon set RLIMIT_NOFILE to a large value at main.c, + * belowing lws_create_context limit the fds of websocket to RLIMIT_NOFILE, + * and malloced memory according to it. To reduce memory, we recover it to 1024 before create m_context. + */ + newLimit.rlim_cur = WS_ULIMIT_FDS; + newLimit.rlim_max = WS_ULIMIT_FDS; + limited = prlimit(0, RLIMIT_NOFILE, &newLimit, &oldLimit); + if (limited != 0) { + WARN("Can not set ulimit of RLIMIT_NOFILE: %s", strerror(errno)); + } + m_context = lws_create_context(&info); + if (m_context == nullptr) { + ERROR("libwebsocket create m_context failed!"); + return -1; + } + if (limited == 0) { + if (setrlimit((int)RLIMIT_NOFILE, &oldLimit) != 0) { + WARN("Can not set ulimit of RLIMIT_NOFILE: %s", strerror(errno)); + } + } + + return 0; +} + +void WebsocketServer::RegisterCallback(const std::string &path, + std::shared_ptr callback) +{ + m_handler.RegisterCallback(path, callback); +} + +void WebsocketServer::CloseAllWsSession() +{ + std::lock_guard lock(m_mutex); + for (auto it = m_wsis.begin(); it != m_wsis.end(); ++it) { + free(it->second.buf); + close(it->second.pipes.at(0)); + close(it->second.pipes.at(1)); + it->second.sended = true; + delete it->second.buf_mutex; + delete it->second.sended_mutex; + } + m_wsis.clear(); +} + +void WebsocketServer::CloseWsSession(struct lws *wsi) +{ + const int WAIT_PERIOD_MS = 50; + + auto it = m_wsis.find(wsi); + if (it != m_wsis.end()) { + while (it->second.GetProcessingStatus()) { + std::this_thread::sleep_for(std::chrono::milliseconds(WAIT_PERIOD_MS)); + } + free(it->second.buf); + close(it->second.pipes.at(0)); + close(it->second.pipes.at(1)); + it->second.sended = true; + delete it->second.buf_mutex; + delete it->second.sended_mutex; + m_wsis.erase(it); + } +} + +int WebsocketServer::DumpHandshakeInfo(struct lws *wsi) noexcept +{ + int read_pipe_fd[PIPE_FD_NUM]; + if (InitRWPipe(read_pipe_fd) < 0) { + ERROR("failed to init read/write pipe!"); + } + + session_data session; + session.pipes = std::array { read_pipe_fd[0], read_pipe_fd[1] }; + m_wsis.insert(std::make_pair(wsi, session)); + m_wsis[wsi].buf = (unsigned char *)util_common_calloc_s(LWS_PRE + MAX_MSG_BUFFER_SIZE + 1); + if (m_wsis[wsi].buf == nullptr) { + ERROR("Out of memory"); + return -1; + } + m_wsis[wsi].buf_mutex = new std::mutex; + m_wsis[wsi].sended_mutex = new std::mutex; + m_wsis[wsi].SetProcessingStatus(false); + + int len; + char buf[MAX_BUF_LEN] { 0 }; + + lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_GET_URI); + if (strlen(buf) == 0) { + lws_close_reason(wsi, LWS_CLOSE_STATUS_PROTOCOL_ERR, (unsigned char *)("Invalid URL"), strlen("Invalid URL")); + return -1; + } + + buf[sizeof(buf) - 1] = '\0'; + // format: "/cri/" + method + "/" + token + "/" + arg(container=cmd) + auto vec = CXXUtils::Split(buf + 1, '/'); + RequestCache *cache = RequestCache::GetInstance(); + if (vec.size() < MIN_VEC_SIZE || + !m_handler.IsValidMethod(vec.at(1)) || + !cache->IsValidToken(vec.at(2))) { + ERROR("invalid url(%s): incorrect format!", buf); + CloseWsSession(wsi); + return -1; + } + + std::thread streamTh([ = ]() { + StreamTask(&m_handler, wsi, vec.at(1), vec.at(2), m_wsis[wsi].pipes.at(0)).Run(); + }); + streamTh.detach(); + int n = 0; + const unsigned char *c = nullptr; + do { + c = lws_token_to_string((lws_token_indexes)n); + if (c == nullptr) { + n++; + continue; + } + len = lws_hdr_total_length(wsi, (lws_token_indexes)n); + if (len == 0 || ((size_t)len > sizeof(buf) - 1)) { + n++; + continue; + } + lws_hdr_copy(wsi, buf, sizeof(buf), (lws_token_indexes)n); + buf[sizeof(buf) - 1] = '\0'; + DEBUG(" %s = %s", (char *)c, buf); + n++; + } while (c != nullptr); + + return 0; +} + +int WebsocketServer::Wswrite(struct lws *wsi, void *in, size_t len) +{ + auto it = m_wsis.find(wsi); + if (it != m_wsis.end()) { + if (it->second.close) { + const std::string closeMsg = "websocket session disconnected"; + DEBUG(closeMsg.c_str()); + lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY, (unsigned char *)(closeMsg.c_str()), + closeMsg.length()); + return -1; + } + it->second.buf_mutex->lock(); + auto &buf = it->second.buf; + if (strlen((const char *)(&buf[LWS_PRE + 1])) == 0) { + it->second.buf_mutex->unlock(); + return 0; + } + int n = lws_write(wsi, (unsigned char *)(&buf[LWS_PRE]), + strlen((const char *)(&buf[LWS_PRE + 1])) + 1, LWS_WRITE_TEXT); + if (n < 0) { + it->second.buf_mutex->unlock(); + ERROR("ERROR %d writing to socket, hanging up", n); + return -1; + } + if (memset_s(buf, LWS_PRE + MAX_MSG_BUFFER_SIZE + 1, + 0, LWS_PRE + MAX_MSG_BUFFER_SIZE + 1) != EOK) { + ERROR("Failed to set memory"); + } + it->second.buf_mutex->unlock(); + } + + return 0; +} + +void WebsocketServer::Receive(struct lws *wsi, void *user, void *in, size_t len) +{ + if (user != nullptr) { + struct per_session_data__echo *pss = (struct per_session_data__echo *)user; + pss->final = lws_is_final_fragment(wsi); + pss->binary = lws_frame_is_binary(wsi); + + if (memcpy_s(&pss->buf[LWS_PRE], MAX_ECHO_PAYLOAD, in, len) != EOK) { + ERROR("failed to copy memory!"); + return; + } + pss->len = (unsigned int)len; + pss->rx += len; + lws_rx_flow_control(wsi, 0); + } + if (m_wsis.find(wsi) == m_wsis.end()) { + ERROR("invailed websocket session!"); + return; + } + + if (*static_cast(in) != WebsocketChannel::STDINCHANNEL) { + ERROR("recevice date from client: %s", (char *)in + 1); + return; + } + + if (write(m_wsis[wsi].pipes.at(1), (void *)((char *)in + 1), len - 1) < 0) { + ERROR("sub write over!"); + return; + } +} + +void WebsocketServer::SetLwsSendedFlag(struct lws *wsi, bool sended) +{ + auto it = m_wsis.find(wsi); + if (it != m_wsis.end()) { + it->second.sended_mutex->lock(); + it->second.sended = sended; + it->second.sended_mutex->unlock(); + } +} + +int WebsocketServer::Callback(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + case LWS_CALLBACK_HTTP: + // disable an http request which has come from a client that is not + // asking to upgrade the connection to a websocket one. + return -1; + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: { + std::lock_guard lock(m_mutex); + if (WebsocketServer::GetInstance()->DumpHandshakeInfo(wsi)) { + // return non-zero here and kill the connection + return -1; + } + } + break; + case LWS_CALLBACK_ESTABLISHED: { + DEBUG("new connection has been established"); + } + break; + case LWS_CALLBACK_SERVER_WRITEABLE: { + std::lock_guard lock(m_mutex); + if (WebsocketServer::GetInstance()->Wswrite(wsi, in, len)) { + WebsocketServer::GetInstance()->SetLwsSendedFlag(wsi, true); + return -1; + } + WebsocketServer::GetInstance()->SetLwsSendedFlag(wsi, true); + lws_rx_flow_control(wsi, 1); + } + break; + case LWS_CALLBACK_RECEIVE: { + std::lock_guard lock(m_mutex); + WebsocketServer::GetInstance()->Receive(wsi, nullptr, (char *)in, len); + lws_rx_flow_control(wsi, 0); + } + break; + case LWS_CALLBACK_CLOSED: { + std::lock_guard lock(m_mutex); + WebsocketServer::GetInstance()->CloseWsSession(wsi); + } + break; + default: + break; + } + return 0; +} + +void WebsocketServer::ServiceWorkThread(int threadid) +{ + int n = 0; + while (n >= 0 && !m_force_exit) { + n = lws_service(m_context, 50); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +void WebsocketServer::Start(Errors &err) +{ + if (CreateContext() < 0) { + err.SetError("Websocket server start failed!, please check your network status" + "(eg: port " + std::to_string(m_listenPort) + "is occupied)"); + return; + } + m_pthread_service = std::thread(&WebsocketServer::ServiceWorkThread, this, 0); +} + +void WebsocketServer::Wait() +{ + if (m_pthread_service.joinable()) { + m_pthread_service.join(); + } + + CloseAllWsSession(); + + lws_context_destroy(m_context); +} + +ssize_t WsWriteToClient(void *context, const void *data, size_t len) +{ + const int RETRIES = 10; + const int CHECK_PERIOD_SECOND = 1; + const int TRIGGER_PERIOD_MS = 100; + + struct lws *wsi = static_cast(context); + WebsocketServer *server = WebsocketServer::GetInstance(); + server->LockAllWsSession(); + auto it = server->GetWsisData().find(wsi); + if (it == server->GetWsisData().end()) { + ERROR("invalid session!"); + server->UnlockAllWsSession(); + return 0; + } + it->second.SetProcessingStatus(true); + server->UnlockAllWsSession(); + server->SetLwsSendedFlag(wsi, false); + it->second.buf_mutex->lock(); + auto &buf = it->second.buf; + // Determine if it is standard output channel or error channel? + if (memset_s(buf, LWS_PRE + MAX_MSG_BUFFER_SIZE + 1, + 0, LWS_PRE + MAX_MSG_BUFFER_SIZE + 1) != EOK) { + ERROR("Failed to set memory"); + } + buf[LWS_PRE] = STDOUTCHANNEL; + if (memcpy_s(&buf[LWS_PRE + 1], MAX_MSG_BUFFER_SIZE, (void *)data, len) != EOK) { + ERROR("failed to copy memory!"); + return 0; + } + auto start = std::chrono::system_clock::now(); + lws_callback_on_writable(wsi); + it->second.buf_mutex->unlock(); + int count = 0; + while (!it->second.sended && count < RETRIES) { + auto end = std::chrono::system_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + double spend_time = static_cast(duration.count()) * std::chrono::microseconds::period::num / + std::chrono::microseconds::period::den; + if (spend_time > CHECK_PERIOD_SECOND) { + lws_callback_on_writable(wsi); + std::this_thread::sleep_for(std::chrono::milliseconds(TRIGGER_PERIOD_MS)); + start = std::chrono::system_clock::now(); + count++; + } + std::this_thread::sleep_for(std::chrono::milliseconds(TRIGGER_PERIOD_MS)); + } + it->second.SetProcessingStatus(false); + return (ssize_t)len; +} + +int closeWsConnect(void *context, char **err) +{ + (void)err; + struct lws *wsi = static_cast(context); + + WebsocketServer *server = WebsocketServer::GetInstance(); + auto it = server->GetWsisData().find(wsi); + if (it == server->GetWsisData().end()) { + ERROR("websocket session not exist"); + return -1; + } + it->second.close = true; + // close websocket session + lws_callback_on_writable(wsi); + return 0; +} + + diff --git a/src/websocket/service/ws_server.h b/src/websocket/service/ws_server.h new file mode 100644 index 0000000..1a67baa --- /dev/null +++ b/src/websocket/service/ws_server.h @@ -0,0 +1,126 @@ +/****************************************************************************** + * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: websockets server implementation + * Author: wujing + * Create: 2019-01-02 + ******************************************************************************/ + +#ifndef __WEBSOCKET_SERVER_H_ +#define __WEBSOCKET_SERVER_H_ +#include +#include +#include +#include +#include +#include +#include +#include +#include "route_callback_register.h" +#include "url.h" +#include "errors.h" + +#define MAX_ECHO_PAYLOAD 1024 +#define MAX_ARRAY_LEN 2 +#define MAX_BUF_LEN 256 +#define MAX_PROTOCOL_NUM 2 +#define MAX_HTTP_HEADER_POOL 8 +#define MIN_VEC_SIZE 3 +#define PIPE_FD_NUM 2 +#define BUF_BASE_SIZE 1024 +#define LWS_TIMEOUT 50 + +struct per_session_data__echo { + size_t rx, tx; + unsigned char buf[LWS_PRE + MAX_ECHO_PAYLOAD + 1]; + unsigned int len; + unsigned int index; + int final; + int continuation; + int binary; +}; + +enum WebsocketChannel { + STDINCHANNEL = 0, + STDOUTCHANNEL, + STDERRCHANNEL +}; + +struct session_data { + std::array pipes; + unsigned char *buf; + volatile bool sended { false }; + volatile bool close { false }; + volatile bool in_processing { false }; + std::mutex *buf_mutex; + std::mutex *sended_mutex; + + void SetProcessingStatus(bool status) + { + in_processing = status; + } + bool GetProcessingStatus() const + { + return in_processing; + } +}; + +class WebsocketServer { +public: + static WebsocketServer *GetInstance() noexcept; + static std::atomic m_instance; + void Start(Errors &err); + void Wait(); + void Shutdown(); + void RegisterCallback(const std::string &path, std::shared_ptr callback); + url::URLDatum GetWebsocketUrl(); + std::map &GetWsisData(); + void SetLwsSendedFlag(struct lws *wsi, bool sended); + void LockAllWsSession(); + void UnlockAllWsSession(); + +private: + WebsocketServer(); + WebsocketServer(const WebsocketServer &) = delete; + WebsocketServer &operator=(const WebsocketServer &) = delete; + ~WebsocketServer(); + int InitRWPipe(int read_fifo[]); + std::vector split(std::string str, char r); + static void EmitLog(int level, const char *line); + int CreateContext(); + inline void Receive(struct lws *client, void *user, void *in, size_t len); + int Wswrite(struct lws *wsi, void *in, size_t len); + inline int DumpHandshakeInfo(struct lws *wsi) noexcept; + static int Callback(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); + void ServiceWorkThread(int threadid); + void CloseWsSession(struct lws *wsi); + void CloseAllWsSession(); + +private: + static std::mutex m_mutex; + static struct lws_context *m_context; + volatile int m_force_exit = 0; + std::thread m_pthread_service; + const struct lws_protocols m_protocols[MAX_PROTOCOL_NUM] = { + { "channel.k8s.io", Callback, sizeof(struct per_session_data__echo), MAX_ECHO_PAYLOAD, }, + { NULL, NULL, 0, 0 } + }; + RouteCallbackRegister m_handler; + static std::map m_wsis; + url::URLDatum m_url; + int m_listenPort = 10251; +}; + +ssize_t WsWriteToClient(void *context, const void *data, size_t len); +int closeWsConnect(void *context, char **err); + +#endif /*__WEBSOCKET_SERVER_H_*/ + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..9bf54af --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,8 @@ +project(iSulad_LLT) + +# setup testing +find_package(Threads REQUIRED) +find_package(GTest REQUIRED) +include_directories(${GTEST_INCLUDE_DIR}) + +add_subdirectory(cutils) diff --git a/test/cutils/CMakeLists.txt b/test/cutils/CMakeLists.txt new file mode 100644 index 0000000..f1ea941 --- /dev/null +++ b/test/cutils/CMakeLists.txt @@ -0,0 +1,3 @@ +project(iSulad_LLT) + +add_subdirectory(utils_string) diff --git a/test/cutils/utils_string/CMakeLists.txt b/test/cutils/utils_string/CMakeLists.txt new file mode 100644 index 0000000..81c2c17 --- /dev/null +++ b/test/cutils/utils_string/CMakeLists.txt @@ -0,0 +1,26 @@ +project(iSulad_LLT) + +SET(EXE utils_string_llt) + +add_executable(${EXE} + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/log.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/cutils/utils_string.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/cutils/utils.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/cutils/utils_array.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/cutils/utils_file.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/cutils/utils_convert.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/cutils/utils_verify.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/sha256/sha256.c + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/path.c + ${CMAKE_BINARY_DIR}/json/json_common.c + utils_string_llt.cc) + +target_include_directories(${EXE} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/../../include + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/sha256 + ${CMAKE_CURRENT_SOURCE_DIR}/../../../src/cutils + ${CMAKE_BINARY_DIR}/json + ) +set_target_properties(${EXE} PROPERTIES LINK_FLAGS "-Wl,--wrap,util_strdup_s -Wl,--wrap,calloc -Wl,--wrap,strcat_s") +target_link_libraries(${EXE} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT} -lyajl -lsecurec -lz) diff --git a/test/cutils/utils_string/utils_string_llt.cc b/test/cutils/utils_string/utils_string_llt.cc new file mode 100644 index 0000000..0e08588 --- /dev/null +++ b/test/cutils/utils_string/utils_string_llt.cc @@ -0,0 +1,806 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2016-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: utils_string llt + * Author: tanyifeng + * Create: 2019-07-08 + */ + +#include +#include +#include +#include +#include "mock.h" +#include "utils_string.h" + +extern "C" { + DECLARE_WRAPPER(util_strdup_s, char *, (const char *str)); + DEFINE_WRAPPER(util_strdup_s, char *, (const char *str), (str)); + + DECLARE_WRAPPER(calloc, void *, (size_t nmemb, size_t size)); + DEFINE_WRAPPER(calloc, void *, (size_t nmemb, size_t size), (nmemb, size)); + + DECLARE_WRAPPER_V(strcat_s, errno_t, (char *strDest, size_t destMax, const char *strSrc)); + DEFINE_WRAPPER_V(strcat_s, errno_t, (char *strDest, size_t destMax, const char *strSrc), + (strDest, destMax, strSrc)); +} + +static int g_strcat_s_cnt = 0; + +static errno_t strcat_s_fail(char *strDest, size_t destMax, const char *strSrc) +{ + return (errno_t)EINVAL; +} + +static errno_t strcat_s_second_fail(char *strDest, size_t destMax, const char *strSrc) +{ + g_strcat_s_cnt++; + if (g_strcat_s_cnt == 1) { + return __real_strcat_s(strDest, destMax, strSrc); + } + return (errno_t)EINVAL; +} + +TEST(utils_string_llt, test_strings_count) +{ + ASSERT_EQ(strings_count("aaaaaaaaaaaaaaaaaaaa", 'a'), 20); + ASSERT_EQ(strings_count("a", 'a'), 1); + ASSERT_EQ(strings_count("", 'a'), 0); + ASSERT_EQ(strings_count(nullptr, 'c'), 0); +} + +TEST(utils_string_llt, test_strings_contains_any) +{ + ASSERT_EQ(strings_contains_any("1234567890abcdefgh!@", "ijklmnopq#123456789"), true); + ASSERT_EQ(strings_contains_any("1234567890abcdefgh!@", "ijklmnopqrstuvw)(*x&-"), false); + ASSERT_EQ(strings_contains_any("1234567890abcdefgh!@", ""), false); + ASSERT_EQ(strings_contains_any("1234567890abcdefgh!@", nullptr), false); + ASSERT_EQ(strings_contains_any("a", "cedefga123415"), true); + ASSERT_EQ(strings_contains_any("", "ijklmnopq#123456789"), false); + ASSERT_EQ(strings_contains_any(nullptr, "ijklmnopq#123456789"), false); +} + + +TEST(utils_string_llt, test_strings_to_lower) +{ + char *result = nullptr; + + std::string str = "AB&^%CDE"; + result = strings_to_lower(str.c_str()); + ASSERT_STRNE(result, nullptr); + ASSERT_STREQ("ab&^%cde", result); + free(result); + + str = "abcdefg12345*()%^#@"; + result = strings_to_lower(str.c_str()); + ASSERT_STRNE(result, nullptr); + ASSERT_STREQ(str.c_str(), result); + free(result); + + str = "aBcDeFg12345*()%^#@"; + result = strings_to_lower(str.c_str()); + ASSERT_STRNE(result, nullptr); + ASSERT_STREQ("abcdefg12345*()%^#@", result); + free(result); + + str = ""; + result = strings_to_lower(str.c_str()); + ASSERT_STRNE(result, nullptr); + ASSERT_STREQ(str.c_str(), result); + free(result); + + result = strings_to_lower(nullptr); + ASSERT_STREQ(result, nullptr); + + + MOCK_SET(util_strdup_s, NULL); + str = "A"; + result = strings_to_lower(str.c_str()); + ASSERT_STREQ(result, NULL); + MOCK_CLEAR(util_strdup_s); +} + +TEST(utils_string_llt, test_strings_to_upper) +{ + char *result = nullptr; + + std::string str = "AB&^%CDE"; + result = strings_to_upper(str.c_str()); + ASSERT_STRNE(result, nullptr); + ASSERT_STREQ(str.c_str(), result); + free(result); + + str = "abcdefg12345*()%^#@"; + result = strings_to_upper(str.c_str()); + ASSERT_STRNE(result, nullptr); + ASSERT_STREQ("ABCDEFG12345*()%^#@", result); + free(result); + + str = "aBcDeFg12345*()%^#@"; + result = strings_to_upper(str.c_str()); + ASSERT_STRNE(result, nullptr); + ASSERT_STREQ("ABCDEFG12345*()%^#@", result); + free(result); + + str = ""; + result = strings_to_upper(str.c_str()); + ASSERT_STRNE(result, nullptr); + ASSERT_STREQ(str.c_str(), result); + free(result); + + result = strings_to_upper(nullptr); + ASSERT_STREQ(result, nullptr); + + MOCK_SET(util_strdup_s, nullptr); + str = "a"; + result = strings_to_upper(str.c_str()); + ASSERT_STREQ(result, nullptr); + MOCK_CLEAR(util_strdup_s); +} + + +TEST(utils_string_llt, test_strings_in_slice) +{ + const char *array_long[] = { "abcd", "1234", nullptr, "", "&^%abc" }; + size_t array_long_len = sizeof(array_long) / sizeof(array_long[0]); + + const char *array_short[] = { "abcd" }; + size_t array_short_len = sizeof(array_short) / sizeof(array_short[0]); + + ASSERT_TRUE(strings_in_slice(array_long, array_long_len, "")); + ASSERT_FALSE(strings_in_slice(array_long, array_long_len, "abc")); + ASSERT_FALSE(strings_in_slice(array_long, array_long_len, nullptr)); + ASSERT_TRUE(strings_in_slice(array_short, array_short_len, "abcd")); + ASSERT_FALSE(strings_in_slice(array_short, array_short_len, "bcd")); + ASSERT_FALSE(strings_in_slice(array_short, array_short_len, nullptr)); + ASSERT_FALSE(strings_in_slice(nullptr, 0, "abcd")); + ASSERT_FALSE(strings_in_slice(nullptr, 0, nullptr)); +} + +TEST(utils_string_llt, test_util_parse_byte_size_string) +{ + int64_t converted = 0; + int ret; + + ret = util_parse_byte_size_string("10.9876B", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 10); + + ret = util_parse_byte_size_string("2048.965kI", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 2098140); + + ret = util_parse_byte_size_string("1.1GiB", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 1181116006); + + ret = util_parse_byte_size_string("2.0tI", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 2199023255552); + + ret = util_parse_byte_size_string("1024mB", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 1073741824); + + ret = util_parse_byte_size_string("10.12a3PIb", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("1234.0a9", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("-10.123", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("-10.0B", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("-10.0GiB", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("-10kI", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("-10tI", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("-10Pib", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("-10.12a3mB", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("0.12345mB", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 129446); + + ret = util_parse_byte_size_string("0.9876543210123456789tI", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 1085937410176); + + ret = util_parse_byte_size_string("0.0kI", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 0); + + ret = util_parse_byte_size_string("0.0Pib", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 0); + + ret = util_parse_byte_size_string("0", &converted); + ASSERT_EQ(ret, 0); + ASSERT_EQ(converted, 0); + + ret = util_parse_byte_size_string("0.123aB", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("0.123aGiB", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("9223372036854775808.123B", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("9007199254740992.0kI", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("8796093022208.0mB", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("8589934592GiB", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("8192PIb", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("8388608.1abtI", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("9223372036854775808.1a", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("123a456.123mB", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("6a1.123Pib", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("12a.0GiB", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("a1230.0", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("1&3B", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("a1tI", &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("1a.a1kI", &converted); + ASSERT_NE(ret, 0); + + + ret = util_parse_byte_size_string(nullptr, &converted); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("1", nullptr); + ASSERT_NE(ret, 0); + + ret = util_parse_byte_size_string("", &converted); + ASSERT_NE(ret, 0); + + MOCK_SET(util_strdup_s, nullptr); + ret = util_parse_byte_size_string("1", &converted); + ASSERT_NE(ret, 0); + MOCK_CLEAR(util_strdup_s); +} + +TEST(utils_string_llt, test_util_string_split_multi) +{ + char **result = nullptr; + + result = util_string_split_multi("abcd,,,1234999999999", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], ""); + free(result[1]); + ASSERT_STREQ(result[2], ""); + free(result[2]); + ASSERT_STREQ(result[3], "1234999999999"); + free(result[3]); + ASSERT_STREQ(result[4], nullptr); + free(result); + + result = util_string_split_multi("abcd,1234,*&^(,defgz", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], "1234"); + free(result[1]); + ASSERT_STREQ(result[2], "*&^("); + free(result[2]); + ASSERT_STREQ(result[3], "defgz"); + free(result[3]); + ASSERT_STREQ(result[4], nullptr); + free(result); + + result = util_string_split_multi(",abcd,12340000000000", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], ""); + free(result[0]); + ASSERT_STREQ(result[1], "abcd"); + free(result[1]); + ASSERT_STREQ(result[2], "12340000000000"); + free(result[2]); + ASSERT_STREQ(result[3], nullptr); + free(result); + + result = util_string_split_multi("abcd,12340000000000,", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], "12340000000000"); + free(result[1]); + ASSERT_STREQ(result[2], ""); + free(result[2]); + ASSERT_STREQ(result[3], nullptr); + free(result); + + result = util_string_split_multi("abcd,1234,", 'x'); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd,1234,"); + free(result[0]); + ASSERT_STREQ(result[1], nullptr); + free(result); + + result = util_string_split_multi(",", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], ""); + free(result[0]); + ASSERT_STREQ(result[1], ""); + free(result[1]); + ASSERT_STREQ(result[2], nullptr); + free(result); + + result = util_string_split_multi("", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], ""); + free(result[0]); + ASSERT_STREQ(result[1], nullptr); + free(result); + + result = util_string_split_multi(nullptr, ','); + ASSERT_EQ(result, nullptr); + + MOCK_SET(calloc, nullptr); + result = util_string_split_multi("abcd,12340000000000,", ','); + ASSERT_EQ(result, nullptr); + MOCK_CLEAR(calloc); +} + +TEST(utils_string_llt, test_util_string_split) +{ + char **result = nullptr; + + result = util_string_split("abcd,,,1234999999999", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], "1234999999999"); + free(result[1]); + ASSERT_STREQ(result[2], nullptr); + free(result); + + result = util_string_split("abcd,1234,*&^(,defgz", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], "1234"); + free(result[1]); + ASSERT_STREQ(result[2], "*&^("); + free(result[2]); + ASSERT_STREQ(result[3], "defgz"); + free(result[3]); + ASSERT_STREQ(result[4], nullptr); + free(result); + + result = util_string_split(",abcd,12340000000000", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], "12340000000000"); + free(result[1]); + ASSERT_STREQ(result[2], nullptr); + free(result); + + result = util_string_split("abcd,12340000000000,", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], "12340000000000"); + free(result[1]); + ASSERT_STREQ(result[2], nullptr); + free(result); + + result = util_string_split("abcd,1234,", 'x'); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd,1234,"); + free(result[0]); + ASSERT_STREQ(result[1], nullptr); + free(result); + + result = util_string_split(",", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], ""); + free(result[0]); + ASSERT_STREQ(result[1], nullptr); + free(result); + + result = util_string_split("", ','); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], ""); + free(result[0]); + ASSERT_STREQ(result[1], nullptr); + free(result); + + result = util_string_split(nullptr, ','); + ASSERT_EQ(result, nullptr); + + MOCK_SET(calloc, nullptr); + result = util_string_split("abcd,12340000000000,", ','); + ASSERT_EQ(result, nullptr); + MOCK_CLEAR(calloc); +} + +TEST(utils_string_llt, test_str_skip_str) +{ + const char *str = "abcdefghij1234567890"; + const char *substr = "abcdefgh"; + const char *result = nullptr; + + result = str_skip_str(str, substr); + ASSERT_STREQ(result, "ij1234567890"); + + result = str_skip_str(str, "habc"); + ASSERT_STREQ(result, nullptr); + + result = str_skip_str(str, ""); + ASSERT_STREQ(result, str); + + result = str_skip_str(str, nullptr); + ASSERT_STREQ(result, nullptr); + + result = str_skip_str("a", "a"); + ASSERT_STREQ(result, ""); + + result = str_skip_str("", ""); + ASSERT_STREQ(result, ""); + + result = str_skip_str(nullptr, ""); + ASSERT_STREQ(result, nullptr); +} + +TEST(utils_string_llt, test_util_string_delchar) +{ + char *result = nullptr; + + result = util_string_delchar("aaaaaaaaaaaaaaaaaaaa", 'a'); + ASSERT_STREQ(result, ""); + free(result); + + result = util_string_delchar("1234567890abc*&^ghij", 'a'); + ASSERT_STREQ(result, "1234567890bc*&^ghij"); + free(result); + + result = util_string_delchar("1234567890abc*&^ghij", 'z'); + ASSERT_STREQ(result, "1234567890abc*&^ghij"); + free(result); + + result = util_string_delchar(nullptr, 'a'); + ASSERT_STREQ(result, nullptr); + + MOCK_SET(util_strdup_s, nullptr); + result = util_string_delchar("a", 'a'); + ASSERT_STREQ(result, nullptr); + MOCK_CLEAR(util_strdup_s); +} + +TEST(utils_string_llt, test_util_trim_newline) +{ + char s_all[ ] = { '\n', '\n', '\n', '\n', '\0' }; + char s_tail[ ] = { '\n', 'a', '\n', 'b', '\n', '\0' }; + char s_not_n[ ] = { 'a', '\n', 'b', 'c', '\0' }; + char s_empty[ ] = { '\0' }; + char *s_nullptr = nullptr; + + util_trim_newline(s_all); + ASSERT_STREQ(s_all, ""); + + util_trim_newline(s_tail); + ASSERT_STREQ(s_tail, "\na\nb"); + + util_trim_newline(s_not_n); + ASSERT_STREQ(s_not_n, "a\nbc"); + + util_trim_newline(s_empty); + ASSERT_STREQ(s_empty, ""); + + util_trim_newline(s_nullptr); + ASSERT_STREQ(s_nullptr, nullptr); +} + +TEST(utils_string_llt, test_util_trim_space) +{ + char s_all[ ] = { '\f', '\n', '\r', '\t', '\v', ' ', '\0' }; + char s_head[ ] = { '\f', '\n', '\r', 'a', 'b', 'c', '\0' }; + char s_tail[ ] = { 'a', 'b', 'c', '\t', '\v', ' ', '\0' }; + char s_head_tail[ ] = { '\f', 'a', 'b', 'c', '\v', ' ', '\0' }; + char s_mid[ ] = { 'a', 'b', '\r', '\t', '\v', 'c', '\0' }; + char s_not_space[ ] = { 'a', 'a', 'b', 'b', 'c', 'c', '\0' }; + char s_empty[ ] = { '\0' }; + char *s_nullptr = nullptr; + char *result = nullptr; + + result = util_trim_space(s_all); + ASSERT_STREQ(result, ""); + + result = util_trim_space(s_head); + ASSERT_STREQ(result, "abc"); + + result = util_trim_space(s_tail); + ASSERT_STREQ(result, "abc"); + + result = util_trim_space(s_head_tail); + ASSERT_STREQ(result, "abc"); + + result = util_trim_space(s_mid); + ASSERT_STREQ(result, "ab\r\t\vc"); + + result = util_trim_space(s_not_space); + ASSERT_STREQ(result, "aabbcc"); + + result = util_trim_space(s_empty); + ASSERT_STREQ(result, ""); + + result = util_trim_space(s_nullptr); + ASSERT_STREQ(result, nullptr); +} + +TEST(utils_string_llt, test_util_trim_quotation) +{ + char s_all[ ] = { '"', '"', '"', '\n', '"', '\0' }; + char s_head[ ] = { '"', '"', 'a', 'b', 'c', '\0' }; + char s_tail_n[ ] = { 'a', 'b', 'c', '\n', '\n', '\0' }; + char s_tail_quo[ ] = { 'a', 'b', 'c', '"', '"', '\0' }; + char s_head_tail[ ] = { '"', '"', 'a', '\n', '"', '\0' }; + char s_mid[ ] = { 'a', 'b', '"', '\n', 'c', '\0' }; + char s_not_space[ ] = { 'a', 'b', 'c', 'd', 'e', '\0' }; + char s_empty[ ] = { '\0' }; + char *s_nullptr = nullptr; + char *result = nullptr; + + result = util_trim_quotation(s_all); + ASSERT_STREQ(result, ""); + + result = util_trim_quotation(s_head); + ASSERT_STREQ(result, "abc"); + + result = util_trim_quotation(s_tail_n); + ASSERT_STREQ(result, "abc"); + + result = util_trim_quotation(s_tail_quo); + ASSERT_STREQ(result, "abc"); + + result = util_trim_quotation(s_head_tail); + ASSERT_STREQ(result, "a"); + + result = util_trim_quotation(s_mid); + ASSERT_STREQ(result, "ab\"\nc"); + + result = util_trim_quotation(s_not_space); + ASSERT_STREQ(result, "abcde"); + + result = util_trim_quotation(s_empty); + ASSERT_STREQ(result, ""); + + result = util_trim_quotation(s_nullptr); + ASSERT_STREQ(result, nullptr); +} + +TEST(utils_string_llt, test_str_array_dup) +{ + const char *array_long[] = { "abcd", "1234", nullptr, "", "&^%abc" }; + size_t array_long_len = sizeof(array_long) / sizeof(array_long[0]); + + const char *array_short[] = { "abcd" }; + size_t array_short_len = sizeof(array_short) / sizeof(array_short[0]); + + char **result = nullptr; + + result = str_array_dup(array_long, array_long_len); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], "1234"); + free(result[1]); + ASSERT_STREQ(result[2], nullptr); + ASSERT_STREQ(result[3], ""); + free(result[3]); + ASSERT_STREQ(result[4], "&^%abc"); + free(result[4]); + ASSERT_STREQ(result[5], nullptr); + free(result); + + result = str_array_dup(array_short, array_short_len); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], nullptr); + free(result); + + result = str_array_dup(nullptr, 0); + ASSERT_EQ(result, nullptr); +} + +TEST(utils_string_llt, test_util_string_join) +{ + const char *array_long[] = { "abcd", "1234", "5678", "", "&^%abc" }; + size_t array_long_len = sizeof(array_long) / sizeof(array_long[0]); + + const char *array_short[] = { "abcd" }; + size_t array_short_len = sizeof(array_short) / sizeof(array_short[0]); + + const char *array_nullptr[] = { nullptr }; + size_t array_nullptr_len = sizeof(array_nullptr) / sizeof(array_nullptr[0]); + + char *result = nullptr; + + result = util_string_join(" ", array_long, array_long_len); + ASSERT_STREQ(result, "abcd 1234 5678 &^%abc"); + free(result); + + result = util_string_join(" ", array_short, array_short_len); + ASSERT_STREQ(result, "abcd"); + free(result); + + result = util_string_join(" ", array_nullptr, array_nullptr_len); + ASSERT_EQ(result, nullptr); + + result = util_string_join(" ", nullptr, 0); + ASSERT_EQ(result, nullptr); + + result = util_string_join("", array_long, array_long_len); + ASSERT_STREQ(result, "abcd12345678&^%abc"); + free(result); + + result = util_string_join(nullptr, array_long, array_long_len); + ASSERT_STREQ(result, nullptr); + + MOCK_SET_V(strcat_s, strcat_s_fail); + result = util_string_join(" ", array_short, array_short_len); + ASSERT_STREQ(result, nullptr); + MOCK_CLEAR(strcat_s); + + MOCK_SET_V(strcat_s, strcat_s_fail); + result = util_string_join(" ", array_long, array_long_len); + ASSERT_STREQ(result, nullptr); + MOCK_CLEAR(strcat_s); + + g_strcat_s_cnt = 0; + MOCK_SET_V(strcat_s, strcat_s_second_fail); + result = util_string_join(" ", array_long, array_long_len); + ASSERT_STREQ(result, nullptr); + MOCK_CLEAR(strcat_s); +} + +TEST(utils_string_llt, test_util_string_append) +{ + char *result = nullptr; + + result = util_string_append("abc", "123"); + ASSERT_STREQ(result, "123abc"); + free(result); + + result = util_string_append("abc", ""); + ASSERT_STREQ(result, "abc"); + free(result); + + result = util_string_append("abc", nullptr); + ASSERT_STREQ(result, "abc"); + free(result); + + result = util_string_append("", "123"); + ASSERT_STREQ(result, "123"); + free(result); + + result = util_string_append("", ""); + ASSERT_STREQ(result, ""); + free(result); + + result = util_string_append("", nullptr); + ASSERT_STREQ(result, ""); + free(result); + + result = util_string_append(nullptr, "123"); + ASSERT_STREQ(result, "123"); + free(result); + + result = util_string_append(nullptr, ""); + ASSERT_STREQ(result, ""); + free(result); + + result = util_string_append(nullptr, nullptr); + ASSERT_STREQ(result, nullptr); + + MOCK_SET(calloc, nullptr); + result = util_string_append("abc", "123"); + ASSERT_STREQ(result, nullptr); + MOCK_CLEAR(calloc); + + MOCK_SET_V(strcat_s, strcat_s_fail); + result = util_string_append("abc", "123"); + ASSERT_STREQ(result, nullptr); + MOCK_CLEAR(strcat_s); + + g_strcat_s_cnt = 0; + MOCK_SET_V(strcat_s, strcat_s_second_fail); + result = util_string_append("abc", "123"); + ASSERT_STREQ(result, nullptr); + MOCK_CLEAR(strcat_s); +} + +TEST(utils_string_llt, test_dup_array_of_strings) +{ + const char *array_long[] = { "abcd", "1234", nullptr, "", "&^%abc" }; + size_t array_long_len = sizeof(array_long) / sizeof(array_long[0]); + + const char *array_short[] = { "abcd" }; + size_t array_short_len = sizeof(array_short) / sizeof(array_short[0]); + + char **result = nullptr; + size_t result_len = 0; + int ret; + + ret = dup_array_of_strings(array_long, array_long_len, &result, &result_len); + ASSERT_EQ(ret, 0); + ASSERT_EQ(array_long_len, result_len); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + ASSERT_STREQ(result[1], "1234"); + free(result[1]); + ASSERT_STREQ(result[2], nullptr); + ASSERT_STREQ(result[3], ""); + free(result[3]); + ASSERT_STREQ(result[4], "&^%abc"); + free(result[4]); + free(result); + + ret = dup_array_of_strings(array_long, array_long_len, &result, nullptr); + ASSERT_NE(ret, 0); + + ret = dup_array_of_strings(array_long, array_long_len, nullptr, &result_len); + ASSERT_NE(ret, 0); + + ret = dup_array_of_strings(array_short, array_short_len, &result, &result_len); + ASSERT_EQ(ret, 0); + ASSERT_EQ(array_short_len, result_len); + ASSERT_NE(result, nullptr); + ASSERT_STREQ(result[0], "abcd"); + free(result[0]); + free(result); + + ret = dup_array_of_strings(nullptr, 0, &result, &result_len); + ASSERT_EQ(ret, 0); + + MOCK_SET(calloc, nullptr); + ret = dup_array_of_strings(array_long, array_long_len, &result, &result_len); + ASSERT_NE(ret, 0); + MOCK_CLEAR(calloc); +} diff --git a/test/include/mock.h b/test/include/mock.h new file mode 100644 index 0000000..3773ed5 --- /dev/null +++ b/test/include/mock.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2016-2019. All rights reserved. + * iSulad licensed under the Mulan PSL v1. + * You can use this software according to the terms and conditions of the Mulan PSL v1. + * You may obtain a copy of Mulan PSL v1 at: + * http://license.coscl.org.cn/MulanPSL + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v1 for more details. + * Description: define mock method + * Author: wangyushui + * Create: 2019-6-10 + */ + +#ifndef MOCK_H +#define MOCK_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MOCK_STRUCT_INIT(...) \ + { __VA_ARGS__ } + +#define DEFINE_RETURN_MOCK(fn, ret) \ + bool ut_ ## fn ## _mocked = false; \ + ret ut_ ## fn + +#define DEFINE_RETURN_MOCK_V(fn, ret, dargs) \ + bool ut_ ## fn ## _mocked = false; \ + ret(* ut_ ## fn) dargs +/* + * For controlling mocked function behavior, setting + * and getting values from the stub, the _P macros are + * for mocking functions that return pointer values. + */ +#define MOCK_SET(fn, val) \ + ut_ ## fn ## _mocked = true; \ + ut_ ## fn = val + +#define MOCK_SET_V(fn, fun) \ + ut_ ## fn ## _mocked = true; \ + ut_ ## fn = fun + +#define MOCK_GET(fn) \ + ut_ ## fn + +#define MOCK_GET_V(fn, args) \ + ut_ ## fn args + +#define MOCK_CLEAR(fn) \ + ut_ ## fn ## _mocked = false; + +#define MOCK_CLEAR_P(fn) \ + ut_ ## fn ## _mocked = false; \ + ut_ ## fn = NULL; + +/* for declaring function protoypes for wrappers */ +#define DECLARE_WRAPPER(fn, ret, args) \ + extern bool ut_ ## fn ## _mocked; \ + extern ret ut_ ## fn; \ + ret __wrap_ ## fn args; \ + ret __real_ ## fn args; + +#define DECLARE_WRAPPER_V(fn, ret, args) \ + extern bool ut_ ## fn ## _mocked; \ + extern ret(* ut_ ## fn) args; \ + ret __wrap_ ## fn args; \ + ret __real_ ## fn args; + +/* for defining the implmentation of wrappers for syscalls */ +#define DEFINE_WRAPPER(fn, ret, dargs, pargs) \ + DEFINE_RETURN_MOCK(fn, ret); \ + ret __wrap_ ## fn dargs \ + { \ + if (!ut_ ## fn ## _mocked) { \ + return __real_ ## fn pargs; \ + } else { \ + return MOCK_GET(fn); \ + } \ + } + +#define DEFINE_WRAPPER_V(fn, ret, dargs, pargs) \ + DEFINE_RETURN_MOCK_V(fn, ret, dargs); \ + __attribute__((used)) ret __wrap_ ## fn dargs \ + { \ + if (!ut_ ## fn ## _mocked) { \ + return __real_ ## fn pargs; \ + } else { \ + return MOCK_GET_V(fn, pargs); \ + } \ + } + +/* DEFINE_STUB is for defining the implmentation of stubs for funcs. */ +#define DEFINE_STUB(fn, ret, dargs, val) \ + bool ut_ ## fn ## _mocked = true; \ + ret ut_ ## fn = val; \ + ret fn dargs; \ + ret fn dargs \ + { \ + return MOCK_GET(fn); \ + } + +/* DEFINE_STUB_V macro is for stubs that don't have a return value */ +#define DEFINE_STUB_V(fn, dargs) \ + void fn dargs; \ + void fn dargs \ + { \ + } + +#define HANDLE_RETURN_MOCK(fn) \ + if (ut_ ## fn ## _mocked) { \ + return ut_ ## fn; \ + } + +#ifdef __cplusplus +} +#endif + +#endif /* MOCK_H */ + + diff --git a/test/llt.sh b/test/llt.sh new file mode 100755 index 0000000..0647b54 --- /dev/null +++ b/test/llt.sh @@ -0,0 +1,264 @@ +####################################################################### +##- @Copyright (C) Huawei Technologies., Ltd. 2017-2019. All rights reserved. +# - iSulad licensed under the Mulan PSL v1. +# - You can use this software according to the terms and conditions of the Mulan PSL v1. +# - You may obtain a copy of Mulan PSL v1 at: +# - http://license.coscl.org.cn/MulanPSL +# - THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +# - IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +# - PURPOSE. +# - See the Mulan PSL v1 for more details. +##- @Description: generate cetification +##- @Author: wujing +##- @Create: 2019-04-25 +####################################################################### +#! /bin/bash + +#set -xe + +usage() +{ + echo "Usage: sh llt.sh [OPTIONS]" + echo "Use llt.sh to control llt operation" + echo "" + echo "Misc:" + echo " -h, --help Print this help, then exit" + echo + echo "Compile Options:" + echo " -m, --cmake