├── .cirrus.yml ├── .clang-format ├── .dir-locals.el ├── .github ├── renovate.json5 └── workflows │ ├── integration.yml │ └── validate.yml ├── .gitignore ├── .packit.yaml ├── .rpmbuild ├── Makefile └── prepare.sh ├── CODE-OF-CONDUCT.md ├── Containerfile ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── VERSION ├── changelog.txt ├── cmd └── conmon-config │ └── conmon-config.go ├── contrib └── spec │ └── conmon.spec.in ├── docs ├── Makefile └── conmon.8.md ├── go.mod ├── go.sum ├── hack ├── get_ci_vm.sh ├── github-actions-setup └── seccomp-notify.sh ├── meson.build ├── nix ├── default-amd64.nix ├── default-arm64.nix ├── default-ppc64le.nix ├── default-riscv64.nix ├── default-s390x.nix ├── default.nix ├── derivation.nix ├── nixpkgs.json ├── nixpkgs.nix └── overlay.nix ├── rpm └── conmon.spec ├── runner ├── config │ ├── config.go │ ├── config_unix.go │ └── config_windows.go ├── conmon │ ├── conmon.go │ ├── options.go │ └── pipes.go └── conmon_test │ ├── conmon_test.go │ ├── ctr_logs_test.go │ ├── runtime_test.go │ └── suite_test.go └── src ├── cgroup.c ├── cgroup.h ├── cli.c ├── cli.h ├── close_fds.c ├── close_fds.h ├── cmsg.c ├── cmsg.h ├── config.h ├── conmon.c ├── conn_sock.c ├── conn_sock.h ├── ctr_exit.c ├── ctr_exit.h ├── ctr_logging.c ├── ctr_logging.h ├── ctr_stdio.c ├── ctr_stdio.h ├── ctrl.c ├── ctrl.h ├── globals.c ├── globals.h ├── oom.c ├── oom.h ├── parent_pipe_fd.c ├── parent_pipe_fd.h ├── runtime_args.c ├── runtime_args.h ├── seccomp_notify.c ├── seccomp_notify.h ├── seccomp_notify_plugin.h ├── utils.c └── utils.h /.cirrus.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # Main collection of env. vars to set for all tasks and scripts. 4 | env: 5 | #### 6 | #### Global variables used for all tasks 7 | #### 8 | GOPATH: "/var/tmp/go" 9 | CONMON_SLUG: "github.com/containers/conmon" 10 | 11 | # Overrides default location (/tmp/cirrus) for repo clone (will become $SRC) 12 | CIRRUS_WORKING_DIR: "${GOPATH}/src/${CONMON_SLUG}" 13 | # Required so $ENVLIB gets loaded and /bin/sh is not used 14 | CIRRUS_SHELL: "/bin/bash" 15 | # Save a little typing (path relative to $CIRRUS_WORKING_DIR) 16 | SCRIPT_BASE: "./contrib/cirrus" 17 | # Spoof self as travis, as cirrus has the same test issues as travis does 18 | TRAVIS: "true" 19 | 20 | # VM Image built in containers/automation_images 21 | IMAGE_SUFFIX: "c20240513t140131z-f40f39d13" 22 | FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" 23 | 24 | # Container FQIN's 25 | FEDORA_CONTAINER_FQIN: "quay.io/libpod/fedora_podman:${IMAGE_SUFFIX}" 26 | PRIOR_FEDORA_CONTAINER_FQIN: "quay.io/libpod/prior-fedora_podman:${IMAGE_SUFFIX}" 27 | 28 | 29 | # Only github users with write-access can define or use encrypted variables 30 | # This credential represents a service account with access to manage both VMs 31 | # and storage. 32 | gcp_credentials: ENCRYPTED[13e51806369f650e6ccc326338deeb3c24052fc0a7be29beef2b96da551aed3200abbb6c6406a936bb4388fb2758405c] 33 | 34 | 35 | # Default VM to use unless set or modified by task 36 | gce_instance: 37 | image_project: "libpod-218412" 38 | zone: "us-central1-f" # Required by Cirrus for the time being 39 | cpu: 2 40 | memory: "4Gb" 41 | disk: 200 # Required for performance reasons 42 | image_name: "${FEDORA_CACHE_IMAGE_NAME}" 43 | 44 | 45 | # Default timeout for each task 46 | timeout_in: '120m' 47 | 48 | # Verify conmon package can be built and installed on Fedora 49 | fedora_packaging_task: 50 | alias: fedora_packaging 51 | 52 | # Runs within Cirrus's "community cluster" 53 | container: 54 | cpu: 1 55 | memory: 4 56 | 57 | matrix: 58 | - name: "Packaging for Fedora" 59 | container: 60 | image: "${FEDORA_CONTAINER_FQIN}" 61 | - name: "Packaging for Fedora N-1" 62 | container: 63 | image: "${PRIOR_FEDORA_CONTAINER_FQIN}" 64 | 65 | script: 66 | - dnf install -y rpm-build golang libseccomp-devel 67 | - cd $CIRRUS_WORKING_DIR 68 | - make 69 | - make -f .rpmbuild/Makefile 70 | - rpmbuild --rebuild conmon-*.src.rpm 71 | - dnf erase -y conmon 72 | - dnf -y install ~/rpmbuild/RPMS/x86_64/conmon*.x86_64.rpm 73 | - ls -l /usr/bin/conmon 74 | 75 | timeout_in: '20m' 76 | 77 | 78 | # Verify calls to bin/config were saved 79 | config_task: 80 | # Runs within Cirrus's "community cluster" 81 | container: 82 | image: "${FEDORA_CONTAINER_FQIN}" 83 | cpu: 1 84 | memory: 4 85 | 86 | script: 87 | - dnf install -y make glib2-devel git gcc golang 88 | - cd $CIRRUS_WORKING_DIR 89 | - make config 90 | - git diff --exit-code 91 | 92 | 93 | # Verify code was fmt'ed properly 94 | fmt_task: 95 | # Runs within Cirrus's "community cluster" 96 | container: 97 | image: "${FEDORA_CONTAINER_FQIN}" 98 | cpu: 1 99 | memory: 4 100 | 101 | script: 102 | - dnf install -y clang clang-tools-extra golang 103 | - cd $CIRRUS_WORKING_DIR 104 | - make fmt 105 | - git diff --exit-code 106 | 107 | 108 | # Build the static binary 109 | static_binary_task: 110 | alias: static_binary 111 | 112 | # Community-maintained task, may fail on occasion. If so, uncomment 113 | # the next line and file an issue with details about the failure. 114 | # allow_failures: true 115 | 116 | timeout_in: '360m' 117 | 118 | gce_instance: 119 | image_name: "${FEDORA_CACHE_IMAGE_NAME}" 120 | cpu: 8 121 | memory: 12 122 | disk: 200 123 | 124 | env: 125 | # Do not use 'latest', fixed-version tag for runtime stability. 126 | CTR_FQIN: "docker.io/nixos/nix:2.15.0" 127 | # Authentication token for pushing the build cache to cachix. 128 | # This is critical, it helps to avoid a very lengthy process of 129 | # statically building every dependency needed to build conmon. 130 | # Assuming the pinned nix dependencies in nix/nixpkgs.json have not 131 | # changed, this cache will ensure that only the static conmon binary is 132 | # built. 133 | CACHIX_AUTH_TOKEN: ENCRYPTED[4c3b8d82b0333abf048c56a71f2559ddb1c9ed38f0c28916eca13f79affa5904cf90c76a5bd8686680c89f41079ef341] 134 | 135 | matrix: 136 | - name: "Static amd64 binary" 137 | env: 138 | TARGET: default.nix 139 | - name: "Static arm64 binary" 140 | env: 141 | TARGET: default-arm64.nix 142 | - name: "Static ppc64le binary" 143 | env: 144 | TARGET: default-ppc64le.nix 145 | - name: "Static riscv64 binary" 146 | env: 147 | TARGET: default-riscv64.nix 148 | - name: "Static s390x binary" 149 | env: 150 | TARGET: default-s390x.nix 151 | 152 | build_script: | 153 | set -ex 154 | podman run -i --rm \ 155 | -e CACHIX_AUTH_TOKEN \ 156 | -v $PWD:$PWD:Z \ 157 | -w $PWD \ 158 | $CTR_FQIN \ 159 | sh -c \ 160 | "nix-env -iA cachix -f https://cachix.org/api/v1/install && \ 161 | cachix use conmon && \ 162 | nix-build nix/$TARGET && \ 163 | nix-store -qR --include-outputs \$(nix-instantiate nix/$TARGET) | grep -v conmon | cachix push conmon && \ 164 | cp -R result/bin ." 165 | 166 | binaries_artifacts: 167 | path: "bin/conmon" 168 | 169 | # This task is critical. It updates the "last-used by" timestamp stored 170 | # in metadata for all VM images. This mechanism functions in tandem with 171 | # an out-of-band pruning operation to remove disused VM images. 172 | meta_task: 173 | name: "VM img. keepalive" 174 | alias: meta 175 | container: 176 | cpu: 2 177 | memory: 2 178 | image: quay.io/libpod/imgts:latest 179 | env: 180 | # Space-separated list of images used by this repository state 181 | IMGNAMES: >- 182 | ${FEDORA_CACHE_IMAGE_NAME} 183 | BUILDID: "${CIRRUS_BUILD_ID}" 184 | REPOREF: "${CIRRUS_REPO_NAME}" 185 | GCPJSON: ENCRYPTED[08de2c74178470b1bc85a107e9962f06dbd11d33c7adf024d3e48ae4399ca5383f9d3ad0e2fd65c3ce12750dd6ef8803] 186 | GCPNAME: ENCRYPTED[561ce33a9357e5b8e3fb54739c3af31730c0c3b736792f16a67026a8544379d83ff3c27d6fea1c7797a6ae49b6e58115] 187 | GCPPROJECT: libpod-218412 188 | clone_script: &noop mkdir -p $CIRRUS_WORKING_DIR 189 | script: /usr/local/bin/entrypoint.sh 190 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: DontAlign 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: false 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: MultiLine 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: NonAssignment 40 | BreakBeforeBraces: Linux 41 | BreakBeforeInheritanceComma: false 42 | BreakInheritanceList: BeforeColon 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: false 45 | BreakConstructorInitializers: BeforeColon 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: false 48 | ColumnLimit: 140 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 8 54 | Cpp11BracedListStyle: true 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: true 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 66 | Priority: 2 67 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 68 | Priority: 3 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: false 73 | IndentPPDirectives: None 74 | IndentWidth: 8 75 | IndentWrappedFunctionNames: false 76 | JavaScriptQuotes: Leave 77 | JavaScriptWrapImports: true 78 | KeepEmptyLinesAtTheStartOfBlocks: true 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 2 82 | NamespaceIndentation: None 83 | ObjCBinPackProtocolList: Auto 84 | ObjCBlockIndentWidth: 2 85 | ObjCSpaceAfterProperty: false 86 | ObjCSpaceBeforeProtocolList: true 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 19 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyBreakTemplateDeclaration: 10 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 60 95 | PointerAlignment: Right 96 | ReflowComments: true 97 | SortIncludes: false 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeCpp11BracedList: false 103 | SpaceBeforeCtorInitializerColon: true 104 | SpaceBeforeInheritanceColon: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceBeforeRangeBasedForLoopColon: true 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInContainerLiterals: true 111 | SpacesInCStyleCastParentheses: false 112 | SpacesInParentheses: false 113 | SpacesInSquareBrackets: false 114 | Standard: Cpp11 115 | TabWidth: 8 116 | UseTab: Always 117 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((c-mode . ((fill-column . 109) 2 | (c-basic-offset . 8) 3 | (indent-tabs-mode t)))) 4 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | /* 2 | Renovate is a service similar to GitHub Dependabot, but with 3 | (fantastically) more configuration options. So many options 4 | in fact, if you're new I recommend glossing over this cheat-sheet 5 | prior to the official documentation: 6 | 7 | https://www.augmentedmind.de/2021/07/25/renovate-bot-cheat-sheet 8 | 9 | Configuration Update/Change Procedure: 10 | 1. Make changes 11 | 2. Manually validate changes (from repo-root): 12 | 13 | podman run -it \ 14 | -v ./.github/renovate.json5:/usr/src/app/renovate.json5:z \ 15 | docker.io/renovate/renovate:latest \ 16 | renovate-config-validator 17 | 3. Commit. 18 | 19 | Configuration Reference: 20 | https://docs.renovatebot.com/configuration-options/ 21 | 22 | Monitoring Dashboard: 23 | https://app.renovatebot.com/dashboard#github/containers 24 | 25 | Note: The Renovate bot will create/manage it's business on 26 | branches named 'renovate/*'. Otherwise, and by 27 | default, the only the copy of this file that matters 28 | is the one on the `main` branch. No other branches 29 | will be monitored or touched in any way. 30 | */ 31 | 32 | { 33 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 34 | 35 | /************************************************* 36 | ****** Global/general configuration options ***** 37 | *************************************************/ 38 | 39 | // Re-use predefined sets of configuration options to DRY 40 | "extends": [ 41 | // https://github.com/containers/automation/blob/main/renovate/defaults.json5 42 | "github>containers/automation//renovate/defaults.json5" 43 | ], 44 | 45 | // Permit automatic rebasing when base-branch changes by more than 46 | // one commit. 47 | "rebaseWhen": "behind-base-branch", 48 | 49 | /************************************************* 50 | *** Repository-specific configuration options *** 51 | *************************************************/ 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: integration 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | 12 | conmon: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version: '1.22' 19 | - run: sudo hack/github-actions-setup 20 | - name: Run conmon integration tests 21 | run: | 22 | sudo mkdir -p /var/run/crio 23 | sudo make test-binary 24 | 25 | cri-o: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | - uses: actions/setup-go@v5 30 | with: 31 | go-version: '1.22' 32 | - run: sudo hack/github-actions-setup 33 | - name: Run CRI-O integration tests 34 | run: | 35 | CRIO_DIR=$(sudo go env GOPATH)/src/github.com/cri-o/cri-o 36 | sudo make -C "$CRIO_DIR" all test-binaries 37 | # skip seccomp tests because they have permission denied issues in a container and accept signed image as they don't use conmon 38 | sudo rm -f "$CRIO_DIR"/test/seccomp*.bats "$CRIO_DIR"/test/image.bats "$CRIO_DIR"/test/policy.bats 39 | sudo sh -c "cd $CRIO_DIR; ./test/test_runner.sh" 40 | env: 41 | JOBS: '2' 42 | 43 | all-done: 44 | needs: 45 | - conmon 46 | - cri-o 47 | runs-on: ubuntu-24.04 48 | steps: 49 | - run: echo "All jobs completed" 50 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: validate 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | - release-* 9 | pull_request: 10 | env: 11 | GO_VERSION: 1.24 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | 17 | deps: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: install go 22 | uses: actions/setup-go@v5 23 | with: 24 | go-version: "${{ env.GO_VERSION }}" 25 | - name: Verify Go dependencies 26 | run: | 27 | make vendor 28 | git diff --exit-code 29 | 30 | all-done: 31 | needs: 32 | - deps 33 | runs-on: ubuntu-latest 34 | steps: 35 | - run: echo "All jobs completed" 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | /bin/conmon 33 | *.exe 34 | *.out 35 | *.app 36 | *.i*86 37 | *.x86_64 38 | *.hex 39 | 40 | # Debug files 41 | *.dSYM/ 42 | *.su 43 | *.idb 44 | *.pdb 45 | 46 | # Kernel Module Compile Results 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | 54 | bin/ 55 | 56 | vendor/ 57 | 58 | result 59 | -------------------------------------------------------------------------------- /.packit.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # See the documentation for more information: 3 | # https://packit.dev/docs/configuration/ 4 | 5 | downstream_package_name: conmon 6 | upstream_tag_template: v{version} 7 | 8 | packages: 9 | conmon-fedora: 10 | pkg_tool: fedpkg 11 | specfile_path: rpm/conmon.spec 12 | conmon-centos: 13 | pkg_tool: centpkg 14 | specfile_path: rpm/conmon.spec 15 | conmon-rhel: 16 | specfile_path: rpm/conmon.spec 17 | 18 | jobs: 19 | - job: copr_build 20 | trigger: pull_request 21 | packages: [conmon-fedora] 22 | notifications: &copr_build_failure_notification 23 | failure_comment: 24 | message: "Ephemeral COPR build failed. @containers/packit-build please check." 25 | enable_net: true 26 | targets: 27 | - fedora-all-aarch64 28 | - fedora-all-x86_64 29 | - fedora-eln-aarch64 30 | - fedora-eln-x86_64 31 | 32 | - job: copr_build 33 | trigger: pull_request 34 | packages: [conmon-centos] 35 | notifications: *copr_build_failure_notification 36 | enable_net: true 37 | targets: 38 | - centos-stream-10-aarch64 39 | - centos-stream-10-x86_64 40 | - centos-stream-9-aarch64 41 | - centos-stream-9-x86_64 42 | 43 | - job: copr_build 44 | trigger: pull_request 45 | packages: [conmon-rhel] 46 | notifications: *copr_build_failure_notification 47 | enable_net: true 48 | targets: 49 | - epel-9-aarch64 50 | - epel-9-x86_64 51 | 52 | # Run on commit to main branch 53 | - job: copr_build 54 | trigger: commit 55 | notifications: 56 | failure_comment: 57 | message: "podman-next COPR build failed. @containers/packit-build please check." 58 | branch: main 59 | owner: rhcontainerbot 60 | project: podman-next 61 | enable_net: true 62 | 63 | # Downstream sync for Fedora 64 | - job: propose_downstream 65 | trigger: release 66 | packages: [conmon-fedora] 67 | update_release: false 68 | dist_git_branches: 69 | - fedora-all 70 | 71 | # Downstream sync for CentOS Stream 72 | - job: propose_downstream 73 | trigger: release 74 | packages: [conmon-centos] 75 | update_release: false 76 | dist_git_branches: 77 | - c10s 78 | 79 | - job: koji_build 80 | trigger: commit 81 | dist_git_branches: 82 | - fedora-all 83 | 84 | - job: bodhi_update 85 | trigger: commit 86 | dist_git_branches: 87 | - fedora-branched # rawhide updates are created automatically 88 | -------------------------------------------------------------------------------- /.rpmbuild/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) 3 | current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path)))) 4 | outdir := $(CURDIR) 5 | topdir := $(CURDIR)/rpmbuild 6 | SHORT_COMMIT ?= $(shell git rev-parse --short=8 HEAD) 7 | 8 | srpm: 9 | mkdir -p $(topdir) 10 | sh $(current_dir)/prepare.sh 11 | rpmbuild -bs -D "dist %{nil}" -D "_sourcedir build/" -D "_srcrpmdir $(outdir)" -D "_topdir $(topdir)" --nodeps contrib/spec/conmon.spec 12 | 13 | build_binary: 14 | mkdir -p $(topdir) 15 | rpmbuild --rebuild -D "_rpmdir $(outdir)" -D "_topdir $(topdir)" $(outdir)/conmon-*.git$(SHORT_COMMIT).src.rpm 16 | 17 | clean: 18 | rm -fr rpms 19 | rm -fr cri-o 20 | -------------------------------------------------------------------------------- /.rpmbuild/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -euf 2 | set -x 3 | 4 | if [ ! -e /usr/bin/git ]; then 5 | dnf -y install git-core 6 | fi 7 | 8 | git fetch --unshallow || : 9 | 10 | COMMIT=$(git rev-parse HEAD) 11 | COMMIT_SHORT=$(git rev-parse --short=8 HEAD) 12 | COMMIT_NUM=$(git rev-list HEAD --count) 13 | COMMIT_DATE=$(date +%s) 14 | 15 | sed "s,#COMMIT#,${COMMIT},; 16 | s,#SHORTCOMMIT#,${COMMIT_SHORT},; 17 | s,#COMMITNUM#,${COMMIT_NUM},; 18 | s,#COMMITDATE#,${COMMIT_DATE}," \ 19 | contrib/spec/conmon.spec.in > contrib/spec/conmon.spec 20 | 21 | mkdir build/ 22 | git archive --prefix "conmon-${COMMIT_SHORT}/" --format "tar.gz" HEAD -o "build/conmon-${COMMIT_SHORT}.tar.gz" 23 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## The conmon Project Community Code of Conduct 2 | 3 | The conmon Project follows the [Containers Community Code of Conduct](https://github.com/containers/common/blob/main/CODE-OF-CONDUCT.md). 4 | -------------------------------------------------------------------------------- /Containerfile: -------------------------------------------------------------------------------- 1 | FROM registry.fedoraproject.org/fedora:latest 2 | 3 | RUN sudo dnf install -y make automake gcc gcc-c++ kernel-devel glib2-devel && \ 4 | sudo dnf clean all && \ 5 | rm -rf /var/cache/dnf 6 | 7 | RUN sudo dnf update -y && \ 8 | sudo dnf clean all && \ 9 | rm -rf /var/cache/dnf 10 | 11 | # replaces the mktemp from the tutorial as everything is temporary in a 12 | # container unless bind mounted out 13 | RUN mkdir -p /tmp/gocache 14 | ENV GOCACHE=/tmp/gocache 15 | 16 | RUN mkdir -p /devenv 17 | ADD . /devenv 18 | WORKDIR /devenv 19 | 20 | RUN make 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2018-2019 github.com/containers authors 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell cat VERSION) 2 | PREFIX ?= /usr/local 3 | BINDIR ?= ${PREFIX}/bin 4 | LIBEXECDIR ?= ${PREFIX}/libexec 5 | GO ?= go 6 | PROJECT := github.com/containers/conmon 7 | PKG_CONFIG ?= pkg-config 8 | HEADERS := $(wildcard src/*.h) 9 | 10 | OBJS := src/conmon.o src/cmsg.o src/ctr_logging.o src/utils.o src/cli.o src/globals.o src/cgroup.o src/conn_sock.o src/oom.o src/ctrl.o src/ctr_stdio.o src/parent_pipe_fd.o src/ctr_exit.o src/runtime_args.o src/close_fds.o src/seccomp_notify.o 11 | 12 | MAKEFILE_PATH := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 13 | 14 | .PHONY: all git-vars docs 15 | all: git-vars bin bin/conmon 16 | 17 | git-vars: 18 | ifeq ($(shell bash -c '[[ `command -v git` && `git rev-parse --git-dir 2>/dev/null` ]] && echo 0'),0) 19 | $(eval COMMIT_NO :=$(shell git rev-parse HEAD 2> /dev/null || true)) 20 | $(eval GIT_COMMIT := $(if $(shell git status --porcelain --untracked-files=no),"${COMMIT_NO}-dirty","${COMMIT_NO}")) 21 | $(eval GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)) 22 | $(eval GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g")) 23 | else 24 | $(eval COMMIT_NO := unknown) 25 | $(eval GIT_COMMIT := unknown) 26 | $(eval GIT_BRANCH := unknown) 27 | $(eval GIT_BRANCH_CLEAN := unknown) 28 | endif 29 | 30 | override LIBS += $(shell $(PKG_CONFIG) --libs glib-2.0) 31 | 32 | CFLAGS ?= -std=c99 -Os -Wall -Wextra -Werror 33 | override CFLAGS += $(shell $(PKG_CONFIG) --cflags glib-2.0) -DVERSION=\"$(VERSION)\" -DGIT_COMMIT=\"$(GIT_COMMIT)\" 34 | 35 | # Conditionally compile journald logging code if the libraries can be found 36 | # if they can be found, set USE_JOURNALD macro for use in conmon code. 37 | # 38 | # "pkg-config --exists" will error if the package doesn't exist. Make can only compare 39 | # output of commands, so the echo commands are to allow pkg-config to error out, make to catch it, 40 | # and allow the compilation to complete. 41 | ifeq ($(shell $(PKG_CONFIG) --exists libsystemd && echo "0"), 0) 42 | override LIBS += $(shell $(PKG_CONFIG) --libs libsystemd) 43 | override CFLAGS += $(shell $(PKG_CONFIG) --cflags libsystemd) -D USE_JOURNALD=1 44 | endif 45 | 46 | ifeq ($(shell hack/seccomp-notify.sh), 0) 47 | override LIBS += $(shell $(PKG_CONFIG) --libs libseccomp) -ldl 48 | override CFLAGS += $(shell $(PKG_CONFIG) --cflags libseccomp) -D USE_SECCOMP=1 49 | endif 50 | 51 | # Update nix/nixpkgs.json its latest stable commit 52 | .PHONY: nixpkgs 53 | nixpkgs: 54 | @nix run -f channel:nixpkgs-unstable nix-prefetch-git -- \ 55 | --no-deepClone https://github.com/nixos/nixpkgs > nix/nixpkgs.json 56 | 57 | # Build statically linked binary 58 | .PHONY: static 59 | static: 60 | @nix build -f nix/ 61 | mkdir -p ./bin 62 | cp -rfp ./result/bin/* ./bin/ 63 | 64 | bin/conmon: $(OBJS) | bin 65 | $(CC) $(LDFLAGS) $(CFLAGS) $(DEBUGFLAG) -o $@ $^ $(LIBS) 66 | 67 | %.o: %.c $(HEADERS) 68 | $(CC) $(CFLAGS) $(DEBUGFLAG) -o $@ -c $< 69 | 70 | config: git-vars cmd/conmon-config/conmon-config.go runner/config/config.go runner/config/config_unix.go runner/config/config_windows.go 71 | $(GO) build $(LDFLAGS) -tags "$(BUILDTAGS)" -o bin/config $(PROJECT)/cmd/conmon-config 72 | ( cd src && $(CURDIR)/bin/config ) 73 | 74 | .PHONY: test-binary 75 | test-binary: bin/conmon _test-files 76 | CONMON_BINARY="$(MAKEFILE_PATH)bin/conmon" $(GO) test $(LDFLAGS) -tags "$(BUILDTAGS)" $(PROJECT)/runner/conmon_test/ -count=1 -v 77 | 78 | .PHONY: test 79 | test:_test-files 80 | $(GO) test $(LDFLAGS) -tags "$(BUILDTAGS)" $(PROJECT)/runner/conmon_test/ 81 | 82 | .PHONY: test-files 83 | _test-files: git-vars runner/conmon_test/*.go runner/conmon/*.go 84 | 85 | bin: 86 | mkdir -p bin 87 | 88 | .PHONY: vendor 89 | vendor: 90 | $(GO) mod tidy 91 | $(GO) mod verify 92 | 93 | .PHONY: docs 94 | docs: 95 | $(MAKE) -C docs 96 | 97 | .PHONY: clean 98 | clean: 99 | rm -rf bin/ src/*.o 100 | $(MAKE) -C docs clean 101 | 102 | .PHONY: install install.bin install.crio install.podman podman crio 103 | install: install.bin docs 104 | $(MAKE) -C docs install 105 | 106 | podman: install.podman 107 | 108 | crio: install.crio 109 | 110 | install.bin: bin/conmon 111 | install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) 112 | install ${SELINUXOPT} -m 755 bin/conmon $(DESTDIR)$(BINDIR)/conmon 113 | 114 | install.crio: bin/conmon 115 | install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(LIBEXECDIR)/crio 116 | install ${SELINUXOPT} -m 755 bin/conmon $(DESTDIR)$(LIBEXECDIR)/crio/conmon 117 | 118 | install.podman: bin/conmon 119 | install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(LIBEXECDIR)/podman 120 | install ${SELINUXOPT} -m 755 bin/conmon $(DESTDIR)$(LIBEXECDIR)/podman/conmon 121 | 122 | .PHONY: fmt 123 | fmt: 124 | git ls-files -z \*.c \*.h | xargs -0 clang-format -i 125 | gofmt -s -w . 126 | 127 | 128 | .PHONY: dbuild 129 | dbuild: 130 | -mkdir -p bin 131 | -podman rm conmon-devenv 132 | podman build -t conmon-devenv:latest . 133 | podman create --name conmon-devenv conmon-devenv:latest 134 | podman cp conmon-devenv:/devenv/bin/conmon bin/conmon 135 | @echo "for installation move conmon file to /usr/local/libexec/podman/" 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # conmon 2 | 3 | An OCI container runtime monitor. 4 | 5 | Conmon is a monitoring program and communication tool between a 6 | container manager (like [Podman](https://podman.io/) or 7 | [CRI-O](https://cri-o.io/)) and an OCI runtime (like 8 | [runc](https://github.com/opencontainers/runc) or 9 | [crun](https://github.com/containers/crun)) for a single container. 10 | 11 | Upon being launched, conmon (usually) double-forks to daemonize and detach from the 12 | parent that launched it. It then launches the runtime as its child. This 13 | allows managing processes to die in the foreground, but still be able to 14 | watch over and connect to the child process (the container). 15 | 16 | While the container runs, conmon does two things: 17 | 18 | - Provides a socket for attaching to the container, holding open the 19 | container's standard streams and forwarding them over the socket. 20 | - Writes the contents of the container's streams to a log file (or to 21 | the systemd journal) so they can be read after the container's 22 | death. 23 | 24 | Finally, upon the containers death, conmon will record its exit time and 25 | code to be read by the managing programs. 26 | 27 | Written in C and designed to have a low memory footprint, conmon is 28 | intended to be run by a container managing library. Essentially, conmon 29 | is the smallest daemon a container can have. 30 | 31 | In most cases, conmon should be packaged with your favorite container 32 | manager. However, if you'd like to try building it from source, follow 33 | the steps below. 34 | 35 | ## Dependencies 36 | 37 | These dependencies are required for the build: 38 | 39 | ### Fedora, CentOS, RHEL, and related distributions: 40 | 41 | ``` shell 42 | sudo yum install -y \ 43 | gcc \ 44 | git \ 45 | glib2-devel \ 46 | glibc-devel \ 47 | libseccomp-devel \ 48 | make \ 49 | pkgconfig \ 50 | runc 51 | ``` 52 | 53 | ### Debian, Ubuntu, and related distributions: 54 | 55 | ``` shell 56 | sudo apt-get install \ 57 | gcc \ 58 | git \ 59 | libc6-dev \ 60 | libglib2.0-dev \ 61 | libseccomp-dev \ 62 | pkg-config \ 63 | make \ 64 | runc 65 | ``` 66 | 67 | ## Build 68 | 69 | Once all the dependencies are installed: 70 | 71 | ``` shell 72 | make 73 | ``` 74 | 75 | There are three options for installation, depending on your environment. 76 | Each can have the PREFIX overridden. The PREFIX defaults to `/usr/local` 77 | for most Linux distributions. 78 | 79 | - `make install` installs to `$PREFIX/bin`, for adding conmon to the 80 | path. 81 | - `make podman` installs to `$PREFIX/libexec/podman`, which is used to 82 | override the conmon version that Podman uses. 83 | - `make crio` installs to `$PREFIX/libexec/crio`, which is used to 84 | override the conmon version that CRI-O uses. 85 | 86 | Note, to run conmon, you'll also need to have an OCI compliant runtime 87 | installed, like [runc](https://github.com/opencontainers/runc) or 88 | [crun](https://github.com/containers/crun). 89 | 90 | ## Static build 91 | 92 | It is possible to build a statically linked binary of conmon by using 93 | the officially provided 94 | [nix](https://nixos.org/nixos/packages.html?attr=conmon&channel=unstable&query=conmon) 95 | package and the derivation of it [within this repository](nix/). The 96 | builds are completely reproducible and will create a x86\_64/amd64 97 | stripped ELF binary for [glibc](https://www.gnu.org/software/libc). 98 | 99 | ### Nix 100 | 101 | To build the binaries by locally installing the nix package manager: 102 | 103 | ``` shell 104 | nix build -f nix/ 105 | ``` 106 | 107 | ### Ansible 108 | 109 | An [Ansible Role](https://github.com/alvistack/ansible-role-conmon) is 110 | also available to automate the installation of the above statically 111 | linked binary on its supported OS: 112 | 113 | ``` shell 114 | sudo su - 115 | mkdir -p ~/.ansible/roles 116 | cd ~/.ansible/roles 117 | git clone https://github.com/alvistack/ansible-role-conmon.git conmon 118 | cd ~/.ansible/roles/conmon 119 | pip3 install --upgrade --ignore-installed --requirement requirements.txt 120 | molecule converge 121 | molecule verify 122 | ``` 123 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security and Disclosure Information Policy for the conmon Project 2 | 3 | The conmon Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects. 4 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.1.13 2 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | - Changelog for v1.0.0 (2019-08-05) 2 | * conmon: close FDs before creating exit file 3 | * add installation guide 4 | * README.md: add LGTM.com badge 5 | * utils: rename cid to log_cid 6 | * conmon: drop FIXME comment 7 | * main: do not shadow global variable container_pid 8 | * ctr_logging: use localtime_r 9 | * conmon: check it is valid pid before kill'ing it 10 | * change conmon exec --attach option to exec-attach 11 | * Update git-vars target to check for executable as well 12 | * prefix logging with conmon: 13 | * Build CRI-O from master instead of RPM 14 | * Enable backwards compatibility 15 | * use conmon for a sync'd exec 16 | 17 | - Changelog for v0.4.0 (2019-07-25) 18 | * Use configurable make variable for pkg-config 19 | * Fix linker error due to missing source files 20 | * conmon: force unlink attach socket 21 | * remove appendix 22 | 23 | - Changelog for v0.3.0 (2019-06-13) 24 | * Update license to have owners and date of copyright 25 | * Add cmd/config/config.go (#41) 26 | * fix: separate DESTDIR variable from PREFIX 27 | * Add podman and crio make target 28 | * Add warning if read events failed 29 | * Update build flags to include -Werror 30 | * Fix typos (#31) 31 | * conmon: support OOM monitor under cgroup v2 (#29) 32 | * Update Makefile git shell commands 33 | * Add .clang-format file 34 | * fix silly syslog error (#22) 35 | * Cleanup Cirrus implementation 36 | 37 | -- Changelog for v0.2.0 (2019-05-20) 38 | * fix silly syslog error 39 | * bump timeout 40 | * Spoof self as travis to pass ctr_oom test 41 | * cirrus: Set read_only flag to false 42 | * disable rhel tests in favor of fedora tests 43 | * Cirrus: Enable CRI-O intg. testing w/ Fedora 44 | * properly set conmon logs 45 | * Remove redundant source remove 46 | * conmon: detect cgroup2 and skip OOM handling 47 | * fix cross-compilation 48 | * fixes assumption that socklen_t is always an unsigned long 49 | * Fix coverity scan problem 50 | * Fix small typo in README 51 | * Add VERSION file 52 | * use sd_journal_sendv 53 | * do not leak fd when creating oom file 54 | * Log oom_handling_score failure to debug 55 | * Small tweaks to journald logging 56 | * Adjust OOM score to low value 57 | * Add journald logging option 58 | * allow to specify additional arguments to the OCI runtime 59 | * updated README 60 | * Cirrus: Fix image names 61 | * Fix CRI-O repo. references 62 | * Update Conmon from CRI-O master branch 63 | * Add Meson build configuration 64 | * Fix Makefile to rebuild when sources are touched 65 | * Add path to conmon executable to gitignore 66 | * Remove executable bit from C header file 67 | * Migrate PAPR build/install test to Cirrus 68 | * cirrus: Remove faulty cri-o 1.11 intg. test 69 | * Cirrus: Add CRI-O Integration testing for RHEL 70 | * Update conmon source to latest from cri-o 71 | * Do not overflow local buffer 72 | * Initial PAPR CI check-in 73 | * contrib/spec: initial rpm spec file addition 74 | * Initial code checking from crio 75 | -------------------------------------------------------------------------------- /cmd/conmon-config/conmon-config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | 8 | "github.com/containers/conmon/runner/config" 9 | ) 10 | 11 | func main() { 12 | output := ` 13 | #if !defined(CONFIG_H) 14 | #define CONFIG_H 15 | 16 | #define BUF_SIZE %d 17 | #define STDIO_BUF_SIZE %d 18 | #define CONN_SOCK_BUF_SIZE %d 19 | #define DEFAULT_SOCKET_PATH "%s" 20 | #define WIN_RESIZE_EVENT %d 21 | #define REOPEN_LOGS_EVENT %d 22 | #define TIMED_OUT_MESSAGE "%s" 23 | 24 | #endif // CONFIG_H 25 | ` 26 | if err := os.WriteFile("config.h", []byte(fmt.Sprintf( 27 | output, 28 | config.BufSize, 29 | config.BufSize, 30 | config.ConnSockBufSize, 31 | config.ContainerAttachSocketDir, 32 | config.WinResizeEvent, 33 | config.ReopenLogsEvent, 34 | config.TimedOutMessage)), 35 | 0o644); err != nil { 36 | log.Fatal(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /contrib/spec/conmon.spec.in: -------------------------------------------------------------------------------- 1 | %global debug_package %{nil} 2 | 3 | %global provider github 4 | %global provider_tld com 5 | %global project containers 6 | %global repo conmon 7 | # https://github.com/projectatomic/conmon 8 | %global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo} 9 | %global import_path %{provider_prefix} 10 | %global git0 https://%{import_path} 11 | %global commit0 #COMMIT# 12 | %global shortcommit0 %(c=%{commit0}; echo ${c:0:8}) 13 | 14 | Name: %{repo} 15 | Version: 0 16 | Release: #COMMITDATE#.git%{shortcommit0}%{?dist} 17 | Summary: OCI container runtime monitor 18 | License: ASL 2.0 19 | URL: %{git0} 20 | Source0: %{git0}/archive/%{commit0}/%{name}-%{shortcommit0}.tar.gz 21 | ExclusiveArch: aarch64 %{arm} ppc64le s390x x86_64 22 | BuildRequires: gcc 23 | BuildRequires: glib2-devel 24 | BuildRequires: glibc-devel 25 | BuildRequires: libseccomp-devel 26 | BuildRequires: git 27 | # If go_compiler is not set to 1, there is no virtual provide. Use golang instead. 28 | BuildRequires: golang 29 | BuildRequires: pkgconfig 30 | 31 | %description 32 | %{summary} 33 | 34 | %prep 35 | %autosetup -Sgit -n %{name}-%{shortcommit0} 36 | 37 | %build 38 | %{__make} all 39 | 40 | %install 41 | %{__make} PREFIX=%{buildroot}%{_usr} install 42 | 43 | %check 44 | 45 | #define license tag if not already defined 46 | %{!?_licensedir:%global license %doc} 47 | 48 | %files 49 | %license LICENSE 50 | %doc README.md 51 | %{_usr}/bin/%{name} 52 | %{_mandir}/man8/*.8* 53 | 54 | %changelog 55 | * Mon Oct 01 2018 Lokesh Mandvekar - 0-0.1.gite7805e2 56 | - new package 57 | 58 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | PREFIX ?= /usr/local 2 | DATADIR := ${PREFIX}/share 3 | MANDIR := $(DATADIR)/man 4 | GOMD2MAN ?= go-md2man 5 | 6 | docs: $(patsubst %.md,%,$(wildcard *.8.md)) 7 | 8 | %.8: %.8.md 9 | $(GOMD2MAN) -in $^ -out $@ 10 | 11 | .PHONY: install 12 | install: 13 | install -d ${DESTDIR}/${MANDIR}/man8 14 | install -m 0644 conmon*.8 ${DESTDIR}/${MANDIR}/man8 15 | 16 | .PHONY: clean 17 | clean: 18 | $(RM) conmon*.8 19 | -------------------------------------------------------------------------------- /docs/conmon.8.md: -------------------------------------------------------------------------------- 1 | conmon 8 "User Commands" 2 | ================================================== 3 | 4 | # NAME 5 | 6 | conmon - container monitor utility 7 | 8 | # SYNOPSIS 9 | 10 | conmon [options] 11 | 12 | # DESCRIPTION 13 | 14 | conmon is a command-line program for monitoring and managing the lifecycle of 15 | Linux containers that follow the Open Container Initiative (OCI) format. 16 | 17 | # APPLICATION OPTIONS 18 | 19 | **--api-version** 20 | Conmon API version to use. 21 | 22 | **-b**, **--bundle** 23 | Location of the OCI Bundle path. 24 | 25 | **-c**, **--cid** 26 | Identification of Container. 27 | 28 | **--exec-attach** 29 | Attach to an exec session. 30 | 31 | **-e**, **--exec** 32 | Exec a command into a running container. 33 | 34 | **--exec-process-spec** 35 | Path to the process spec for execution. 36 | 37 | **--exit-command** 38 | Path to the program to execute when the container terminates its execution. 39 | 40 | **--exit-command-arg** 41 | Additional arguments to pass to the exit command. Can be specified multiple times. 42 | 43 | **--exit-delay** 44 | Delay before invoking the exit command (in seconds). 45 | 46 | **--exit-dir** 47 | Path to the directory where exit files are written. 48 | 49 | **-h**, **--help** 50 | Show help options. 51 | 52 | **-i**, **--stdin** 53 | Open up a pipe to pass stdin to the container. 54 | 55 | This option tells conmon to setup the pipe regardless of whether there is a terminal connection. 56 | 57 | **-l**, **--log-path** 58 | Path to store all stdout and stderr messages from the container. 59 | 60 | **--leave-stdin-open** 61 | Leave stdin open when the attached client disconnects. 62 | 63 | **--log-level** 64 | Print debug logs based on the log level. 65 | 66 | **--log-size-max** 67 | Maximum size of the log file (in bytes). 68 | 69 | **--log-tag** 70 | Additional tag to use for logging. 71 | 72 | **--log-label** 73 | Additional label to use for logging. The accepted format is LABEL=VALUE. Can be specified multiple times. 74 | Note that LABEL must contain only uppercase letters, numbers and underscore character. 75 | 76 | **-n**, **--name** 77 | Container name. 78 | 79 | **--no-new-keyring** 80 | Do not create a new session keyring for the container. 81 | 82 | **--no-pivot** 83 | Do not use pivot_root. 84 | 85 | **--no-sync-log** 86 | Do not manually call sync on logs after container shutdown. 87 | 88 | **-0**, **--persist-dir** 89 | Persistent directory for a container that can be used for storing container data. 90 | 91 | **-p**, **--container-pidfile** 92 | PID file for the initial pid inside of the container. 93 | 94 | **-P**, **--conmon-pidfile** 95 | PID file for the conmon process. 96 | 97 | **-r**, **--runtime** 98 | Path to store runtime data for the container. 99 | 100 | **--replace-listen-pid** 101 | Replace listen PID if set for oci-runtime PID. 102 | 103 | **--restore** 104 | Restore a container from a checkpoint. 105 | 106 | **--runtime-arg** 107 | Additional arguments to pass to the runtime. Can be specified multiple times. 108 | 109 | **--runtime-opt** 110 | Additional options to pass to the restore or exec command. Can be specified multiple times. 111 | 112 | **-s**, **--systemd-cgroup** 113 | Enable systemd cgroup manager, rather than use the cgroupfs directly. 114 | 115 | **--socket-dir-path** 116 | Location of container attach sockets. 117 | 118 | **--sync** 119 | Keep the main conmon process as its child by only forking once. 120 | 121 | **--syslog** 122 | Log to syslog (use with cgroupfs cgroup manager). 123 | 124 | **-t**, **--terminal** 125 | Allocate a pseudo-TTY. The default is false. 126 | 127 | When set to true, conmon will allocate a pseudo-tty and attach to the 128 | standard input of the container. This can be used, for example, to run 129 | a throwaway interactive shell. The default is false. 130 | 131 | **-T**, **--timeout** 132 | Kill container after specified timeout in seconds. 133 | 134 | **-u**, **--cuuid** 135 | Specify the Container UUID to use. 136 | 137 | **--version** 138 | Print the version and exit. 139 | 140 | ## SEE ALSO 141 | podman(1), buildah(1), cri-o(1), crun(8), runc(8) 142 | 143 | ## HISTORY 144 | October 2020, Originally compiled by Dan Walsh 145 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/containers/conmon 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/containers/storage v1.48.0 7 | github.com/coreos/go-systemd/v22 v22.5.0 8 | github.com/onsi/ginkgo/v2 v2.15.0 9 | github.com/onsi/gomega v1.31.1 10 | github.com/opencontainers/runtime-tools v0.9.1-0.20230914150019-408c51e934dc 11 | golang.org/x/sys v0.20.0 12 | ) 13 | 14 | require ( 15 | github.com/go-logr/logr v1.3.0 // indirect 16 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 17 | github.com/google/go-cmp v0.6.0 // indirect 18 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect 19 | github.com/hashicorp/errwrap v1.1.0 // indirect 20 | github.com/kr/text v0.2.0 // indirect 21 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 22 | github.com/opencontainers/runtime-spec v1.1.0-rc.3 // indirect 23 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect 24 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 25 | golang.org/x/net v0.19.0 // indirect 26 | golang.org/x/text v0.14.0 // indirect 27 | golang.org/x/tools v0.16.1 // indirect 28 | google.golang.org/protobuf v1.30.0 // indirect 29 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 2 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 3 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 4 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 5 | github.com/containers/storage v1.48.0 h1:wiPs8J2xiFoOEAhxHDRtP6A90Jzj57VqzLRXOqeizns= 6 | github.com/containers/storage v1.48.0/go.mod h1:pRp3lkRo2qodb/ltpnudoXggrviRmaCmU5a5GhTBae0= 7 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 8 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= 14 | github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 15 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 16 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 17 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 18 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 19 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 20 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 22 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 23 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= 24 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 25 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 26 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 27 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 28 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 29 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 30 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 31 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 32 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 33 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 34 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 35 | github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= 36 | github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= 37 | github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= 38 | github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= 39 | github.com/opencontainers/runtime-spec v1.1.0-rc.3 h1:l04uafi6kxByhbxev7OWiuUv0LZxEsYUfDWZ6bztAuU= 40 | github.com/opencontainers/runtime-spec v1.1.0-rc.3/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= 41 | github.com/opencontainers/runtime-tools v0.9.1-0.20230914150019-408c51e934dc h1:d2hUh5O6MRBvStV55MQ8we08t42zSTqBbscoQccWmMc= 42 | github.com/opencontainers/runtime-tools v0.9.1-0.20230914150019-408c51e934dc/go.mod h1:8tx1helyqhUC65McMm3x7HmOex8lO2/v9zPuxmKHurs= 43 | github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU= 44 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 45 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 46 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 47 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 48 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 49 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 50 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= 51 | github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= 52 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 53 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 54 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 55 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 56 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 57 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 58 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 59 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 60 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 61 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 62 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 63 | golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= 64 | golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 65 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 66 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 67 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 68 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 71 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 72 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 73 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 74 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | -------------------------------------------------------------------------------- /hack/get_ci_vm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # For help and usage information, simply execute the script w/o any arguments. 5 | # 6 | # This script is intended to be run by Red Hat podman developers who need 7 | # to debug problems specifically related to Cirrus-CI automated testing. 8 | # It requires that you have been granted prior access to create VMs in 9 | # google-cloud. For non-Red Hat contributors, VMs are available as-needed, 10 | # with supervision upon request. 11 | 12 | set -e 13 | 14 | SCRIPT_FILEPATH=$(realpath "${BASH_SOURCE[0]}") 15 | SCRIPT_DIRPATH=$(dirname "$SCRIPT_FILEPATH") 16 | REPO_DIRPATH=$(realpath "$SCRIPT_DIRPATH/../") 17 | 18 | # Help detect if we were called by get_ci_vm container 19 | GET_CI_VM="${GET_CI_VM:-0}" 20 | in_get_ci_vm() { 21 | if ((GET_CI_VM==0)); then 22 | echo "Error: $1 is not intended for use in this context" 23 | exit 2 24 | fi 25 | } 26 | 27 | # get_ci_vm APIv1 container entrypoint calls into this script 28 | # to obtain required repo. specific configuration options. 29 | if [[ "$1" == "--config" ]]; then 30 | in_get_ci_vm "$1" 31 | cat < /dev/stderr 49 | source ./contrib/cirrus/lib.sh 50 | echo "+ Running environment setup" > /dev/stderr 51 | ./contrib/cirrus/setup_environment.sh 52 | else 53 | # Create and access VM for specified Cirrus-CI task 54 | mkdir -p $HOME/.config/gcloud/ssh 55 | podman run -it --rm \ 56 | --tz=local \ 57 | -e NAME="$USER" \ 58 | -e SRCDIR=/src \ 59 | -e GCLOUD_ZONE="$GCLOUD_ZONE" \ 60 | -e DEBUG="${DEBUG:-0}" \ 61 | -v $REPO_DIRPATH:/src:O \ 62 | -v $HOME/.config/gcloud:/root/.config/gcloud:z \ 63 | -v $HOME/.config/gcloud/ssh:/root/.ssh:z \ 64 | quay.io/libpod/get_ci_vm:latest "$@" 65 | fi 66 | -------------------------------------------------------------------------------- /hack/github-actions-setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | declare -A VERSIONS=( 5 | ["cni-plugins"]=v1.3.0 6 | ["runc"]=v1.1.14 7 | ["crun"]=1.17 8 | ["bats"]=v1.9.0 9 | ) 10 | 11 | main() { 12 | set -x 13 | prepare_system 14 | 15 | install_packages 16 | install_conmon 17 | install_bats 18 | install_critools 19 | install_runc 20 | install_crun 21 | install_cni_plugins 22 | install_testdeps 23 | setup_etc_subid 24 | } 25 | 26 | prepare_system() { 27 | sudo systemctl stop docker 28 | sudo ufw disable 29 | 30 | # enable necessary kernel modules 31 | sudo ip6tables --list >/dev/null 32 | 33 | # enable necessary sysctls 34 | sudo sysctl -w net.ipv4.conf.all.route_localnet=1 35 | sudo sysctl -w net.ipv4.ip_forward=1 36 | # needed for crictl test 37 | sudo sysctl -w net.bridge.bridge-nf-call-iptables=1 38 | sudo iptables -t nat -I POSTROUTING -s 127.0.0.0/8 ! -d 127.0.0.0/8 -j MASQUERADE 39 | } 40 | 41 | remove_packages() { 42 | sudo apt-get remove \ 43 | conmon \ 44 | containernetworking-plugins 45 | } 46 | 47 | install_packages() { 48 | . /etc/os-release 49 | CRIU_REPO="https://download.opensuse.org/repositories/devel:/tools:/criu/xUbuntu_$VERSION_ID" 50 | 51 | curl -fSsL $CRIU_REPO/Release.key | sudo apt-key add - 52 | echo "deb $CRIU_REPO/ /" | sudo tee /etc/apt/sources.list.d/criu.list 53 | 54 | sudo apt update 55 | sudo apt install -y \ 56 | autoconf \ 57 | automake \ 58 | conntrack \ 59 | criu \ 60 | libaio-dev \ 61 | libapparmor-dev \ 62 | libbtrfs-dev \ 63 | libcap-dev \ 64 | libdevmapper-dev \ 65 | libfuse-dev \ 66 | libgpgme11-dev \ 67 | libglib2.0-dev \ 68 | libnet1-dev \ 69 | libnl-3-dev \ 70 | libprotobuf-c-dev \ 71 | libprotobuf-dev \ 72 | libseccomp-dev \ 73 | libsystemd-dev \ 74 | libtool \ 75 | libudev-dev \ 76 | libyajl-dev \ 77 | sed \ 78 | socat \ 79 | uuid-dev 80 | } 81 | 82 | install_conmon() { 83 | sudo make install.bin 84 | conmon --version 85 | } 86 | 87 | install_bats() { 88 | git clone https://github.com/bats-core/bats-core 89 | pushd bats-core 90 | git checkout "${VERSIONS["bats"]}" 91 | sudo ./install.sh /usr/local 92 | popd 93 | rm -rf bats-core 94 | mkdir -p ~/.parallel 95 | touch ~/.parallel/will-cite 96 | } 97 | 98 | install_critools() { 99 | URL=https://github.com/kubernetes-sigs/cri-tools 100 | 101 | git clone $URL 102 | pushd cri-tools 103 | sudo -E PATH="$PATH" make BINDIR=/usr/bin install 104 | popd 105 | sudo rm -rf cri-tools 106 | sudo critest --version 107 | sudo crictl --version 108 | } 109 | 110 | install_cni_plugins() { 111 | URL=https://github.com/containernetworking/plugins/releases/download 112 | TARBALL=cni-plugins-linux-amd64-${VERSIONS["cni-plugins"]}.tgz 113 | CNI_DIR=/opt/cni/bin 114 | sudo mkdir -p "$CNI_DIR" 115 | wget -O "$TARBALL" $URL/"${VERSIONS["cni-plugins"]}"/"$TARBALL" 116 | sudo tar xf "$TARBALL" -C "$CNI_DIR" 117 | rm "$TARBALL" 118 | ls -lah "$CNI_DIR" 119 | } 120 | 121 | install_runc() { 122 | URL=https://github.com/opencontainers/runc/releases/download/"${VERSIONS["runc"]}" 123 | BINARY=/usr/sbin/runc 124 | sudo wget -O "$BINARY" "$URL"/runc.amd64 125 | sudo chmod +x "$BINARY" 126 | 127 | # Verify the SHA256 128 | SUMFILE=runc.sha256sum 129 | wget "$URL"/$SUMFILE 130 | grep -qw "$(sha256sum "$BINARY" | awk '{ print $1 }')" $SUMFILE 131 | rm $SUMFILE 132 | 133 | runc --version 134 | } 135 | 136 | install_crun() { 137 | URL=https://github.com/containers/crun/releases/download/"${VERSIONS["crun"]}"/crun-"${VERSIONS["crun"]}"-linux-amd64 138 | 139 | BINARY=/usr/bin/crun 140 | sudo wget -O "$BINARY" "$URL" 141 | sudo chmod +x "$BINARY" 142 | 143 | crun --version 144 | } 145 | 146 | install_testdeps() { 147 | CLONE_PATH=$(go env GOPATH)/src/github.com/cri-o 148 | mkdir -p "$CLONE_PATH" 149 | pushd "$CLONE_PATH" 150 | 151 | URL=https://github.com/cri-o/cri-o 152 | git clone $URL 153 | pushd cri-o 154 | make "$(pwd)"/build/bin/ginkgo 155 | sudo cp build/bin/ginkgo /usr/bin 156 | ginkgo version 157 | 158 | sudo mkdir -p /etc/containers/registries.d 159 | sudo cp test/policy.json /etc/containers 160 | sudo cp test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml 161 | sudo cp test/registries.conf /etc/containers/registries.conf 162 | popd 163 | popd 164 | } 165 | 166 | setup_etc_subid() { 167 | echo "containers:200000:65536" | sudo tee -a /etc/subuid 168 | echo "containers:200000:65536" | sudo tee -a /etc/subgid 169 | 170 | } 171 | 172 | main "$@" 173 | -------------------------------------------------------------------------------- /hack/seccomp-notify.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | if $(printf '#include \nvoid main(){struct seccomp_notif_sizes s;}' | cc -x c - -o /dev/null 2> /dev/null && pkg-config --atleast-version 2.5.0 libseccomp); then 3 | echo "0" 4 | fi 5 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('conmon', 'c', 2 | version : run_command('cat', files('VERSION'),).stdout().strip(), 3 | license : 'Apache-2.0', 4 | default_options : [ 5 | 'c_std=c99', 6 | ], 7 | meson_version : '>= 0.46', 8 | ) 9 | 10 | git_commit = '' 11 | 12 | git = find_program('git', required : false) 13 | if git.found() 14 | git_commit = run_command( 15 | git, 16 | ['--git-dir=@0@/.git'.format(meson.source_root()), 17 | 'rev-parse', 'HEAD']).stdout().strip() 18 | git_dirty = run_command( 19 | git, 20 | ['--git-dir=@0@/.git'.format(meson.source_root()), 21 | 'status', '--porcelain', '--untracked-files=no']).stdout().strip() 22 | if git_dirty != '' 23 | git_commit = git_commit + '-dirty' 24 | endif 25 | endif 26 | 27 | conf = configuration_data() 28 | conf.set_quoted('VERSION', meson.project_version()) 29 | conf.set_quoted('GIT_COMMIT', git_commit) 30 | 31 | add_project_arguments('-Os', '-Wall', '-Werror', 32 | '-DVERSION=' + conf.get('VERSION'), 33 | '-DGIT_COMMIT=' + conf.get('GIT_COMMIT'), 34 | language : 'c') 35 | 36 | glib = dependency('glib-2.0') 37 | seccomp = dependency('libseccomp', version : '>= 2.5.2') 38 | if seccomp.found() 39 | add_project_arguments('-DUSE_SECCOMP=1', language : 'c') 40 | endif 41 | 42 | cc = meson.get_compiler('c') 43 | null_dep = dependency('', required : false) 44 | if cc.has_function('dlopen') 45 | libdl = null_dep 46 | else 47 | libdl = cc.find_library('dl') 48 | endif 49 | 50 | sd_journal = dependency('libsystemd', required : false) 51 | if sd_journal.found() 52 | add_project_arguments('-DUSE_JOURNALD=1', language : 'c') 53 | endif 54 | 55 | executable('conmon', 56 | ['src/conmon.c', 57 | 'src/config.h', 58 | 'src/cmsg.c', 59 | 'src/cmsg.h', 60 | 'src/ctr_logging.c', 61 | 'src/ctr_logging.h', 62 | 'src/cgroup.c', 63 | 'src/cgroup.h', 64 | 'src/cli.c', 65 | 'src/cli.h', 66 | 'src/conn_sock.c', 67 | 'src/conn_sock.h', 68 | 'src/ctr_exit.c', 69 | 'src/ctr_exit.h', 70 | 'src/ctrl.c', 71 | 'src/ctrl.h', 72 | 'src/ctr_logging.c', 73 | 'src/ctr_logging.h', 74 | 'src/ctr_stdio.c', 75 | 'src/ctr_stdio.h', 76 | 'src/globals.c', 77 | 'src/globals.h', 78 | 'src/close_fds.c', 79 | 'src/close_fds.h', 80 | 'src/oom.c', 81 | 'src/oom.h', 82 | 'src/parent_pipe_fd.c', 83 | 'src/parent_pipe_fd.h', 84 | 'src/runtime_args.c', 85 | 'src/runtime_args.h', 86 | 'src/utils.c', 87 | 'src/utils.h', 88 | 'src/seccomp_notify.c', 89 | 'src/seccomp_notify.h'], 90 | dependencies : [glib, libdl, sd_journal, seccomp], 91 | install : true, 92 | install_dir : join_paths(get_option('libexecdir'), 'podman'), 93 | ) 94 | -------------------------------------------------------------------------------- /nix/default-amd64.nix: -------------------------------------------------------------------------------- 1 | default.nix -------------------------------------------------------------------------------- /nix/default-arm64.nix: -------------------------------------------------------------------------------- 1 | (import ./nixpkgs.nix { 2 | crossSystem = { 3 | config = "aarch64-unknown-linux-gnu"; 4 | }; 5 | overlays = [ (import ./overlay.nix) ]; 6 | }).callPackage ./derivation.nix 7 | { } 8 | -------------------------------------------------------------------------------- /nix/default-ppc64le.nix: -------------------------------------------------------------------------------- 1 | (import ./nixpkgs.nix { 2 | crossSystem = { 3 | config = "powerpc64le-unknown-linux-gnu"; 4 | }; 5 | overlays = [ (import ./overlay.nix) ]; 6 | }).callPackage ./derivation.nix 7 | { } 8 | -------------------------------------------------------------------------------- /nix/default-riscv64.nix: -------------------------------------------------------------------------------- 1 | (import ./nixpkgs.nix { 2 | crossSystem = { 3 | config = "riscv64-unknown-linux-gnu"; 4 | }; 5 | overlays = [ (import ./overlay.nix) ]; 6 | }).callPackage ./derivation.nix 7 | { } 8 | -------------------------------------------------------------------------------- /nix/default-s390x.nix: -------------------------------------------------------------------------------- 1 | (import ./nixpkgs.nix { 2 | crossSystem = { 3 | # TODO: Switch back to glibc when 4 | # https://github.com/NixOS/nixpkgs/issues/306473 5 | # is resolved. 6 | config = "s390x-unknown-linux-musl"; 7 | }; 8 | overlays = [ (import ./overlay.nix) ]; 9 | }).callPackage ./derivation.nix 10 | { } 11 | -------------------------------------------------------------------------------- /nix/default.nix: -------------------------------------------------------------------------------- 1 | (import ./nixpkgs.nix { 2 | overlays = [ (import ./overlay.nix) ]; 3 | }).callPackage ./derivation.nix 4 | { } 5 | -------------------------------------------------------------------------------- /nix/derivation.nix: -------------------------------------------------------------------------------- 1 | { stdenv 2 | , pkgs 3 | }: 4 | with pkgs; stdenv.mkDerivation rec { 5 | name = "conmon"; 6 | # Use Pure to avoid exuding the .git directory 7 | src = nix-gitignore.gitignoreSourcePure [ ../.gitignore ] ./..; 8 | vendorHash = null; 9 | doCheck = false; 10 | enableParallelBuilding = true; 11 | outputs = [ "out" ]; 12 | nativeBuildInputs = with buildPackages; [ 13 | gitMinimal 14 | pkg-config 15 | ]; 16 | buildInputs = lib.optionals (!stdenv.hostPlatform.isMusl) [ 17 | glibc.static 18 | ] ++ [ 19 | pkgsStatic.glib 20 | libseccomp 21 | ]; 22 | prePatch = '' 23 | export CFLAGS='-static -pthread' 24 | export LDFLAGS='-s -w -static-libgcc -static' 25 | export EXTRA_LDFLAGS='-s -w -linkmode external -extldflags "-static -lm"' 26 | ''; 27 | buildPhase = '' 28 | patchShebangs . 29 | make 30 | ''; 31 | installPhase = '' 32 | install -Dm755 bin/conmon $out/bin/conmon 33 | ''; 34 | } 35 | -------------------------------------------------------------------------------- /nix/nixpkgs.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://github.com/nixos/nixpkgs", 3 | "rev": "17d58b30575bb965966d67e8ca1070f9a71cfb41", 4 | "date": "2024-04-25T11:03:10+03:00", 5 | "path": "/nix/store/01ql69ya47924icr0sm5vclrcl98ryr2-nixpkgs", 6 | "sha256": "1szc653551qmd1iqk6lqcnamsr7bdd5qrfimi4r1ma199cblpd14", 7 | "hash": "sha256-JLRLF0spqBoyiTW6jEtr62RdlWWYmoljaBWHUkYx7Os=", 8 | "fetchLFS": false, 9 | "fetchSubmodules": false, 10 | "deepClone": false, 11 | "leaveDotGit": false 12 | } 13 | -------------------------------------------------------------------------------- /nix/nixpkgs.nix: -------------------------------------------------------------------------------- 1 | let 2 | json = builtins.fromJSON (builtins.readFile ./nixpkgs.json); 3 | nixpkgs = import (builtins.fetchTarball { 4 | name = "nixos-unstable"; 5 | url = "${json.url}/archive/${json.rev}.tar.gz"; 6 | inherit (json) sha256; 7 | }); 8 | in nixpkgs 9 | -------------------------------------------------------------------------------- /nix/overlay.nix: -------------------------------------------------------------------------------- 1 | self: super: 2 | { 3 | libseccomp = super.libseccomp.overrideAttrs (x: { 4 | doCheck = false; 5 | dontDisableStatic = true; 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /rpm/conmon.spec: -------------------------------------------------------------------------------- 1 | %global with_debug 1 2 | 3 | %if 0%{?with_debug} 4 | %global _find_debuginfo_dwz_opts %{nil} 5 | %global _dwz_low_mem_die_limit 0 6 | %else 7 | %global debug_package %{nil} 8 | %endif 9 | 10 | %if %{defined rhel} 11 | %bcond_with docs 12 | %else 13 | %bcond_without docs 14 | %endif 15 | 16 | Name: conmon 17 | %if %{defined rhel} 18 | Epoch: 3 19 | %else 20 | Epoch: 2 21 | %endif 22 | Version: 2.1.13 23 | License: Apache-2.0 24 | Release: %autorelease 25 | Summary: OCI container runtime monitor 26 | URL: https://github.com/containers/%{name} 27 | # Tarball fetched from upstream 28 | Source0: %{url}/archive/v%{version}.tar.gz 29 | %if %{with docs} 30 | ExclusiveArch: %{golang_arches_future} 31 | BuildRequires: go-md2man 32 | %endif 33 | BuildRequires: gcc 34 | BuildRequires: git-core 35 | BuildRequires: glib2-devel 36 | BuildRequires: libseccomp-devel 37 | BuildRequires: systemd-devel 38 | BuildRequires: systemd-libs 39 | BuildRequires: make 40 | Requires: glib2 41 | Requires: systemd-libs 42 | Requires: libseccomp 43 | 44 | %description 45 | %{summary}. 46 | 47 | %prep 48 | %autosetup -Sgit %{name}-%{version} 49 | sed -i 's/install.bin: bin\/conmon/install.bin:/' Makefile 50 | 51 | %build 52 | %{__make} DEBUGFLAG="-g" bin/conmon 53 | 54 | %if %{with docs} 55 | %{__make} GOMD2MAN=go-md2man -C docs 56 | %endif 57 | 58 | %install 59 | %{__make} PREFIX=%{buildroot}%{_prefix} install.bin 60 | 61 | %if %{with docs} 62 | %{__make} PREFIX=%{buildroot}%{_prefix} -C docs install 63 | %endif 64 | 65 | #define license tag if not already defined 66 | %{!?_licensedir:%global license %doc} 67 | 68 | %files 69 | %license LICENSE 70 | %doc README.md 71 | %{_bindir}/%{name} 72 | 73 | %if %{with docs} 74 | %{_mandir}/man8/%{name}.8.gz 75 | %endif 76 | 77 | %changelog 78 | %autochangelog 79 | -------------------------------------------------------------------------------- /runner/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | // BufSize is the size of buffers passed in to sockets 5 | BufSize = 8192 6 | // ConnSockBufSize is the size of the socket used for 7 | // to attach to the container 8 | ConnSockBufSize = 32768 9 | // WinResizeEvent is the event code the caller program will 10 | // send along the ctrl fd to signal conmon to resize 11 | // the pty window 12 | WinResizeEvent = 1 13 | // ReopenLogsEvent is the event code the caller program will 14 | // send along the ctrl fd to signal conmon to reopen the log files 15 | ReopenLogsEvent = 2 16 | // TimedOutMessage is the message sent back to the caller by conmon 17 | // when a container times out 18 | TimedOutMessage = "command timed out" 19 | ) 20 | -------------------------------------------------------------------------------- /runner/config/config_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package config 4 | 5 | const ( 6 | ContainerAttachSocketDir = "/var/run/crio" 7 | ) 8 | -------------------------------------------------------------------------------- /runner/config/config_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package config 4 | 5 | const ( 6 | ContainerAttachSocketDir = "C:\\crio\\run\\" 7 | ) 8 | -------------------------------------------------------------------------------- /runner/conmon/conmon.go: -------------------------------------------------------------------------------- 1 | package conmon 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "strconv" 10 | ) 11 | 12 | var ErrConmonNotStarted = errors.New("conmon instance is not started") 13 | 14 | type ConmonInstance struct { 15 | args []string 16 | cmd *exec.Cmd 17 | started bool 18 | path string 19 | pidFile string 20 | stdout io.Writer 21 | stderr io.Writer 22 | stdin io.Reader 23 | 24 | parentStartPipe *os.File 25 | parentAttachPipe *os.File 26 | parentSyncPipe *os.File 27 | childSyncPipe *os.File 28 | childStartPipe *os.File 29 | childAttachPipe *os.File 30 | } 31 | 32 | func CreateAndExecConmon(options ...ConmonOption) (*ConmonInstance, error) { 33 | ci, err := NewConmonInstance(options...) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | ci.Start() 39 | return ci, nil 40 | } 41 | 42 | func NewConmonInstance(options ...ConmonOption) (*ConmonInstance, error) { 43 | ci := &ConmonInstance{ 44 | args: make([]string, 0), 45 | } 46 | for _, option := range options { 47 | if err := option(ci); err != nil { 48 | return nil, err 49 | } 50 | } 51 | 52 | // TODO verify path more 53 | if ci.path == "" { 54 | return nil, errors.New("conmon path not specified") 55 | } 56 | 57 | ci.cmd = exec.Command(ci.path, ci.args...) 58 | ci.configurePipeEnv() 59 | 60 | ci.cmd.Stdout = ci.stdout 61 | ci.cmd.Stderr = ci.stderr 62 | ci.cmd.Stdin = ci.stdin 63 | return ci, nil 64 | } 65 | 66 | func (ci *ConmonInstance) Start() error { 67 | ci.started = true 68 | return ci.cmd.Start() 69 | } 70 | 71 | func (ci *ConmonInstance) Wait() error { 72 | if !ci.started { 73 | return ErrConmonNotStarted 74 | } 75 | defer func() { 76 | ci.childSyncPipe.Close() 77 | ci.childStartPipe.Close() 78 | ci.childAttachPipe.Close() 79 | }() 80 | return ci.cmd.Wait() 81 | } 82 | 83 | func (ci *ConmonInstance) Stdout() (io.Writer, error) { 84 | if !ci.started { 85 | return nil, ErrConmonNotStarted 86 | } 87 | return ci.cmd.Stdout, nil 88 | } 89 | 90 | func (ci *ConmonInstance) Stderr() (io.Writer, error) { 91 | if !ci.started { 92 | return nil, ErrConmonNotStarted 93 | } 94 | return ci.cmd.Stderr, nil 95 | } 96 | 97 | func (ci *ConmonInstance) Pid() (int, error) { 98 | if ci.pidFile == "" { 99 | return -1, errors.New("conmon pid file not specified") 100 | } 101 | if !ci.started { 102 | return -1, ErrConmonNotStarted 103 | } 104 | 105 | pid, err := readConmonPidFile(ci.pidFile) 106 | if err != nil { 107 | return -1, fmt.Errorf("failed to get conmon pid: %w", err) 108 | } 109 | return pid, nil 110 | } 111 | 112 | // readConmonPidFile attempts to read conmon's pid from its pid file 113 | func readConmonPidFile(pidFile string) (int, error) { 114 | // Let's try reading the Conmon pid at the same time. 115 | if pidFile != "" { 116 | contents, err := os.ReadFile(pidFile) 117 | if err != nil { 118 | return -1, err 119 | } 120 | // Convert it to an int 121 | conmonPID, err := strconv.Atoi(string(contents)) 122 | if err != nil { 123 | return -1, err 124 | } 125 | return conmonPID, nil 126 | } 127 | return 0, nil 128 | } 129 | 130 | func (ci *ConmonInstance) Cleanup() { 131 | ci.closePipesOnCleanup() 132 | } 133 | -------------------------------------------------------------------------------- /runner/conmon/options.go: -------------------------------------------------------------------------------- 1 | package conmon 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | type ConmonOption func(*ConmonInstance) error 12 | 13 | func WithVersion() ConmonOption { 14 | return func(ci *ConmonInstance) error { 15 | return ci.addArgs("--version") 16 | } 17 | } 18 | 19 | func WithStdout(stdout io.Writer) ConmonOption { 20 | return func(ci *ConmonInstance) error { 21 | ci.stdout = stdout 22 | return nil 23 | } 24 | } 25 | 26 | func WithStderr(stderr io.Writer) ConmonOption { 27 | return func(ci *ConmonInstance) error { 28 | ci.stderr = stderr 29 | return nil 30 | } 31 | } 32 | 33 | func WithStdin(stdin io.Reader) ConmonOption { 34 | return func(ci *ConmonInstance) error { 35 | ci.stdin = stdin 36 | return nil 37 | } 38 | } 39 | 40 | func WithPath(path string) ConmonOption { 41 | return func(ci *ConmonInstance) error { 42 | ci.path = path 43 | return nil 44 | } 45 | } 46 | 47 | func WithContainerID(ctrID string) ConmonOption { 48 | return func(ci *ConmonInstance) error { 49 | return ci.addArgs("--cid", ctrID) 50 | } 51 | } 52 | 53 | func WithContainerUUID(ctrUUID string) ConmonOption { 54 | return func(ci *ConmonInstance) error { 55 | return ci.addArgs("--cuuid", ctrUUID) 56 | } 57 | } 58 | 59 | func WithRuntimePath(path string) ConmonOption { 60 | return func(ci *ConmonInstance) error { 61 | return ci.addArgs("--runtime", path) 62 | } 63 | } 64 | 65 | func WithLogDriver(driver, path string) ConmonOption { 66 | return func(ci *ConmonInstance) error { 67 | fullDriver := path 68 | if driver != "" { 69 | fullDriver = fmt.Sprintf("%s:%s", driver, path) 70 | } 71 | return ci.addArgs("--log-path", fullDriver) 72 | } 73 | } 74 | 75 | func WithLogPath(path string) ConmonOption { 76 | return func(ci *ConmonInstance) error { 77 | return ci.addArgs("--log-path", path) 78 | } 79 | } 80 | 81 | func WithBundlePath(path string) ConmonOption { 82 | return func(ci *ConmonInstance) error { 83 | return ci.addArgs("--bundle", path) 84 | } 85 | } 86 | 87 | func WithSyslog() ConmonOption { 88 | return func(ci *ConmonInstance) error { 89 | return ci.addArgs("--syslog") 90 | } 91 | } 92 | 93 | func WithLogLevel(level string) ConmonOption { 94 | return func(ci *ConmonInstance) error { 95 | // TODO verify level is right 96 | return ci.addArgs("--log-level", level) 97 | } 98 | } 99 | 100 | func WithSocketPath(path string) ConmonOption { 101 | return func(ci *ConmonInstance) error { 102 | // TODO verify path is right 103 | // TODO automatically add container ID? right now it's callers responsibility 104 | return ci.addArgs("--socket-dir-path", path) 105 | } 106 | } 107 | 108 | func WithContainerPidFile(path string) ConmonOption { 109 | return func(ci *ConmonInstance) error { 110 | // TODO verify path is right 111 | return ci.addArgs("--container-pidfile", path) 112 | } 113 | } 114 | 115 | func WithRuntimeConfig(path string) ConmonOption { 116 | return func(ci *ConmonInstance) error { 117 | // TODO verify path is right 118 | return ci.addArgs("--container-pidfile", path) 119 | } 120 | } 121 | 122 | func WithConmonPidFile(path string) ConmonOption { 123 | return func(ci *ConmonInstance) error { 124 | // TODO verify path is right 125 | ci.pidFile = path 126 | return ci.addArgs("--conmon-pidfile", path) 127 | } 128 | } 129 | 130 | func WithStartPipe() ConmonOption { 131 | return func(ci *ConmonInstance) error { 132 | read, write, err := newPipe() 133 | if err != nil { 134 | return err 135 | } 136 | ci.parentStartPipe = write 137 | ci.childStartPipe = read 138 | return nil 139 | } 140 | } 141 | 142 | func WithAttachPipe() ConmonOption { 143 | return func(ci *ConmonInstance) error { 144 | read, write, err := newPipe() 145 | if err != nil { 146 | return err 147 | } 148 | ci.parentAttachPipe = read 149 | ci.childAttachPipe = write 150 | return nil 151 | } 152 | } 153 | 154 | func WithSyncPipe() ConmonOption { 155 | return func(ci *ConmonInstance) error { 156 | read, write, err := newPipe() 157 | if err != nil { 158 | return err 159 | } 160 | ci.parentSyncPipe = read 161 | ci.childSyncPipe = write 162 | return nil 163 | } 164 | } 165 | 166 | // newPipe creates a unix socket pair for communication 167 | func newPipe() (read *os.File, write *os.File, err error) { 168 | fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_SEQPACKET|unix.SOCK_CLOEXEC, 0) 169 | if err != nil { 170 | return nil, nil, err 171 | } 172 | return os.NewFile(uintptr(fds[1]), "read"), os.NewFile(uintptr(fds[0]), "write"), nil 173 | } 174 | 175 | func (ci *ConmonInstance) addArgs(args ...string) error { 176 | ci.args = append(ci.args, args...) 177 | return nil 178 | } 179 | -------------------------------------------------------------------------------- /runner/conmon/pipes.go: -------------------------------------------------------------------------------- 1 | package conmon 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "os" 9 | "regexp" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // These errors are adapted from github.com/containers/podman:libpod/define 15 | // And copied to reduce the vendor surface area of this library 16 | var ( 17 | // ErrInternal indicates an internal library error 18 | ErrInternal = fmt.Errorf("internal error") 19 | // ErrOCIRuntime indicates a generic error from the OCI runtime 20 | ErrOCIRuntime = fmt.Errorf("OCI runtime error") 21 | // ErrOCIRuntimePermissionDenied indicates the OCI runtime attempted to invoke a command that returned 22 | // a permission denied error 23 | ErrOCIRuntimePermissionDenied = fmt.Errorf("OCI permission denied") 24 | // ErrOCIRuntimeNotFound indicates the OCI runtime attempted to invoke a command 25 | // that was not found 26 | ErrOCIRuntimeNotFound = fmt.Errorf("OCI runtime attempted to invoke a command that was not found") 27 | ) 28 | 29 | func (ci *ConmonInstance) configurePipeEnv() error { 30 | if ci.cmd == nil { 31 | return errors.New("conmon instance command must be configured") 32 | } 33 | if ci.started { 34 | return errors.New("conmon instance environment cannot be configured after it's started") 35 | } 36 | // TODO handle PreserveFDs 37 | preserveFDs := 0 38 | fdCount := 3 39 | if ci.childSyncPipe != nil { 40 | ci.cmd.Env = append(ci.cmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", preserveFDs+fdCount)) 41 | ci.cmd.ExtraFiles = append(ci.cmd.ExtraFiles, ci.childSyncPipe) 42 | fdCount++ 43 | } 44 | if ci.childStartPipe != nil { 45 | ci.cmd.Env = append(ci.cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", preserveFDs+fdCount)) 46 | ci.cmd.ExtraFiles = append(ci.cmd.ExtraFiles, ci.childStartPipe) 47 | fdCount++ 48 | } 49 | if ci.childAttachPipe != nil { 50 | ci.cmd.Env = append(ci.cmd.Env, fmt.Sprintf("_OCI_ATTACHPIPE=%d", preserveFDs+fdCount)) 51 | ci.cmd.ExtraFiles = append(ci.cmd.ExtraFiles, ci.childAttachPipe) 52 | fdCount++ 53 | } 54 | return nil 55 | } 56 | 57 | func (ci *ConmonInstance) ContainerExitCode() (int, error) { 58 | return readConmonPipeData(ci.parentSyncPipe) 59 | } 60 | 61 | // readConmonPipeData attempts to read a syncInfo struct from the pipe 62 | // TODO podman checks for ociLog capability 63 | func readConmonPipeData(pipe *os.File) (int, error) { 64 | // syncInfo is used to return data from monitor process to daemon 65 | type syncInfo struct { 66 | Data int `json:"data"` 67 | Message string `json:"message,omitempty"` 68 | } 69 | 70 | // Wait to get container pid from conmon 71 | type syncStruct struct { 72 | si *syncInfo 73 | err error 74 | } 75 | ch := make(chan syncStruct) 76 | go func() { 77 | var si *syncInfo 78 | rdr := bufio.NewReader(pipe) 79 | b, err := rdr.ReadBytes('\n') 80 | if err != nil { 81 | ch <- syncStruct{err: err} 82 | } 83 | if err := json.Unmarshal(b, &si); err != nil { 84 | ch <- syncStruct{err: err} 85 | return 86 | } 87 | ch <- syncStruct{si: si} 88 | }() 89 | 90 | data := -1 91 | select { 92 | case ss := <-ch: 93 | if ss.err != nil { 94 | return -1, fmt.Errorf("error received on processing data from conmon pipe: %w", ss.err) 95 | } 96 | if ss.si.Data < 0 { 97 | if ss.si.Message != "" { 98 | return ss.si.Data, getOCIRuntimeError(ss.si.Message) 99 | } 100 | return ss.si.Data, fmt.Errorf("conmon invocation failed: %w", ErrInternal) 101 | } 102 | data = ss.si.Data 103 | case <-time.After(1 * time.Minute): 104 | return -1, fmt.Errorf("conmon invocation timeout: %w", ErrInternal) 105 | } 106 | return data, nil 107 | } 108 | 109 | func getOCIRuntimeError(runtimeMsg string) error { 110 | // TODO base off of log level 111 | // includeFullOutput := logrus.GetLevel() == logrus.DebugLevel 112 | includeFullOutput := true 113 | 114 | if match := regexp.MustCompile("(?i).*permission denied.*|.*operation not permitted.*").FindString(runtimeMsg); match != "" { 115 | errStr := match 116 | if includeFullOutput { 117 | errStr = runtimeMsg 118 | } 119 | return fmt.Errorf("%s: %w", strings.Trim(errStr, "\n"), ErrOCIRuntimePermissionDenied) 120 | } 121 | if match := regexp.MustCompile("(?i).*executable file not found in.*|.*no such file or directory.*").FindString(runtimeMsg); match != "" { 122 | errStr := match 123 | if includeFullOutput { 124 | errStr = runtimeMsg 125 | } 126 | return fmt.Errorf("%s: %w", strings.Trim(errStr, "\n"), ErrOCIRuntimeNotFound) 127 | } 128 | return fmt.Errorf("%s: %w", strings.Trim(runtimeMsg, "\n"), ErrOCIRuntime) 129 | } 130 | 131 | func (ci *ConmonInstance) closePipesOnCleanup() { 132 | ci.parentSyncPipe.Close() 133 | ci.parentStartPipe.Close() 134 | ci.parentAttachPipe.Close() 135 | } 136 | -------------------------------------------------------------------------------- /runner/conmon_test/conmon_test.go: -------------------------------------------------------------------------------- 1 | package conmon_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/containers/conmon/runner/conmon" 10 | . "github.com/onsi/ginkgo/v2" 11 | . "github.com/onsi/gomega" 12 | "golang.org/x/sys/unix" 13 | ) 14 | 15 | var _ = Describe("conmon", func() { 16 | Describe("version", func() { 17 | It("Should return conmon version", func() { 18 | out, _ := getConmonOutputGivenOptions( 19 | conmon.WithVersion(), 20 | conmon.WithPath(conmonPath), 21 | ) 22 | Expect(out).To(ContainSubstring("conmon version")) 23 | Expect(out).To(ContainSubstring("commit")) 24 | }) 25 | }) 26 | Describe("no container ID", func() { 27 | It("should fail", func() { 28 | _, err := getConmonOutputGivenOptions( 29 | conmon.WithPath(conmonPath), 30 | ) 31 | Expect(err).To(ContainSubstring("conmon: Container ID not provided. Use --cid")) 32 | }) 33 | }) 34 | Describe("no container UUID", func() { 35 | It("should fail", func() { 36 | _, err := getConmonOutputGivenOptions( 37 | conmon.WithPath(conmonPath), 38 | conmon.WithContainerID(ctrID), 39 | ) 40 | Expect(err).To(ContainSubstring("Container UUID not provided. Use --cuuid")) 41 | }) 42 | }) 43 | Describe("runtime path", func() { 44 | It("no path should fail", func() { 45 | _, err := getConmonOutputGivenOptions( 46 | conmon.WithPath(conmonPath), 47 | conmon.WithContainerID(ctrID), 48 | conmon.WithContainerUUID(ctrID), 49 | ) 50 | Expect(err).To(ContainSubstring("Runtime path not provided. Use --runtime")) 51 | }) 52 | It("invalid path should fail", func() { 53 | _, err := getConmonOutputGivenOptions( 54 | conmon.WithPath(conmonPath), 55 | conmon.WithContainerID(ctrID), 56 | conmon.WithContainerUUID(ctrID), 57 | conmon.WithRuntimePath(invalidPath), 58 | ) 59 | Expect(err).To(ContainSubstring(fmt.Sprintf("Runtime path %s is not valid", invalidPath))) 60 | }) 61 | }) 62 | Describe("ctr logs", func() { 63 | var tmpDir string 64 | var tmpLogPath string 65 | var origCwd string 66 | BeforeEach(func() { 67 | tmpDir = GinkgoT().TempDir() 68 | tmpLogPath = filepath.Join(tmpDir, "log") 69 | var err error 70 | origCwd, err = os.Getwd() 71 | Expect(err).To(BeNil()) 72 | }) 73 | AfterEach(func() { 74 | for { 75 | // There is a race condition on the directory deletion 76 | // as conmon could still be running and creating files 77 | // under tmpDir. Attempt rmdir again if it fails with 78 | // ENOTEMPTY. 79 | err := os.RemoveAll(tmpDir) 80 | if err != nil && errors.Is(err, unix.ENOTEMPTY) { 81 | continue 82 | } 83 | Expect(err).To(BeNil()) 84 | break 85 | } 86 | Expect(os.RemoveAll(tmpDir)).To(BeNil()) 87 | err := os.Chdir(origCwd) 88 | Expect(err).To(BeNil()) 89 | }) 90 | It("no log driver should fail", func() { 91 | _, stderr := getConmonOutputGivenOptions( 92 | conmon.WithPath(conmonPath), 93 | conmon.WithContainerID(ctrID), 94 | conmon.WithContainerUUID(ctrID), 95 | conmon.WithRuntimePath(validPath), 96 | ) 97 | Expect(stderr).To(ContainSubstring("Log driver not provided. Use --log-path")) 98 | }) 99 | It("empty log driver should fail", func() { 100 | _, stderr := getConmonOutputGivenOptions( 101 | conmon.WithPath(conmonPath), 102 | conmon.WithContainerID(ctrID), 103 | conmon.WithContainerUUID(ctrID), 104 | conmon.WithRuntimePath(validPath), 105 | conmon.WithLogPath(""), 106 | ) 107 | Expect(stderr).To(ContainSubstring("log-path must not be empty")) 108 | }) 109 | It("empty log driver and path should fail", func() { 110 | _, stderr := getConmonOutputGivenOptions( 111 | conmon.WithPath(conmonPath), 112 | conmon.WithContainerID(ctrID), 113 | conmon.WithContainerUUID(ctrID), 114 | conmon.WithRuntimePath(validPath), 115 | conmon.WithLogPath(":"), 116 | ) 117 | Expect(stderr).To(ContainSubstring("log-path must not be empty")) 118 | }) 119 | It("k8s-file requires a filename", func() { 120 | _, stderr := getConmonOutputGivenOptions( 121 | conmon.WithPath(conmonPath), 122 | conmon.WithContainerID(ctrID), 123 | conmon.WithContainerUUID(ctrID), 124 | conmon.WithRuntimePath(validPath), 125 | conmon.WithLogPath("k8s-file"), 126 | ) 127 | Expect(stderr).To(ContainSubstring("k8s-file requires a filename")) 128 | }) 129 | It("k8s-file: requires a filename", func() { 130 | _, stderr := getConmonOutputGivenOptions( 131 | conmon.WithPath(conmonPath), 132 | conmon.WithContainerID(ctrID), 133 | conmon.WithContainerUUID(ctrID), 134 | conmon.WithRuntimePath(validPath), 135 | conmon.WithLogPath("k8s-file:"), 136 | ) 137 | Expect(stderr).To(ContainSubstring("k8s-file requires a filename")) 138 | }) 139 | It("log driver as path should pass", func() { 140 | _, stderr := getConmonOutputGivenOptions( 141 | conmon.WithPath(conmonPath), 142 | conmon.WithContainerID(ctrID), 143 | conmon.WithContainerUUID(ctrID), 144 | conmon.WithRuntimePath(validPath), 145 | conmon.WithLogDriver("", tmpLogPath), 146 | ) 147 | Expect(stderr).To(BeEmpty()) 148 | 149 | _, err := os.Stat(tmpLogPath) 150 | Expect(err).To(BeNil()) 151 | }) 152 | It("log driver as k8s-file:path should pass", func() { 153 | _, stderr := getConmonOutputGivenOptions( 154 | conmon.WithPath(conmonPath), 155 | conmon.WithContainerID(ctrID), 156 | conmon.WithContainerUUID(ctrID), 157 | conmon.WithRuntimePath(validPath), 158 | conmon.WithLogDriver("k8s-file", tmpLogPath), 159 | ) 160 | Expect(stderr).To(BeEmpty()) 161 | 162 | _, err := os.Stat(tmpLogPath) 163 | Expect(err).To(BeNil()) 164 | }) 165 | It("log driver as :path should pass", func() { 166 | _, stderr := getConmonOutputGivenOptions( 167 | conmon.WithPath(conmonPath), 168 | conmon.WithContainerID(ctrID), 169 | conmon.WithContainerUUID(ctrID), 170 | conmon.WithRuntimePath(validPath), 171 | conmon.WithLogPath(":"+tmpLogPath), 172 | ) 173 | Expect(stderr).To(BeEmpty()) 174 | 175 | _, err := os.Stat(tmpLogPath) 176 | Expect(err).To(BeNil()) 177 | }) 178 | It("log driver as none should pass", func() { 179 | direrr := os.Chdir(tmpDir) 180 | Expect(direrr).To(BeNil()) 181 | 182 | _, stderr := getConmonOutputGivenOptions( 183 | conmon.WithPath(conmonPath), 184 | conmon.WithContainerID(ctrID), 185 | conmon.WithContainerUUID(ctrID), 186 | conmon.WithRuntimePath(validPath), 187 | conmon.WithLogDriver("none", ""), 188 | ) 189 | Expect(stderr).To(BeEmpty()) 190 | 191 | _, err := os.Stat("none") 192 | Expect(err).NotTo(BeNil()) 193 | }) 194 | It("log driver as off should pass", func() { 195 | direrr := os.Chdir(tmpDir) 196 | Expect(direrr).To(BeNil()) 197 | 198 | _, stderr := getConmonOutputGivenOptions( 199 | conmon.WithPath(conmonPath), 200 | conmon.WithContainerID(ctrID), 201 | conmon.WithContainerUUID(ctrID), 202 | conmon.WithRuntimePath(validPath), 203 | conmon.WithLogDriver("off", ""), 204 | ) 205 | Expect(stderr).To(BeEmpty()) 206 | 207 | _, err := os.Stat("off") 208 | Expect(err).NotTo(BeNil()) 209 | }) 210 | It("log driver as null should pass", func() { 211 | direrr := os.Chdir(tmpDir) 212 | Expect(direrr).To(BeNil()) 213 | 214 | _, stderr := getConmonOutputGivenOptions( 215 | conmon.WithPath(conmonPath), 216 | conmon.WithContainerID(ctrID), 217 | conmon.WithContainerUUID(ctrID), 218 | conmon.WithRuntimePath(validPath), 219 | conmon.WithLogDriver("null", ""), 220 | ) 221 | Expect(stderr).To(BeEmpty()) 222 | 223 | _, err := os.Stat("none") 224 | Expect(err).NotTo(BeNil()) 225 | }) 226 | It("log driver as journald should pass", func() { 227 | direrr := os.Chdir(tmpDir) 228 | Expect(direrr).To(BeNil()) 229 | 230 | _, stderr := getConmonOutputGivenOptions( 231 | conmon.WithPath(conmonPath), 232 | conmon.WithContainerID(ctrID), 233 | conmon.WithContainerUUID(ctrID), 234 | conmon.WithRuntimePath(validPath), 235 | conmon.WithLogDriver("journald", ""), 236 | ) 237 | Expect(stderr).To(BeEmpty()) 238 | 239 | _, err := os.Stat("journald") 240 | Expect(err).NotTo(BeNil()) 241 | }) 242 | It("log driver as :journald should pass", func() { 243 | direrr := os.Chdir(tmpDir) 244 | Expect(direrr).To(BeNil()) 245 | 246 | _, stderr := getConmonOutputGivenOptions( 247 | conmon.WithPath(conmonPath), 248 | conmon.WithContainerID(ctrID), 249 | conmon.WithContainerUUID(ctrID), 250 | conmon.WithRuntimePath(validPath), 251 | conmon.WithLogPath(":journald"), 252 | ) 253 | Expect(stderr).To(BeEmpty()) 254 | 255 | _, err := os.Stat("journald") 256 | Expect(err).To(BeNil()) 257 | }) 258 | It("log driver as journald with short cid should fail", func() { 259 | // conmon requires a cid of len > 12 260 | shortCtrID := "abcdefghijkl" 261 | _, stderr := getConmonOutputGivenOptions( 262 | conmon.WithPath(conmonPath), 263 | conmon.WithContainerID(shortCtrID), 264 | conmon.WithContainerUUID(shortCtrID), 265 | conmon.WithRuntimePath(validPath), 266 | conmon.WithLogDriver("journald", ""), 267 | ) 268 | Expect(stderr).To(ContainSubstring("Container ID must be longer than 12 characters")) 269 | }) 270 | It("log driver as k8s-file with path should pass", func() { 271 | _, stderr := getConmonOutputGivenOptions( 272 | conmon.WithPath(conmonPath), 273 | conmon.WithContainerID(ctrID), 274 | conmon.WithContainerUUID(ctrID), 275 | conmon.WithRuntimePath(validPath), 276 | conmon.WithLogDriver("k8s-file", tmpLogPath), 277 | ) 278 | Expect(stderr).To(BeEmpty()) 279 | 280 | _, err := os.Stat(tmpLogPath) 281 | Expect(err).To(BeNil()) 282 | }) 283 | It("log driver as k8s-file with invalid path should fail", func() { 284 | _, stderr := getConmonOutputGivenOptions( 285 | conmon.WithPath(conmonPath), 286 | conmon.WithContainerID(ctrID), 287 | conmon.WithContainerUUID(ctrID), 288 | conmon.WithRuntimePath(validPath), 289 | conmon.WithLogDriver("k8s-file", invalidPath), 290 | ) 291 | Expect(stderr).To(ContainSubstring("Failed to open log file")) 292 | }) 293 | It("log driver as invalid driver should fail", func() { 294 | invalidLogDriver := "invalid" 295 | _, stderr := getConmonOutputGivenOptions( 296 | conmon.WithPath(conmonPath), 297 | conmon.WithContainerID(ctrID), 298 | conmon.WithContainerUUID(ctrID), 299 | conmon.WithRuntimePath(validPath), 300 | conmon.WithLogDriver(invalidLogDriver, tmpLogPath), 301 | ) 302 | Expect(stderr).To(ContainSubstring("No such log driver " + invalidLogDriver)) 303 | }) 304 | It("log driver as invalid driver with a blank path should fail", func() { 305 | invalidLogDriver := "invalid" 306 | _, stderr := getConmonOutputGivenOptions( 307 | conmon.WithPath(conmonPath), 308 | conmon.WithContainerID(ctrID), 309 | conmon.WithContainerUUID(ctrID), 310 | conmon.WithRuntimePath(validPath), 311 | conmon.WithLogDriver(invalidLogDriver, ""), 312 | ) 313 | Expect(stderr).To(ContainSubstring("No such log driver " + invalidLogDriver)) 314 | }) 315 | It("multiple log drivers should pass", func() { 316 | _, stderr := getConmonOutputGivenOptions( 317 | conmon.WithPath(conmonPath), 318 | conmon.WithContainerID(ctrID), 319 | conmon.WithContainerUUID(ctrID), 320 | conmon.WithRuntimePath(validPath), 321 | conmon.WithLogDriver("k8s-file", tmpLogPath), 322 | conmon.WithLogDriver("journald", ""), 323 | ) 324 | Expect(stderr).To(BeEmpty()) 325 | 326 | _, err := os.Stat(tmpLogPath) 327 | Expect(err).To(BeNil()) 328 | }) 329 | It("multiple log drivers with one invalid should fail", func() { 330 | invalidLogDriver := "invalid" 331 | _, stderr := getConmonOutputGivenOptions( 332 | conmon.WithPath(conmonPath), 333 | conmon.WithContainerID(ctrID), 334 | conmon.WithContainerUUID(ctrID), 335 | conmon.WithRuntimePath(validPath), 336 | conmon.WithLogDriver("k8s-file", tmpLogPath), 337 | conmon.WithLogDriver(invalidLogDriver, tmpLogPath), 338 | ) 339 | Expect(stderr).To(ContainSubstring("No such log driver " + invalidLogDriver)) 340 | }) 341 | }) 342 | }) 343 | -------------------------------------------------------------------------------- /runner/conmon_test/ctr_logs_test.go: -------------------------------------------------------------------------------- 1 | package conmon_test 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/containers/conmon/runner/conmon" 8 | . "github.com/onsi/ginkgo/v2" 9 | . "github.com/onsi/gomega" 10 | ) 11 | 12 | var _ = Describe("conmon ctr logs", func() { 13 | var tmpDir string 14 | var tmpLogPath string 15 | const invalidLogDriver = "invalid" 16 | BeforeEach(func() { 17 | tmpDir = GinkgoT().TempDir() 18 | tmpLogPath = filepath.Join(tmpDir, "log") 19 | }) 20 | It("no log driver should fail", func() { 21 | _, stderr := getConmonOutputGivenLogOpts() 22 | Expect(stderr).To(ContainSubstring("Log driver not provided. Use --log-path")) 23 | }) 24 | It("log driver as path should pass", func() { 25 | _, stderr := getConmonOutputGivenLogOpts(conmon.WithLogDriver("", tmpLogPath)) 26 | Expect(stderr).To(BeEmpty()) 27 | 28 | _, err := os.Stat(tmpLogPath) 29 | Expect(err).To(BeNil()) 30 | }) 31 | It("log driver as journald should pass", func() { 32 | _, stderr := getConmonOutputGivenLogOpts(conmon.WithLogDriver("journald", "")) 33 | Expect(stderr).To(BeEmpty()) 34 | }) 35 | It("log driver as journald with short cid should fail", func() { 36 | // conmon requires a cid of len > 12 37 | shortCtrID := "abcdefghijkl" 38 | 39 | _, stderr := getConmonOutputGivenLogOpts( 40 | conmon.WithLogDriver("journald", ""), 41 | conmon.WithContainerID(shortCtrID), 42 | ) 43 | Expect(stderr).To(ContainSubstring("Container ID must be longer than 12 characters")) 44 | }) 45 | It("log driver as k8s-file with path should pass", func() { 46 | _, stderr := getConmonOutputGivenLogOpts(conmon.WithLogDriver("k8s-file", tmpLogPath)) 47 | Expect(stderr).To(BeEmpty()) 48 | 49 | _, err := os.Stat(tmpLogPath) 50 | Expect(err).To(BeNil()) 51 | }) 52 | It("log driver as passthrough should pass", func() { 53 | stdout, stderr := getConmonOutputGivenLogOpts(conmon.WithLogDriver("passthrough", "")) 54 | Expect(stdout).To(BeEmpty()) 55 | Expect(stderr).To(BeEmpty()) 56 | }) 57 | It("log driver as k8s-file with invalid path should fail", func() { 58 | _, stderr := getConmonOutputGivenLogOpts(conmon.WithLogDriver("k8s-file", invalidPath)) 59 | Expect(stderr).To(ContainSubstring("Failed to open log file")) 60 | }) 61 | It("log driver as invalid driver should fail", func() { 62 | _, stderr := getConmonOutputGivenLogOpts(conmon.WithLogDriver(invalidLogDriver, tmpLogPath)) 63 | Expect(stderr).To(ContainSubstring("No such log driver " + invalidLogDriver)) 64 | }) 65 | It("multiple log drivers should pass", func() { 66 | _, stderr := getConmonOutputGivenLogOpts( 67 | conmon.WithLogDriver("k8s-file", tmpLogPath), 68 | conmon.WithLogDriver("journald", ""), 69 | ) 70 | Expect(stderr).To(BeEmpty()) 71 | 72 | _, err := os.Stat(tmpLogPath) 73 | Expect(err).To(BeNil()) 74 | }) 75 | It("multiple log drivers with one invalid should fail", func() { 76 | _, stderr := getConmonOutputGivenLogOpts( 77 | conmon.WithLogDriver("k8s-file", tmpLogPath), 78 | conmon.WithLogDriver(invalidLogDriver, tmpLogPath), 79 | ) 80 | Expect(stderr).To(ContainSubstring("No such log driver " + invalidLogDriver)) 81 | }) 82 | }) 83 | 84 | func getConmonOutputGivenLogOpts(logDriverOpts ...conmon.ConmonOption) (string, string) { 85 | opts := []conmon.ConmonOption{ 86 | conmon.WithPath(conmonPath), 87 | conmon.WithContainerID(ctrID), 88 | conmon.WithContainerUUID(ctrID), 89 | conmon.WithRuntimePath(validPath), 90 | } 91 | opts = append(opts, logDriverOpts...) 92 | return getConmonOutputGivenOptions(opts...) 93 | } 94 | -------------------------------------------------------------------------------- /runner/conmon_test/runtime_test.go: -------------------------------------------------------------------------------- 1 | package conmon_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "time" 8 | 9 | "github.com/containers/conmon/runner/conmon" 10 | "github.com/containers/storage/pkg/stringid" 11 | . "github.com/onsi/ginkgo/v2" 12 | . "github.com/onsi/gomega" 13 | "github.com/opencontainers/runtime-tools/generate" 14 | ) 15 | 16 | var _ = Describe("runc", func() { 17 | var ( 18 | tmpDir string 19 | tmpLogPath string 20 | tmpPidFile string 21 | tmpRootfs string 22 | ) 23 | BeforeEach(func() { 24 | // save busy box binary if we don't have it 25 | Expect(cacheBusyBox()).To(BeNil()) 26 | 27 | // create tmpDir 28 | tmpDir = GinkgoT().TempDir() 29 | 30 | // generate logging path 31 | tmpLogPath = filepath.Join(tmpDir, "log") 32 | 33 | // generate container ID 34 | ctrID = stringid.GenerateNonCryptoID() 35 | 36 | // create the rootfs of the "container" 37 | tmpRootfs = filepath.Join(tmpDir, "rootfs") 38 | Expect(os.MkdirAll(tmpRootfs, 0o755)).To(BeNil()) 39 | 40 | tmpPidFile = filepath.Join(tmpDir, "pidfile") 41 | 42 | busyboxPath := filepath.Join(tmpRootfs, "busybox") 43 | Expect(os.Link(busyboxDest, busyboxPath)).To(BeNil()) 44 | Expect(os.Chmod(busyboxPath, 0o777)).To(BeNil()) 45 | 46 | // finally, create config.json 47 | _, err := generateRuntimeConfig(tmpDir, tmpRootfs) 48 | Expect(err).To(BeNil()) 49 | }) 50 | AfterEach(func() { 51 | Expect(runRuntimeCommand("delete", "-f", ctrID)).To(BeNil()) 52 | }) 53 | It("simple runtime test", func() { 54 | stdout, stderr := getConmonOutputGivenOptions( 55 | conmon.WithPath(conmonPath), 56 | conmon.WithContainerID(ctrID), 57 | conmon.WithContainerUUID(ctrID), 58 | conmon.WithRuntimePath(runtimePath), 59 | conmon.WithLogDriver("k8s-file", tmpLogPath), 60 | conmon.WithBundlePath(tmpDir), 61 | conmon.WithSocketPath(tmpDir), 62 | conmon.WithSyslog(), 63 | conmon.WithLogLevel("trace"), 64 | conmon.WithContainerPidFile(tmpPidFile), 65 | conmon.WithConmonPidFile(fmt.Sprintf("%s/conmon-pidfile", tmpDir)), 66 | conmon.WithSyncPipe(), 67 | ) 68 | Expect(stdout).To(BeEmpty()) 69 | Expect(stderr).To(BeEmpty()) 70 | 71 | Expect(runRuntimeCommand("start", ctrID)).To(BeNil()) 72 | // Make sure we write the file before checking if it was written 73 | time.Sleep(100 * time.Millisecond) 74 | 75 | Expect(getFileContents(tmpLogPath)).To(ContainSubstring("busybox")) 76 | Expect(getFileContents(tmpPidFile)).To(Not(BeEmpty())) 77 | }) 78 | }) 79 | 80 | func getFileContents(filename string) string { 81 | b, err := os.ReadFile(filename) 82 | Expect(err).To(BeNil()) 83 | return string(b) 84 | } 85 | 86 | func generateRuntimeConfig(bundlePath, rootfs string) (string, error) { 87 | configPath := filepath.Join(bundlePath, "config.json") 88 | g, err := generate.New("linux") 89 | if err != nil { 90 | return "", err 91 | } 92 | g.SetProcessCwd("/") 93 | g.SetProcessArgs([]string{"/busybox", "echo", "busybox"}) 94 | g.SetRootPath(rootfs) 95 | 96 | if err := g.SaveToFile(configPath, generate.ExportOptions{}); err != nil { 97 | return "", err 98 | } 99 | return configPath, nil 100 | } 101 | -------------------------------------------------------------------------------- /runner/conmon_test/suite_test.go: -------------------------------------------------------------------------------- 1 | package conmon_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "os/exec" 10 | "strconv" 11 | "testing" 12 | 13 | "github.com/containers/conmon/runner/conmon" 14 | "github.com/coreos/go-systemd/v22/sdjournal" 15 | . "github.com/onsi/ginkgo/v2" 16 | . "github.com/onsi/gomega" 17 | ) 18 | 19 | var ( 20 | conmonPath = "/usr/bin/conmon" 21 | runtimePath = "/usr/bin/runc" 22 | busyboxSource = "https://busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox" 23 | busyboxDestDir = "/tmp/conmon-test-images" 24 | busyboxDest = "/tmp/conmon-test-images/busybox" 25 | ctrID = "abcdefghijklm" 26 | validPath = "/tmp" 27 | invalidPath = "/not/a/path" 28 | ) 29 | 30 | func TestConmon(t *testing.T) { 31 | configureSuiteFromEnv() 32 | RegisterFailHandler(Fail) 33 | RunSpecs(t, "Conmon Suite") 34 | } 35 | 36 | func getConmonOutputGivenOptions(options ...conmon.ConmonOption) (string, string) { 37 | var stdout bytes.Buffer 38 | var stderr bytes.Buffer 39 | var stdin bytes.Buffer 40 | 41 | options = append(options, conmon.WithStdout(&stdout), conmon.WithStderr(&stderr), conmon.WithStdin(&stdin)) 42 | 43 | ci, err := conmon.CreateAndExecConmon(options...) 44 | Expect(err).To(BeNil()) 45 | 46 | defer ci.Cleanup() 47 | 48 | ci.Wait() 49 | 50 | pid, _ := ci.Pid() 51 | if pid < 0 { 52 | return stdout.String(), stderr.String() 53 | } 54 | 55 | _, err = ci.ContainerExitCode() 56 | Expect(err).To(BeNil()) 57 | 58 | journalerr, err := getConmonJournalOutput(pid, 3) 59 | Expect(err).To(BeNil()) 60 | 61 | alljournalout, err := getConmonJournalOutput(pid, -1) 62 | Expect(err).To(BeNil()) 63 | fmt.Fprintf(GinkgoWriter, alljournalout+"\n") 64 | 65 | return stdout.String(), stderr.String() + journalerr 66 | } 67 | 68 | func getConmonJournalOutput(pid int, level int) (string, error) { 69 | matches := []sdjournal.Match{ 70 | { 71 | Field: sdjournal.SD_JOURNAL_FIELD_COMM, 72 | Value: "conmon", 73 | }, 74 | { 75 | Field: sdjournal.SD_JOURNAL_FIELD_PID, 76 | Value: strconv.Itoa(pid), 77 | }, 78 | } 79 | if level > 0 { 80 | matches = append(matches, sdjournal.Match{ 81 | Field: sdjournal.SD_JOURNAL_FIELD_PRIORITY, 82 | Value: strconv.Itoa(level), 83 | }) 84 | } 85 | r, err := sdjournal.NewJournalReader(sdjournal.JournalReaderConfig{ 86 | Matches: matches, 87 | Formatter: formatter, 88 | }) 89 | if err != nil { 90 | return "", err 91 | } 92 | defer r.Close() 93 | 94 | return readAllFromBuffer(r) 95 | } 96 | 97 | func formatter(entry *sdjournal.JournalEntry) (string, error) { 98 | return entry.Fields[sdjournal.SD_JOURNAL_FIELD_MESSAGE], nil 99 | } 100 | 101 | func readAllFromBuffer(r io.ReadCloser) (string, error) { 102 | bufLen := 16384 103 | stringOutput := "" 104 | 105 | bytes := make([]byte, bufLen) 106 | // /me complains about no do-while in go 107 | ec, err := r.Read(bytes) 108 | for ec != 0 && err == nil { 109 | // because we are reusing bytes, we need to make 110 | // sure the old data doesn't get into the new line 111 | bytestr := string(bytes[:ec]) 112 | stringOutput += string(bytestr) 113 | ec, err = r.Read(bytes) 114 | } 115 | if err != nil && err != io.EOF { 116 | return stringOutput, err 117 | } 118 | return stringOutput, nil 119 | } 120 | 121 | func configureSuiteFromEnv() { 122 | if path := os.Getenv("CONMON_BINARY"); path != "" { 123 | conmonPath = path 124 | } 125 | if path := os.Getenv("RUNTIME_BINARY"); path != "" { 126 | runtimePath = path 127 | } 128 | } 129 | 130 | func cacheBusyBox() error { 131 | if _, err := os.Stat(busyboxDest); err == nil { 132 | return nil 133 | } 134 | if err := os.MkdirAll(busyboxDestDir, 0o755); err != nil && !os.IsExist(err) { 135 | return err 136 | } 137 | if err := downloadFile(busyboxSource, busyboxDest); err != nil { 138 | return err 139 | } 140 | if err := os.Chmod(busyboxDest, 0o777); err != nil { 141 | return err 142 | } 143 | return nil 144 | } 145 | 146 | // source: https://progolang.com/how-to-download-files-in-go/ 147 | // downloadFile will download a url and store it in local filepath. 148 | // It writes to the destination file as it downloads it, without 149 | // loading the entire file into memory. 150 | func downloadFile(url string, filepath string) error { 151 | // Create the file 152 | out, err := os.Create(filepath) 153 | if err != nil { 154 | return err 155 | } 156 | defer out.Close() 157 | 158 | // Get the data 159 | resp, err := http.Get(url) 160 | if err != nil { 161 | return err 162 | } 163 | defer resp.Body.Close() 164 | 165 | // Write the body to file 166 | _, err = io.Copy(out, resp.Body) 167 | if err != nil { 168 | return err 169 | } 170 | 171 | return nil 172 | } 173 | 174 | func runRuntimeCommand(args ...string) error { 175 | var stdout bytes.Buffer 176 | var stderr bytes.Buffer 177 | 178 | cmd := exec.Command(runtimePath, args...) 179 | cmd.Stdout = &stdout 180 | cmd.Stderr = &stderr 181 | if err := cmd.Run(); err != nil { 182 | return err 183 | } 184 | cmd.Run() 185 | stdoutString := stdout.String() 186 | if stdoutString != "" { 187 | fmt.Fprintf(GinkgoWriter, stdoutString+"\n") 188 | } 189 | return nil 190 | } 191 | -------------------------------------------------------------------------------- /src/cgroup.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include "cgroup.h" 4 | #include "globals.h" 5 | #include "utils.h" 6 | #include "cli.h" 7 | #include "config.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #ifdef __linux__ 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #endif 19 | 20 | #ifndef CGROUP2_SUPER_MAGIC 21 | #define CGROUP2_SUPER_MAGIC 0x63677270 22 | #endif 23 | 24 | #define CGROUP_ROOT "/sys/fs/cgroup" 25 | 26 | int oom_event_fd = -1; 27 | int oom_cgroup_fd = -1; 28 | 29 | #ifdef __linux__ 30 | 31 | static char *process_cgroup_subsystem_path(int pid, bool cgroup2, const char *subsystem); 32 | static void setup_oom_handling_cgroup_v2(int pid); 33 | static void setup_oom_handling_cgroup_v1(int pid); 34 | static gboolean oom_cb_cgroup_v2(int fd, GIOCondition condition, G_GNUC_UNUSED gpointer user_data); 35 | static gboolean oom_cb_cgroup_v1(int fd, GIOCondition condition, G_GNUC_UNUSED gpointer user_data); 36 | static int create_oom_files(); 37 | static int create_oom_file(const char *base_path); 38 | 39 | void setup_oom_handling(int pid) 40 | { 41 | struct statfs sfs; 42 | 43 | if (statfs("/sys/fs/cgroup", &sfs) == 0 && sfs.f_type == CGROUP2_SUPER_MAGIC) { 44 | is_cgroup_v2 = TRUE; 45 | setup_oom_handling_cgroup_v2(pid); 46 | return; 47 | } 48 | setup_oom_handling_cgroup_v1(pid); 49 | } 50 | 51 | /* 52 | * Returns the path for specified controller name for a pid. 53 | * Returns NULL on error. 54 | */ 55 | static char *process_cgroup_subsystem_path(int pid, bool cgroup2, const char *subsystem) 56 | { 57 | _cleanup_free_ char *cgroups_file_path = g_strdup_printf("/proc/%d/cgroup", pid); 58 | _cleanup_fclose_ FILE *fp = fopen(cgroups_file_path, "re"); 59 | if (fp == NULL) { 60 | nwarnf("Failed to open cgroups file: %s", cgroups_file_path); 61 | return NULL; 62 | } 63 | 64 | _cleanup_free_ char *line = NULL; 65 | ssize_t read; 66 | size_t len = 0; 67 | char *ptr, *path; 68 | while ((read = getline(&line, &len, fp)) != -1) { 69 | _cleanup_strv_ char **subsystems = NULL; 70 | ptr = strchr(line, ':'); 71 | if (ptr == NULL) { 72 | nwarnf("Error parsing cgroup, ':' not found: %s", line); 73 | return NULL; 74 | } 75 | ptr++; 76 | path = strchr(ptr, ':'); 77 | if (path == NULL) { 78 | nwarnf("Error parsing cgroup, second ':' not found: %s", line); 79 | return NULL; 80 | } 81 | *path = 0; 82 | path++; 83 | if (cgroup2) { 84 | char *subsystem_path = g_strdup_printf("%s%s", CGROUP_ROOT, path); 85 | subsystem_path[strlen(subsystem_path) - 1] = '\0'; 86 | return subsystem_path; 87 | } 88 | subsystems = g_strsplit(ptr, ",", -1); 89 | for (int i = 0; subsystems[i] != NULL; i++) { 90 | if (strcmp(subsystems[i], subsystem) == 0) { 91 | char *subpath = strchr(subsystems[i], '='); 92 | if (subpath == NULL) { 93 | subpath = ptr; 94 | } else { 95 | *subpath = 0; 96 | } 97 | 98 | char *subsystem_path = g_strdup_printf("%s/%s%s", CGROUP_ROOT, subpath, path); 99 | subsystem_path[strlen(subsystem_path) - 1] = '\0'; 100 | return subsystem_path; 101 | } 102 | } 103 | } 104 | 105 | return NULL; 106 | } 107 | 108 | 109 | static void setup_oom_handling_cgroup_v2(int pid) 110 | { 111 | cgroup2_path = process_cgroup_subsystem_path(pid, true, ""); 112 | if (!cgroup2_path) { 113 | nwarn("Failed to get cgroup path. Container may have exited"); 114 | return; 115 | } 116 | 117 | _cleanup_free_ char *memory_events_file_path = g_build_filename(cgroup2_path, "memory.events", NULL); 118 | 119 | _cleanup_close_ int ifd = -1; 120 | if ((ifd = inotify_init()) < 0) { 121 | nwarnf("Failed to create inotify fd"); 122 | return; 123 | } 124 | 125 | if (inotify_add_watch(ifd, memory_events_file_path, IN_MODIFY) < 0) { 126 | nwarnf("Failed to add inotify watch for %s", memory_events_file_path); 127 | return; 128 | } 129 | 130 | /* Move ownership to inotify_fd. */ 131 | inotify_fd = ifd; 132 | ifd = -1; 133 | 134 | g_unix_fd_add(inotify_fd, G_IO_IN, oom_cb_cgroup_v2, NULL); 135 | } 136 | 137 | static void setup_oom_handling_cgroup_v1(int pid) 138 | { 139 | /* Setup OOM notification for container process */ 140 | _cleanup_free_ char *memory_cgroup_path = process_cgroup_subsystem_path(pid, false, "memory"); 141 | if (!memory_cgroup_path) { 142 | nwarn("Failed to get memory cgroup path. Container may have exited"); 143 | return; 144 | } 145 | 146 | /* this will be cleaned up in oom_cb_cgroup_v1 */ 147 | char *memory_cgroup_file_path = g_build_filename(memory_cgroup_path, "cgroup.event_control", NULL); 148 | _cleanup_close_ int cfd = open(memory_cgroup_file_path, O_WRONLY | O_CLOEXEC); 149 | if (cfd == -1) { 150 | nwarnf("Failed to open %s", memory_cgroup_file_path); 151 | g_free(memory_cgroup_file_path); 152 | return; 153 | } 154 | 155 | _cleanup_free_ char *memory_cgroup_file_oom_path = g_build_filename(memory_cgroup_path, "memory.oom_control", NULL); 156 | 157 | oom_cgroup_fd = open(memory_cgroup_file_oom_path, O_RDONLY | O_CLOEXEC); /* Not closed */ 158 | if (oom_cgroup_fd == -1) 159 | pexitf("Failed to open %s", memory_cgroup_file_oom_path); 160 | 161 | if ((oom_event_fd = eventfd(0, EFD_CLOEXEC)) == -1) 162 | pexit("Failed to create eventfd"); 163 | 164 | _cleanup_free_ char *data = g_strdup_printf("%d %d", oom_event_fd, oom_cgroup_fd); 165 | if (write_all(cfd, data, strlen(data)) < 0) { 166 | /* This used to be fatal, but we make it advisory and stumble on because 167 | * https://github.com/torvalds/linux/commit/2343e88d238f5de973d609d861c505890f94f22e 168 | * disables this interface in PREEMPT_RT kernel configs. 169 | */ 170 | nwarnf("Failed to write to cgroup.event_control"); 171 | g_free(memory_cgroup_file_path); 172 | return; 173 | } 174 | 175 | g_unix_fd_add(oom_event_fd, G_IO_IN, oom_cb_cgroup_v1, memory_cgroup_file_path); 176 | } 177 | 178 | static gboolean oom_cb_cgroup_v2(int fd, GIOCondition condition, G_GNUC_UNUSED gpointer user_data) 179 | { 180 | const size_t events_size = sizeof(struct inotify_event) + NAME_MAX + 1; 181 | char events[events_size]; 182 | 183 | /* Drop the inotify events. */ 184 | ssize_t num_read = read(fd, &events, events_size); 185 | if (num_read < 0) { 186 | nwarn("Failed to read oom event from eventfd in v2"); 187 | return G_SOURCE_CONTINUE; 188 | } 189 | 190 | gboolean ret = G_SOURCE_REMOVE; 191 | if ((condition & G_IO_IN) != 0) { 192 | ret = check_cgroup2_oom(); 193 | } 194 | 195 | if (ret == G_SOURCE_REMOVE) { 196 | /* End of input */ 197 | close(fd); 198 | inotify_fd = -1; 199 | } 200 | 201 | return ret; 202 | } 203 | 204 | /* user_data is expected to be the container's cgroup.event_control file, 205 | * used to verify the cgroup hasn't been cleaned up */ 206 | static gboolean oom_cb_cgroup_v1(int fd, GIOCondition condition, gpointer user_data) 207 | { 208 | char *cgroup_event_control_path = (char *)user_data; 209 | if ((condition & G_IO_IN) == 0) { 210 | /* End of input */ 211 | close(fd); 212 | oom_event_fd = -1; 213 | g_free(cgroup_event_control_path); 214 | return G_SOURCE_REMOVE; 215 | } 216 | 217 | /* Attempt to read the container's cgroup path. 218 | * if the cgroup.memory_control file does not exist, 219 | * we know one of the events on this fd was a cgroup removal 220 | */ 221 | gboolean cgroup_removed = FALSE; 222 | if (access(cgroup_event_control_path, F_OK) < 0) { 223 | ndebugf("Memory cgroup removal event received"); 224 | cgroup_removed = TRUE; 225 | } 226 | 227 | /* there are three cases we need to worry about: 228 | * oom kill happened (1 event) 229 | * cgroup was removed (1 event) 230 | * oom kill happened and cgroup was removed (2 events) 231 | */ 232 | uint64_t event_count; 233 | ssize_t num_read = read(fd, &event_count, sizeof(uint64_t)); 234 | if (num_read < 0) { 235 | nwarn("Failed to read oom event from eventfd"); 236 | return G_SOURCE_CONTINUE; 237 | } 238 | 239 | if (num_read == 0) { 240 | close(fd); 241 | oom_event_fd = -1; 242 | g_free(cgroup_event_control_path); 243 | return G_SOURCE_REMOVE; 244 | } 245 | 246 | if (num_read != sizeof(uint64_t)) { 247 | nwarn("Failed to read full oom event from eventfd"); 248 | return G_SOURCE_CONTINUE; 249 | } 250 | 251 | ndebugf("Memory cgroup event count: %ld", (long)event_count); 252 | if (event_count == 0) { 253 | nwarn("Unexpected event count (zero) when reading for oom event"); 254 | return G_SOURCE_CONTINUE; 255 | } 256 | 257 | /* if there's only one event, and the cgroup was removed 258 | * we know the event was for a cgroup removal, not an OOM kill 259 | */ 260 | if (event_count == 1 && cgroup_removed) 261 | return G_SOURCE_CONTINUE; 262 | 263 | /* we catch the two other cases here, both of which are OOM kill events */ 264 | ninfo("OOM event received"); 265 | create_oom_files(); 266 | 267 | return G_SOURCE_CONTINUE; 268 | } 269 | 270 | gboolean check_cgroup2_oom() 271 | { 272 | static long int last_counter = 0; 273 | 274 | if (!is_cgroup_v2) 275 | return G_SOURCE_REMOVE; 276 | 277 | _cleanup_free_ char *memory_events_file_path = g_build_filename(cgroup2_path, "memory.events", NULL); 278 | 279 | _cleanup_fclose_ FILE *fp = fopen(memory_events_file_path, "re"); 280 | if (fp == NULL) { 281 | nwarnf("Failed to open cgroups file: %s", memory_events_file_path); 282 | return G_SOURCE_CONTINUE; 283 | } 284 | 285 | _cleanup_free_ char *line = NULL; 286 | size_t len = 0; 287 | ssize_t read; 288 | while ((read = getline(&line, &len, fp)) != -1) { 289 | long int counter; 290 | const int oom_len = 4, oom_kill_len = 9; 291 | 292 | if (read >= oom_kill_len + 2 && memcmp(line, "oom_kill ", oom_kill_len) == 0) 293 | len = oom_kill_len; 294 | else if (read >= oom_len + 2 && memcmp(line, "oom ", oom_len) == 0) 295 | len = oom_len; 296 | else 297 | continue; 298 | 299 | counter = strtol(&line[len], NULL, 10); 300 | 301 | if (counter == LONG_MAX) { 302 | nwarnf("Failed to parse: %s", &line[len]); 303 | continue; 304 | } 305 | 306 | if (counter == 0) 307 | continue; 308 | 309 | if (counter != last_counter) { 310 | if (create_oom_files() == 0) 311 | last_counter = counter; 312 | } 313 | return G_SOURCE_CONTINUE; 314 | } 315 | return G_SOURCE_REMOVE; 316 | } 317 | 318 | /* create the appropriate files to tell the caller there was an oom event 319 | * this can be used for v1 and v2 OOMs 320 | * returns 0 on success, negative value on failure 321 | */ 322 | static int create_oom_files() 323 | { 324 | ninfo("OOM received"); 325 | int r = 0; 326 | r |= create_oom_file(opt_persist_path); 327 | r |= create_oom_file(opt_bundle_path); 328 | return r; 329 | } 330 | 331 | static int create_oom_file(const char *base_path) 332 | { 333 | if (base_path == NULL || base_path[0] == '\0') 334 | return 0; 335 | 336 | _cleanup_free_ char *ctr_oom_file_path = g_build_filename(base_path, "oom", NULL); 337 | _cleanup_close_ int ctr_oom_fd = open(ctr_oom_file_path, O_CREAT | O_CLOEXEC, 0666); 338 | if (ctr_oom_fd < 0) { 339 | nwarnf("Failed to write oom file to the %s path", base_path); 340 | return -1; 341 | } 342 | return 0; 343 | } 344 | 345 | #endif 346 | -------------------------------------------------------------------------------- /src/cgroup.h: -------------------------------------------------------------------------------- 1 | #if !defined(CGROUP_H) 2 | #define CGROUP_H 3 | 4 | #include /* gboolean */ 5 | 6 | extern int oom_cgroup_fd; 7 | extern int oom_event_fd; 8 | 9 | void setup_oom_handling(int pid); 10 | gboolean conn_sock_cb(int fd, GIOCondition condition, gpointer user_data); 11 | gboolean check_cgroup2_oom(); 12 | 13 | #endif // CGROUP_H 14 | -------------------------------------------------------------------------------- /src/cli.c: -------------------------------------------------------------------------------- 1 | #include "cli.h" 2 | #include "globals.h" 3 | #include "ctr_logging.h" 4 | #include "config.h" 5 | #include "utils.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #ifdef __linux__ 12 | #include 13 | #endif 14 | 15 | gboolean opt_version = FALSE; 16 | gboolean opt_terminal = FALSE; 17 | gboolean opt_stdin = FALSE; 18 | gboolean opt_leave_stdin_open = FALSE; 19 | gboolean opt_syslog = FALSE; 20 | gboolean is_cgroup_v2 = FALSE; 21 | char *cgroup2_path = NULL; 22 | char *opt_cid = NULL; 23 | char *opt_cuuid = NULL; 24 | char *opt_name = NULL; 25 | char *opt_runtime_path = NULL; 26 | char *opt_bundle_path = NULL; 27 | char *opt_persist_path = NULL; 28 | char *opt_container_pid_file = NULL; 29 | char *opt_conmon_pid_file = NULL; 30 | gboolean opt_systemd_cgroup = FALSE; 31 | gboolean opt_no_pivot = FALSE; 32 | gboolean opt_attach = FALSE; 33 | char *opt_exec_process_spec = NULL; 34 | gboolean opt_exec = FALSE; 35 | int opt_api_version = 0; 36 | char *opt_restore_path = NULL; 37 | gchar **opt_runtime_opts = NULL; 38 | gchar **opt_runtime_args = NULL; 39 | gchar **opt_log_path = NULL; 40 | char *opt_exit_dir = NULL; 41 | int opt_timeout = 0; 42 | int64_t opt_log_size_max = -1; 43 | int64_t opt_log_global_size_max = -1; 44 | char *opt_socket_path = DEFAULT_SOCKET_PATH; 45 | gboolean opt_no_new_keyring = FALSE; 46 | char *opt_exit_command = NULL; 47 | gchar **opt_exit_args = NULL; 48 | int opt_exit_delay = 0; 49 | gboolean opt_replace_listen_pid = FALSE; 50 | char *opt_log_level = NULL; 51 | char *opt_log_tag = NULL; 52 | gchar **opt_log_labels = NULL; 53 | gboolean opt_sync = FALSE; 54 | gboolean opt_no_sync_log = FALSE; 55 | char *opt_sdnotify_socket = NULL; 56 | gboolean opt_full_attach_path = FALSE; 57 | char *opt_seccomp_notify_socket = NULL; 58 | char *opt_seccomp_notify_plugins = NULL; 59 | GOptionEntry opt_entries[] = { 60 | {"api-version", 0, 0, G_OPTION_ARG_NONE, &opt_api_version, "Conmon API version to use", NULL}, 61 | {"bundle", 'b', 0, G_OPTION_ARG_STRING, &opt_bundle_path, "Location of the OCI Bundle path", NULL}, 62 | {"cid", 'c', 0, G_OPTION_ARG_STRING, &opt_cid, "Identification of Container", NULL}, 63 | {"conmon-pidfile", 'P', 0, G_OPTION_ARG_STRING, &opt_conmon_pid_file, "PID file for the conmon process", NULL}, 64 | {"container-pidfile", 'p', 0, G_OPTION_ARG_STRING, &opt_container_pid_file, "PID file for the initial pid inside of container", 65 | NULL}, 66 | {"cuuid", 'u', 0, G_OPTION_ARG_STRING, &opt_cuuid, "Container UUID", NULL}, 67 | {"exec", 'e', 0, G_OPTION_ARG_NONE, &opt_exec, "Exec a command into a running container", NULL}, 68 | {"exec-attach", 0, 0, G_OPTION_ARG_NONE, &opt_attach, "Attach to an exec session", NULL}, 69 | {"exec-process-spec", 0, 0, G_OPTION_ARG_STRING, &opt_exec_process_spec, "Path to the process spec for execution", NULL}, 70 | {"exit-command", 0, 0, G_OPTION_ARG_STRING, &opt_exit_command, 71 | "Path to the program to execute when the container terminates its execution", NULL}, 72 | {"exit-command-arg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_exit_args, 73 | "Additional arg to pass to the exit command. Can be specified multiple times", NULL}, 74 | {"exit-delay", 0, 0, G_OPTION_ARG_INT, &opt_exit_delay, "Delay before invoking the exit command (in seconds)", NULL}, 75 | {"exit-dir", 0, 0, G_OPTION_ARG_STRING, &opt_exit_dir, "Path to the directory where exit files are written", NULL}, 76 | {"leave-stdin-open", 0, 0, G_OPTION_ARG_NONE, &opt_leave_stdin_open, "Leave stdin open when attached client disconnects", NULL}, 77 | {"log-level", 0, 0, G_OPTION_ARG_STRING, &opt_log_level, "Print debug logs based on log level", NULL}, 78 | {"log-path", 'l', 0, G_OPTION_ARG_STRING_ARRAY, &opt_log_path, "Log file path", NULL}, 79 | {"log-size-max", 0, 0, G_OPTION_ARG_INT64, &opt_log_size_max, "Maximum size of log file", NULL}, 80 | {"log-global-size-max", 0, 0, G_OPTION_ARG_INT64, &opt_log_global_size_max, "Maximum size of all log files", NULL}, 81 | {"log-tag", 0, 0, G_OPTION_ARG_STRING, &opt_log_tag, "Additional tag to use for logging", NULL}, 82 | {"log-label", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_log_labels, 83 | "Additional label to include in logs. Can be specified multiple times", NULL}, 84 | {"name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Container name", NULL}, 85 | {"no-new-keyring", 0, 0, G_OPTION_ARG_NONE, &opt_no_new_keyring, "Do not create a new session keyring for the container", NULL}, 86 | {"no-pivot", 0, 0, G_OPTION_ARG_NONE, &opt_no_pivot, "Do not use pivot_root", NULL}, 87 | {"no-sync-log", 0, 0, G_OPTION_ARG_NONE, &opt_no_sync_log, "Do not manually call sync on logs after container shutdown", NULL}, 88 | {"persist-dir", '0', 0, G_OPTION_ARG_STRING, &opt_persist_path, 89 | "Persistent directory for a container that can be used for storing container data", NULL}, 90 | {"pidfile", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_container_pid_file, "PID file (DEPRECATED)", NULL}, 91 | {"replace-listen-pid", 0, 0, G_OPTION_ARG_NONE, &opt_replace_listen_pid, "Replace listen pid if set for oci-runtime pid", NULL}, 92 | {"restore", 0, 0, G_OPTION_ARG_STRING, &opt_restore_path, "Restore a container from a checkpoint", NULL}, 93 | {"restore-arg", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_opts, 94 | "Additional arg to pass to the restore command. Can be specified multiple times. (DEPRECATED)", NULL}, 95 | {"runtime", 'r', 0, G_OPTION_ARG_STRING, &opt_runtime_path, "Path to store runtime data for the container", NULL}, 96 | {"runtime-arg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_args, 97 | "Additional arg to pass to the runtime. Can be specified multiple times", NULL}, 98 | {"runtime-opt", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_opts, 99 | "Additional opts to pass to the restore or exec command. Can be specified multiple times", NULL}, 100 | {"sdnotify-socket", 0, 0, G_OPTION_ARG_STRING, &opt_sdnotify_socket, "Path to the host's sd-notify socket to relay messages to", 101 | NULL}, 102 | {"socket-dir-path", 0, 0, G_OPTION_ARG_STRING, &opt_socket_path, "Location of container attach sockets", NULL}, 103 | {"stdin", 'i', 0, G_OPTION_ARG_NONE, &opt_stdin, "Open up a pipe to pass stdin to the container", NULL}, 104 | {"sync", 0, 0, G_OPTION_ARG_NONE, &opt_sync, "Keep the main conmon process as its child by only forking once", NULL}, 105 | {"syslog", 0, 0, G_OPTION_ARG_NONE, &opt_syslog, "Log to syslog (use with cgroupfs cgroup manager)", NULL}, 106 | {"systemd-cgroup", 's', 0, G_OPTION_ARG_NONE, &opt_systemd_cgroup, 107 | "Enable systemd cgroup manager, rather then use the cgroupfs directly", NULL}, 108 | {"terminal", 't', 0, G_OPTION_ARG_NONE, &opt_terminal, "Allocate a pseudo-TTY. The default is false", NULL}, 109 | {"timeout", 'T', 0, G_OPTION_ARG_INT, &opt_timeout, "Kill container after specified timeout in seconds.", NULL}, 110 | {"version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Print the version and exit", NULL}, 111 | {"full-attach", 0, 0, G_OPTION_ARG_NONE, &opt_full_attach_path, 112 | "Don't truncate the path to the attach socket. This option causes conmon to ignore --socket-dir-path", NULL}, 113 | {"seccomp-notify-socket", 0, 0, G_OPTION_ARG_STRING, &opt_seccomp_notify_socket, 114 | "Path to the socket where the seccomp notification fd is received", NULL}, 115 | {"seccomp-notify-plugins", 0, 0, G_OPTION_ARG_STRING, &opt_seccomp_notify_plugins, 116 | "Plugins to use for managing the seccomp notifications", NULL}, 117 | {NULL, 0, 0, 0, NULL, NULL, NULL}}; 118 | 119 | 120 | int initialize_cli(int argc, char *argv[]) 121 | { 122 | GOptionContext *context = g_option_context_new("- conmon utility"); 123 | g_option_context_add_main_entries(context, opt_entries, "conmon"); 124 | 125 | GError *error = NULL; 126 | if (!g_option_context_parse(context, &argc, &argv, &error)) { 127 | g_printerr("conmon: option parsing failed: %s\n", error->message); 128 | exit(EXIT_FAILURE); 129 | } 130 | 131 | g_option_context_free(context); 132 | context = NULL; 133 | 134 | if (opt_version) { 135 | g_print("conmon version " VERSION "\ncommit: " GIT_COMMIT "\n"); 136 | exit(EXIT_SUCCESS); 137 | } 138 | 139 | if (opt_cid == NULL) { 140 | fprintf(stderr, "conmon: Container ID not provided. Use --cid\n"); 141 | exit(EXIT_FAILURE); 142 | } 143 | return -1; 144 | } 145 | 146 | void process_cli() 147 | { 148 | /* Command line parameters */ 149 | set_conmon_logs(opt_log_level, opt_cid, opt_syslog, opt_log_tag); 150 | 151 | 152 | main_loop = g_main_loop_new(NULL, FALSE); 153 | 154 | if (opt_restore_path && opt_exec) 155 | nexit("Cannot use 'exec' and 'restore' at the same time"); 156 | 157 | if (!opt_exec && opt_attach) 158 | nexit("Attach can only be specified with exec"); 159 | 160 | if (opt_api_version < 1 && opt_attach) 161 | nexit("Attach can only be specified for a non-legacy exec session"); 162 | 163 | /* The old exec API did not require opt_cuuid */ 164 | if (opt_cuuid == NULL && (!opt_exec || opt_api_version >= 1)) 165 | nexit("Container UUID not provided. Use --cuuid"); 166 | 167 | if (opt_seccomp_notify_plugins == NULL) 168 | opt_seccomp_notify_plugins = getenv("CONMON_SECCOMP_NOTIFY_PLUGINS"); 169 | 170 | if (opt_runtime_path == NULL) 171 | nexit("Runtime path not provided. Use --runtime"); 172 | if (access(opt_runtime_path, X_OK) < 0) 173 | pexitf("Runtime path %s is not valid", opt_runtime_path); 174 | 175 | if (opt_exec && opt_exec_process_spec == NULL) { 176 | nexit("Exec process spec path not provided. Use --exec-process-spec"); 177 | } 178 | 179 | char cwd[PATH_MAX]; 180 | if (getcwd(cwd, sizeof(cwd)) == NULL) { 181 | nexit("Failed to get working directory"); 182 | } 183 | 184 | // opt_bundle_path in exec means we will set up the attach socket 185 | // for the exec session. the legacy version of exec does not need this 186 | // and thus we only override an empty opt_bundle_path when we're not exec 187 | if (opt_bundle_path == NULL && !opt_exec) { 188 | opt_bundle_path = cwd; 189 | } 190 | 191 | if (opt_exit_delay < 0) { 192 | nexit("Delay before invoking exit command must be greater than or equal to 0"); 193 | } 194 | 195 | // we should always override the container pid file if it's empty 196 | // TODO FIXME I removed default_pid_file here. shouldn't opt_container_pid_file be cleaned up? 197 | if (opt_container_pid_file == NULL) 198 | opt_container_pid_file = g_strdup_printf("%s/pidfile-%s", cwd, opt_cid); 199 | 200 | configure_log_drivers(opt_log_path, opt_log_size_max, opt_log_global_size_max, opt_cid, opt_name, opt_log_tag, opt_log_labels); 201 | } 202 | -------------------------------------------------------------------------------- /src/cli.h: -------------------------------------------------------------------------------- 1 | #if !defined(CLI_H) 2 | #define CLI_H 3 | 4 | #include /* gboolean and GOptionEntry */ 5 | #include /* int64_t */ 6 | 7 | extern gboolean opt_version; 8 | extern gboolean opt_terminal; 9 | extern gboolean opt_stdin; 10 | extern gboolean opt_leave_stdin_open; 11 | extern gboolean opt_syslog; 12 | extern gboolean is_cgroup_v2; 13 | extern char *cgroup2_path; 14 | extern char *opt_cid; 15 | extern char *opt_cuuid; 16 | extern char *opt_name; 17 | extern char *opt_runtime_path; 18 | extern char *opt_bundle_path; 19 | extern char *opt_persist_path; 20 | extern char *opt_container_pid_file; 21 | extern char *opt_conmon_pid_file; 22 | extern gboolean opt_systemd_cgroup; 23 | extern gboolean opt_no_pivot; 24 | extern gboolean opt_attach; 25 | extern char *opt_exec_process_spec; 26 | extern gboolean opt_exec; 27 | extern int opt_api_version; 28 | extern char *opt_restore_path; 29 | extern gchar **opt_runtime_opts; 30 | extern gchar **opt_runtime_args; 31 | extern gchar **opt_log_path; 32 | extern char *opt_exit_dir; 33 | extern int opt_timeout; 34 | extern int64_t opt_log_size_max; 35 | extern char *opt_socket_path; 36 | extern gboolean opt_no_new_keyring; 37 | extern char *opt_exit_command; 38 | extern gchar **opt_exit_args; 39 | extern int opt_exit_delay; 40 | extern gboolean opt_replace_listen_pid; 41 | extern char *opt_log_level; 42 | extern char *opt_log_tag; 43 | extern gboolean opt_no_sync_log; 44 | extern gboolean opt_sync; 45 | extern char *opt_sdnotify_socket; 46 | extern char *opt_seccomp_notify_socket; 47 | extern char *opt_seccomp_notify_plugins; 48 | extern GOptionEntry opt_entries[]; 49 | extern gboolean opt_full_attach_path; 50 | 51 | int initialize_cli(int argc, char *argv[]); 52 | void process_cli(); 53 | 54 | #endif // CLI_H 55 | -------------------------------------------------------------------------------- /src/close_fds.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #if __STDC_VERSION__ >= 199901L 3 | /* C99 or later */ 4 | #else 5 | #error conmon.c requires C99 or later 6 | #endif 7 | 8 | #include "utils.h" 9 | #include "ctr_logging.h" 10 | #include "cgroup.h" 11 | #include "cli.h" 12 | #include "globals.h" 13 | #include "oom.h" 14 | #include "conn_sock.h" 15 | #include "ctrl.h" 16 | #include "ctr_stdio.h" 17 | #include "config.h" 18 | #include "parent_pipe_fd.h" 19 | #include "ctr_exit.h" 20 | #include "close_fds.h" 21 | #include "runtime_args.h" 22 | 23 | #include 24 | 25 | #ifdef __FreeBSD__ 26 | #define OPEN_FILES_DIR "/dev/fd" 27 | #else 28 | #define OPEN_FILES_DIR "/proc/self/fd" 29 | #endif 30 | 31 | static int open_files_max_fd; 32 | static fd_set *open_files_set; 33 | 34 | static void __attribute__((constructor)) init() 35 | { 36 | struct dirent *ent; 37 | ssize_t size = 0; 38 | DIR *d; 39 | 40 | d = opendir(OPEN_FILES_DIR); 41 | if (!d) 42 | return; 43 | 44 | for (ent = readdir(d); ent; ent = readdir(d)) { 45 | int fd; 46 | 47 | if (ent->d_name[0] == '.') 48 | continue; 49 | 50 | fd = atoi(ent->d_name); 51 | if (fd == dirfd(d)) 52 | continue; 53 | 54 | if (fd >= size * FD_SETSIZE) { 55 | int i; 56 | ssize_t new_size; 57 | 58 | new_size = (fd / FD_SETSIZE) + 1; 59 | open_files_set = realloc(open_files_set, new_size * sizeof(fd_set)); 60 | if (open_files_set == NULL) 61 | _exit(EXIT_FAILURE); 62 | 63 | for (i = size; i < new_size; i++) 64 | FD_ZERO(&(open_files_set[i])); 65 | 66 | size = new_size; 67 | } 68 | 69 | if (fd > open_files_max_fd) 70 | open_files_max_fd = fd; 71 | 72 | FD_SET(fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE])); 73 | } 74 | closedir(d); 75 | } 76 | 77 | void close_other_fds() 78 | { 79 | int fd; 80 | 81 | if (open_files_set == NULL) 82 | return; 83 | for (fd = 3; fd <= open_files_max_fd; fd++) { 84 | if (fd != sync_pipe_fd && FD_ISSET(fd % FD_SETSIZE, &(open_files_set[fd / FD_SETSIZE]))) 85 | close(fd); 86 | } 87 | } 88 | 89 | void close_all_fds_ge_than(int firstfd) 90 | { 91 | struct dirent *ent; 92 | DIR *d; 93 | 94 | d = opendir(OPEN_FILES_DIR); 95 | if (!d) 96 | return; 97 | 98 | for (ent = readdir(d); ent; ent = readdir(d)) { 99 | int fd; 100 | 101 | if (ent->d_name[0] == '.') 102 | continue; 103 | 104 | fd = atoi(ent->d_name); 105 | if (fd == dirfd(d)) 106 | continue; 107 | if (fd >= firstfd) 108 | close(fd); 109 | } 110 | closedir(d); 111 | } 112 | -------------------------------------------------------------------------------- /src/close_fds.h: -------------------------------------------------------------------------------- 1 | void close_other_fds(); 2 | void close_all_fds_ge_than(int firstfd); 3 | -------------------------------------------------------------------------------- /src/cmsg.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 SUSE LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* NOTE: This code comes directly from runc/libcontainer/utils/cmsg.c. */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "cmsg.h" 28 | 29 | #if __STDC_VERSION__ >= 199901L 30 | /* C99 or later */ 31 | #else 32 | #error cmsg.c requires C99 or later 33 | #endif 34 | 35 | #ifdef __FreeBSD__ 36 | #define ECOMM EINVAL 37 | #endif 38 | 39 | #define errorf(fmt, ...) \ 40 | do { \ 41 | fprintf(stderr, "conmon: " fmt "\n", ##__VA_ARGS__); \ 42 | errno = ECOMM; \ 43 | goto err; /* return value */ \ 44 | } while (0) 45 | 46 | #define error(s) errorf("%s", s) 47 | 48 | /* 49 | * Sends a file descriptor along the sockfd provided. Returns the return 50 | * value of sendmsg(2). Any synchronisation and preparation of state 51 | * should be done external to this (we expect the other side to be in 52 | * recvfd() in the code). 53 | */ 54 | ssize_t sendfd(int sockfd, struct file_t file) 55 | { 56 | struct msghdr msg = {0}; 57 | struct iovec iov[1] = {{0}}; 58 | struct cmsghdr *cmsg; 59 | int *fdptr; 60 | 61 | union { 62 | char buf[CMSG_SPACE(sizeof(file.fd))]; 63 | struct cmsghdr align; 64 | } u; 65 | 66 | /* 67 | * We need to send some other data along with the ancillary data, 68 | * otherwise the other side won't receive any data. This is very 69 | * well-hidden in the documentation (and only applies to 70 | * SOCK_STREAM). See the bottom part of unix(7). 71 | */ 72 | iov[0].iov_base = file.name; 73 | iov[0].iov_len = strlen(file.name) + 1; 74 | 75 | msg.msg_name = NULL; 76 | msg.msg_namelen = 0; 77 | msg.msg_iov = iov; 78 | msg.msg_iovlen = 1; 79 | msg.msg_control = u.buf; 80 | msg.msg_controllen = sizeof(u.buf); 81 | 82 | cmsg = CMSG_FIRSTHDR(&msg); 83 | cmsg->cmsg_level = SOL_SOCKET; 84 | cmsg->cmsg_type = SCM_RIGHTS; 85 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 86 | 87 | fdptr = (int *)CMSG_DATA(cmsg); 88 | memcpy(fdptr, &file.fd, sizeof(int)); 89 | 90 | return sendmsg(sockfd, &msg, 0); 91 | } 92 | 93 | /* 94 | * Receives a file descriptor from the sockfd provided. Returns the file 95 | * descriptor as sent from sendfd(). It will return the file descriptor 96 | * or die (literally) trying. Any synchronisation and preparation of 97 | * state should be done external to this (we expect the other side to be 98 | * in sendfd() in the code). 99 | */ 100 | struct file_t recvfd(int sockfd) 101 | { 102 | struct msghdr msg = {0}; 103 | struct iovec iov[1] = {{0}}; 104 | struct cmsghdr *cmsg; 105 | struct file_t file = {0}; 106 | int *fdptr; 107 | int olderrno; 108 | 109 | union { 110 | char buf[CMSG_SPACE(sizeof(file.fd))]; 111 | struct cmsghdr align; 112 | } u; 113 | 114 | /* Allocate a buffer. */ 115 | /* TODO: Make this dynamic with MSG_PEEK. */ 116 | file.name = malloc(TAG_BUFFER); 117 | if (!file.name) 118 | error("recvfd: failed to allocate file.tag buffer"); 119 | 120 | /* 121 | * We need to "receive" the non-ancillary data even though we don't 122 | * plan to use it at all. Otherwise, things won't work as expected. 123 | * See unix(7) and other well-hidden documentation. 124 | */ 125 | iov[0].iov_base = file.name; 126 | iov[0].iov_len = TAG_BUFFER; 127 | 128 | msg.msg_name = NULL; 129 | msg.msg_namelen = 0; 130 | msg.msg_iov = iov; 131 | msg.msg_iovlen = 1; 132 | msg.msg_control = u.buf; 133 | msg.msg_controllen = sizeof(u.buf); 134 | 135 | ssize_t ret = recvmsg(sockfd, &msg, 0); 136 | if (ret < 0) 137 | goto err; 138 | 139 | cmsg = CMSG_FIRSTHDR(&msg); 140 | if (!cmsg) 141 | error("recvfd: got NULL from CMSG_FIRSTHDR"); 142 | if (cmsg->cmsg_level != SOL_SOCKET) 143 | errorf("recvfd: expected SOL_SOCKET in cmsg: %d", cmsg->cmsg_level); 144 | if (cmsg->cmsg_type != SCM_RIGHTS) 145 | errorf("recvfd: expected SCM_RIGHTS in cmsg: %d", cmsg->cmsg_type); 146 | if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) 147 | errorf("recvfd: expected correct CMSG_LEN in cmsg: %lu", (unsigned long)cmsg->cmsg_len); 148 | 149 | fdptr = (int *)CMSG_DATA(cmsg); 150 | if (!fdptr || *fdptr < 0) 151 | error("recvfd: received invalid pointer"); 152 | 153 | file.fd = *fdptr; 154 | return file; 155 | 156 | err: 157 | olderrno = errno; 158 | free(file.name); 159 | errno = olderrno; 160 | return (struct file_t){.name = NULL, .fd = -1}; 161 | } 162 | -------------------------------------------------------------------------------- /src/cmsg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 SUSE LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* NOTE: This code comes directly from runc/libcontainer/utils/cmsg.h. */ 18 | 19 | #pragma once 20 | 21 | #if !defined(CMSG_H) 22 | #define CMSG_H 23 | 24 | #include 25 | 26 | /* TODO: Implement this properly with MSG_PEEK. */ 27 | #define TAG_BUFFER 4096 28 | 29 | /* This mirrors Go's (*os.File). */ 30 | struct file_t { 31 | char *name; 32 | int fd; 33 | }; 34 | 35 | struct file_t recvfd(int sockfd); 36 | ssize_t sendfd(int sockfd, struct file_t file); 37 | 38 | #endif /* !defined(CMSG_H) */ 39 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | 2 | #if !defined(CONFIG_H) 3 | #define CONFIG_H 4 | 5 | #define BUF_SIZE 8192 6 | #define STDIO_BUF_SIZE 8192 7 | #define CONN_SOCK_BUF_SIZE 32768 8 | #define DEFAULT_SOCKET_PATH "/var/run/crio" 9 | #define WIN_RESIZE_EVENT 1 10 | #define REOPEN_LOGS_EVENT 2 11 | #define TIMED_OUT_MESSAGE "command timed out" 12 | 13 | #endif // CONFIG_H 14 | -------------------------------------------------------------------------------- /src/conmon.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #if __STDC_VERSION__ >= 199901L 3 | /* C99 or later */ 4 | #else 5 | #error conmon.c requires C99 or later 6 | #endif 7 | 8 | #include "utils.h" 9 | #include "ctr_logging.h" 10 | #include "cgroup.h" 11 | #include "cli.h" 12 | #include "globals.h" 13 | #include "oom.h" 14 | #include "conn_sock.h" 15 | #include "ctrl.h" 16 | #include "ctr_stdio.h" 17 | #include "config.h" 18 | #include "parent_pipe_fd.h" 19 | #include "ctr_exit.h" 20 | #include "close_fds.h" 21 | #include "seccomp_notify.h" 22 | #include "runtime_args.h" 23 | 24 | #include 25 | #include 26 | 27 | static void disconnect_std_streams(int dev_null_r, int dev_null_w) 28 | { 29 | if (dup2(dev_null_r, STDIN_FILENO) < 0) 30 | pexit("Failed to dup over stdin"); 31 | if (dup2(dev_null_w, STDOUT_FILENO) < 0) 32 | pexit("Failed to dup over stdout"); 33 | if (dup2(dev_null_w, STDERR_FILENO) < 0) 34 | pexit("Failed to dup over stderr"); 35 | } 36 | 37 | #define DEFAULT_UMASK 0022 38 | 39 | int main(int argc, char *argv[]) 40 | { 41 | setlocale(LC_ALL, ""); 42 | umask(DEFAULT_UMASK); 43 | _cleanup_gerror_ GError *err = NULL; 44 | char buf[BUF_SIZE]; 45 | int num_read; 46 | _cleanup_close_ int dev_null_r_cleanup = -1; 47 | _cleanup_close_ int dev_null_w_cleanup = -1; 48 | _cleanup_close_ int dummyfd = -1; 49 | 50 | int initialize_ec = initialize_cli(argc, argv); 51 | if (initialize_ec >= 0) { 52 | exit(initialize_ec); 53 | } 54 | 55 | process_cli(); 56 | 57 | attempt_oom_adjust(-1000); 58 | 59 | /* ignoring SIGPIPE prevents conmon from being spuriously killed */ 60 | signal(SIGPIPE, SIG_IGN); 61 | /* Catch SIGTERM and call exit(). This causes the atexit functions to be called. */ 62 | signal(SIGTERM, handle_signal); 63 | 64 | int start_pipe_fd = get_pipe_fd_from_env("_OCI_STARTPIPE"); 65 | if (start_pipe_fd > 0) { 66 | /* Block for an initial write to the start pipe before 67 | spawning any childred or exiting, to ensure the 68 | parent can put us in the right cgroup. */ 69 | num_read = read(start_pipe_fd, buf, BUF_SIZE); 70 | if (num_read < 0) { 71 | pexit("start-pipe read failed"); 72 | } 73 | /* If we aren't attaching in an exec session, 74 | we don't need this anymore. */ 75 | if (!opt_attach) 76 | close(start_pipe_fd); 77 | } 78 | 79 | dev_null_r_cleanup = dev_null_r = open("/dev/null", O_RDONLY | O_CLOEXEC); 80 | if (dev_null_r < 0) 81 | pexit("Failed to open /dev/null"); 82 | 83 | dev_null_w_cleanup = dev_null_w = open("/dev/null", O_WRONLY | O_CLOEXEC); 84 | if (dev_null_w < 0) 85 | pexit("Failed to open /dev/null"); 86 | 87 | /* In the non-sync case, we double-fork in 88 | * order to disconnect from the parent, as we want to 89 | * continue in a daemon-like way */ 90 | if (!opt_sync) { 91 | pid_t main_pid = fork(); 92 | if (main_pid < 0) { 93 | pexit("Failed to fork the create command"); 94 | } else if (main_pid != 0) { 95 | if (opt_conmon_pid_file) { 96 | char content[12]; 97 | sprintf(content, "%i", main_pid); 98 | 99 | if (!g_file_set_contents(opt_conmon_pid_file, content, strlen(content), &err)) { 100 | _pexitf("Failed to write conmon pidfile: %s", err->message); 101 | } 102 | } 103 | _exit(0); 104 | } 105 | } 106 | 107 | /* before we fork, ensure our children will be reaped */ 108 | atexit(reap_children); 109 | 110 | /* If we were passed a sd-notify socket to use, set it up now */ 111 | if (opt_sdnotify_socket) { 112 | setup_notify_socket(opt_sdnotify_socket); 113 | } 114 | 115 | /* Environment variables */ 116 | sync_pipe_fd = get_pipe_fd_from_env("_OCI_SYNCPIPE"); 117 | 118 | if (opt_attach) { 119 | attach_pipe_fd = get_pipe_fd_from_env("_OCI_ATTACHPIPE"); 120 | if (attach_pipe_fd < 0) { 121 | pexit("--attach specified but _OCI_ATTACHPIPE was not"); 122 | } 123 | } 124 | 125 | 126 | /* Disconnect stdio from parent. We need to do this, because 127 | the parent is waiting for the stdout to end when the intermediate 128 | child dies */ 129 | if (!logging_is_passthrough()) 130 | disconnect_std_streams(dev_null_r, dev_null_w); 131 | /* Create a new session group */ 132 | setsid(); 133 | 134 | /* 135 | * Set self as subreaper so we can wait for container process 136 | * and return its exit code. 137 | */ 138 | int ret = set_subreaper(true); 139 | if (ret != 0) { 140 | pexit("Failed to set as subreaper"); 141 | } 142 | 143 | _cleanup_free_ char *csname = NULL; 144 | _cleanup_free_ char *seccomp_listener = NULL; 145 | int workerfd_stdin = -1; 146 | int workerfd_stdout = -1; 147 | int workerfd_stderr = -1; 148 | int fds[2]; 149 | if (opt_terminal) { 150 | csname = setup_console_socket(); 151 | } else { 152 | 153 | /* 154 | * Create a "fake" main fd so that we can use the same epoll code in 155 | * both cases. The workerfd_*s will be closed after we dup over 156 | * everything. 157 | * 158 | * We use pipes here because open(/dev/std{out,err}) will fail if we 159 | * used anything else (and it wouldn't be a good idea to create a new 160 | * pty pair in the host). 161 | */ 162 | 163 | if (opt_stdin) { 164 | if (pipe2(fds, O_CLOEXEC) < 0) 165 | pexit("Failed to create !terminal stdin pipe"); 166 | 167 | mainfd_stdin = fds[1]; 168 | workerfd_stdin = fds[0]; 169 | 170 | if (g_unix_set_fd_nonblocking(mainfd_stdin, TRUE, NULL) == FALSE) 171 | nwarn("Failed to set mainfd_stdin to non blocking"); 172 | } 173 | 174 | if (pipe2(fds, O_CLOEXEC) < 0) 175 | pexit("Failed to create !terminal stdout pipe"); 176 | 177 | mainfd_stdout = fds[0]; 178 | workerfd_stdout = fds[1]; 179 | } 180 | 181 | if (opt_seccomp_notify_socket != NULL) { 182 | #ifdef USE_SECCOMP 183 | pexit("seccomp support not present"); 184 | #else 185 | if (opt_seccomp_notify_plugins == NULL) 186 | pexit("seccomp notify socket specified without any plugin"); 187 | seccomp_listener = setup_seccomp_socket(opt_seccomp_notify_socket); 188 | #endif 189 | } 190 | 191 | /* We always create a stderr pipe, because that way we can capture 192 | runc stderr messages before the tty is created */ 193 | if (pipe2(fds, O_CLOEXEC) < 0) 194 | pexit("Failed to create stderr pipe"); 195 | 196 | mainfd_stderr = fds[0]; 197 | workerfd_stderr = fds[1]; 198 | 199 | GPtrArray *runtime_argv = configure_runtime_args(csname); 200 | 201 | /* Setup endpoint for attach */ 202 | _cleanup_free_ char *attach_symlink_dir_path = NULL; 203 | if (opt_bundle_path != NULL && !logging_is_passthrough()) { 204 | attach_symlink_dir_path = setup_attach_socket(); 205 | dummyfd = setup_terminal_control_fifo(); 206 | setup_console_fifo(); 207 | 208 | if (opt_attach) { 209 | ndebug("sending attach message to parent"); 210 | write_or_close_sync_fd(&attach_pipe_fd, 0, NULL); 211 | ndebug("sent attach message to parent"); 212 | } 213 | } 214 | 215 | sigset_t mask, oldmask; 216 | if ((sigemptyset(&mask) < 0) || (sigaddset(&mask, SIGTERM) < 0) || (sigaddset(&mask, SIGQUIT) < 0) || (sigaddset(&mask, SIGINT) < 0) 217 | || sigprocmask(SIG_BLOCK, &mask, &oldmask) < 0) 218 | pexit("Failed to block signals"); 219 | /* 220 | * We have to fork here because the current runC API dups the stdio of the 221 | * calling process over the container's fds. This is actually *very bad* 222 | * but is currently being discussed for change in 223 | * https://github.com/opencontainers/runtime-spec/pull/513. Hopefully this 224 | * won't be the case for very long. 225 | */ 226 | 227 | /* Create our container. */ 228 | create_pid = fork(); 229 | if (create_pid < 0) { 230 | pexit("Failed to fork the create command"); 231 | } else if (!create_pid) { 232 | if (set_pdeathsig(SIGKILL) < 0) 233 | _pexit("Failed to set PDEATHSIG"); 234 | if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) 235 | _pexit("Failed to unblock signals"); 236 | 237 | if (!logging_is_passthrough()) { 238 | /* 239 | * EINVAL indicates the type of file descriptor used is not supporting fchmod(2) on the given platform. 240 | * Only more unusual cases are logged. 241 | */ 242 | if (workerfd_stdin < 0) 243 | workerfd_stdin = dev_null_r; 244 | if (dup2(workerfd_stdin, STDIN_FILENO) < 0) 245 | _pexit("Failed to dup over stdin"); 246 | if (workerfd_stdin != dev_null_r && fchmod(STDIN_FILENO, 0777) < 0 && errno != EINVAL) 247 | nwarn("Failed to chmod stdin"); 248 | 249 | if (workerfd_stdout < 0) 250 | workerfd_stdout = dev_null_w; 251 | if (dup2(workerfd_stdout, STDOUT_FILENO) < 0) 252 | _pexit("Failed to dup over stdout"); 253 | if (workerfd_stdout != dev_null_w && fchmod(STDOUT_FILENO, 0777) < 0 && errno != EINVAL) 254 | nwarn("Failed to chmod stdout"); 255 | 256 | if (workerfd_stderr < 0) 257 | workerfd_stderr = workerfd_stdout; 258 | if (dup2(workerfd_stderr, STDERR_FILENO) < 0) 259 | _pexit("Failed to dup over stderr"); 260 | if (workerfd_stderr != dev_null_w && fchmod(STDERR_FILENO, 0777) < 0 && errno != EINVAL) 261 | nwarn("Failed to chmod stderr"); 262 | } 263 | /* If LISTEN_PID env is set, we need to set the LISTEN_PID 264 | it to the new child process */ 265 | char *listenpid = getenv("LISTEN_PID"); 266 | if (listenpid != NULL) { 267 | errno = 0; 268 | int lpid = strtol(listenpid, NULL, 10); 269 | if (errno != 0 || lpid <= 0) 270 | _pexitf("Invalid LISTEN_PID %.10s", listenpid); 271 | if (opt_replace_listen_pid || lpid == getppid()) { 272 | gchar *pidstr = g_strdup_printf("%d", getpid()); 273 | if (!pidstr) 274 | _pexit("Failed to g_strdup_sprintf pid"); 275 | if (setenv("LISTEN_PID", pidstr, true) < 0) 276 | _pexit("Failed to setenv LISTEN_PID"); 277 | free(pidstr); 278 | } 279 | } 280 | 281 | // If we are execing, and the user is trying to attach to this exec session, 282 | // we need to wait until they attach to the console before actually execing, 283 | // or else we may lose output 284 | if (opt_attach) { 285 | if (start_pipe_fd > 0) { 286 | ndebug("exec with attach is waiting for start message from parent"); 287 | num_read = read(start_pipe_fd, buf, BUF_SIZE); 288 | ndebug("exec with attach got start message from parent"); 289 | if (num_read < 0) { 290 | _pexit("start-pipe read failed"); 291 | } 292 | close(start_pipe_fd); 293 | } 294 | } 295 | 296 | // We don't want runc to be unkillable so we reset the oom_score_adj back to 0 297 | reset_oom_adjust(); 298 | execv(g_ptr_array_index(runtime_argv, 0), (char **)runtime_argv->pdata); 299 | exit(127); 300 | } 301 | 302 | if (logging_is_passthrough()) 303 | disconnect_std_streams(dev_null_r, dev_null_w); 304 | 305 | if ((signal(SIGTERM, on_sig_exit) == SIG_ERR) || (signal(SIGQUIT, on_sig_exit) == SIG_ERR) 306 | || (signal(SIGINT, on_sig_exit) == SIG_ERR)) 307 | pexit("Failed to register the signal handler"); 308 | 309 | 310 | if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) 311 | pexit("Failed to unblock signals"); 312 | 313 | /* Map pid to its handler. */ 314 | _cleanup_hashtable_ GHashTable *pid_to_handler = g_hash_table_new(g_int_hash, g_int_equal); 315 | g_hash_table_insert(pid_to_handler, (pid_t *)&create_pid, runtime_exit_cb); 316 | 317 | /* Register a handler via signalfd for handling SIGCHLD */ 318 | struct pid_check_data data = { 319 | .pid_to_handler = pid_to_handler, 320 | .exit_status_cache = NULL, 321 | }; 322 | int signal_fd = get_signal_descriptor(); 323 | if (signal_fd < 0) 324 | pexit("Failed to create signalfd"); 325 | int signal_fd_tag = g_unix_fd_add(signal_fd, G_IO_IN, on_signalfd_cb, &data); 326 | 327 | if (opt_exit_command) 328 | atexit(do_exit_command); 329 | 330 | g_ptr_array_free(runtime_argv, TRUE); 331 | 332 | /* The runtime has that fd now. We don't need to touch it anymore. */ 333 | if (workerfd_stdin > -1) 334 | close(workerfd_stdin); 335 | if (workerfd_stdout > -1) 336 | close(workerfd_stdout); 337 | if (workerfd_stderr > -1) 338 | close(workerfd_stderr); 339 | 340 | if (seccomp_listener != NULL) 341 | g_unix_fd_add(seccomp_socket_fd, G_IO_IN, seccomp_accept_cb, csname); 342 | 343 | if (csname != NULL) { 344 | g_unix_fd_add(console_socket_fd, G_IO_IN, terminal_accept_cb, csname); 345 | /* Process any SIGCHLD we may have missed before the signal handler was in place. */ 346 | if (!opt_exec || !opt_terminal || container_status < 0) { 347 | GHashTable *exit_status_cache = g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_free); 348 | data.exit_status_cache = exit_status_cache; 349 | g_idle_add(check_child_processes_cb, &data); 350 | g_main_loop_run(main_loop); 351 | } 352 | } else { 353 | int ret; 354 | /* Wait for our create child to exit with the return code. */ 355 | do 356 | ret = waitpid(create_pid, &runtime_status, 0); 357 | while (ret < 0 && errno == EINTR); 358 | if (ret < 0) { 359 | if (create_pid > 0) { 360 | int old_errno = errno; 361 | kill(create_pid, SIGKILL); 362 | errno = old_errno; 363 | } 364 | pexitf("Failed to wait for `runtime %s`", opt_exec ? "exec" : "create"); 365 | } 366 | } 367 | 368 | if (!WIFEXITED(runtime_status) || WEXITSTATUS(runtime_status) != 0) { 369 | /* 370 | * Read from container stderr for any error and send it to parent 371 | * We send -1 as pid to signal to parent that create container has failed. 372 | */ 373 | num_read = read(mainfd_stderr, buf, BUF_SIZE - 1); 374 | if (num_read > 0) { 375 | buf[num_read] = '\0'; 376 | nwarnf("runtime stderr: %s", buf); 377 | if (sync_pipe_fd > 0) { 378 | int to_report = -1; 379 | if (opt_exec && container_status > 0) { 380 | to_report = -1 * container_status; 381 | } 382 | write_or_close_sync_fd(&sync_pipe_fd, to_report, buf); 383 | } 384 | } 385 | nexitf("Failed to create container: exit status %d", get_exit_status(runtime_status)); 386 | } 387 | 388 | if (opt_terminal && mainfd_stdout == -1) 389 | nexit("Runtime did not set up terminal"); 390 | 391 | /* Read the pid so we can wait for the process to exit */ 392 | _cleanup_free_ char *contents = NULL; 393 | if (!g_file_get_contents(opt_container_pid_file, &contents, NULL, &err)) { 394 | nwarnf("Failed to read pidfile: %s", err->message); 395 | exit(1); 396 | } 397 | 398 | container_pid = atoi(contents); 399 | ndebugf("container PID: %d", container_pid); 400 | 401 | g_hash_table_insert(pid_to_handler, (pid_t *)&container_pid, container_exit_cb); 402 | 403 | /* Send the container pid back to parent 404 | * Only send this pid back if we are using the current exec API. Old consumers expect 405 | * conmon to only send one value down this pipe, which will later be the exit code 406 | * Thus, if we are legacy and we are exec, skip this write. 407 | */ 408 | if ((opt_api_version >= 1 || !opt_exec) && sync_pipe_fd >= 0) 409 | write_or_close_sync_fd(&sync_pipe_fd, container_pid, NULL); 410 | 411 | #ifdef __linux__ 412 | setup_oom_handling(container_pid); 413 | #endif 414 | 415 | if (mainfd_stdout >= 0) { 416 | g_unix_fd_add(mainfd_stdout, G_IO_IN, stdio_cb, GINT_TO_POINTER(STDOUT_PIPE)); 417 | } 418 | if (mainfd_stderr >= 0) { 419 | g_unix_fd_add(mainfd_stderr, G_IO_IN, stdio_cb, GINT_TO_POINTER(STDERR_PIPE)); 420 | } 421 | 422 | if (opt_timeout > 0) { 423 | g_timeout_add_seconds(opt_timeout, timeout_cb, NULL); 424 | } 425 | 426 | if (data.exit_status_cache) { 427 | GHashTableIter iter; 428 | gpointer key, value; 429 | 430 | g_hash_table_iter_init(&iter, data.exit_status_cache); 431 | while (g_hash_table_iter_next(&iter, &key, &value)) { 432 | pid_t *k = (pid_t *)key; 433 | int *v = (int *)value; 434 | void (*cb)(GPid, int, gpointer) = g_hash_table_lookup(pid_to_handler, k); 435 | if (cb) 436 | cb(*k, *v, 0); 437 | } 438 | g_hash_table_destroy(data.exit_status_cache); 439 | data.exit_status_cache = NULL; 440 | } 441 | 442 | /* There are three cases we want to run this main loop: 443 | 1. If we are using the legacy API 444 | 2. if we are running create or restore 445 | 3. if we are running exec without a terminal 446 | no matter the speed of the command being executed, having outstanding 447 | output to process from the child process keeps it alive, so we can read the io, 448 | and let the callback handler take care of the container_status as normal. 449 | 4. if we are exec with a tty open, and our container_status hasn't been changed 450 | by any callbacks yet 451 | specifically, the check child processes call above could set the container 452 | status if it is a quickly exiting command. We only want to run the loop if 453 | this hasn't happened yet. 454 | Note: there exists a chance that we have the container_status, are exec, and api>=1, 455 | but are not terminal. In this case, we still want to run to process all of the output, 456 | but will need to exit once all the i/o is read. This will be handled in stdio_cb above. 457 | */ 458 | if (opt_api_version < 1 || !opt_exec || !opt_terminal || container_status < 0) { 459 | g_idle_add(check_child_processes_cb, &data); 460 | g_main_loop_run(main_loop); 461 | } 462 | 463 | #ifdef __linux__ 464 | check_cgroup2_oom(); 465 | #endif 466 | 467 | /* Drain stdout and stderr only if a timeout doesn't occur */ 468 | if (!timed_out) 469 | drain_stdio(); 470 | 471 | if (!opt_no_sync_log) 472 | sync_logs(); 473 | 474 | int exit_status = -1; 475 | const char *exit_message = NULL; 476 | 477 | /* 478 | * If timed_out is TRUE but container_pid is -1, the process must have died before 479 | * the timer elapsed. Ignore the timeout and treat it like a normal container exit. 480 | */ 481 | if (timed_out && container_pid > 0) { 482 | pid_t process_group = getpgid(container_pid); 483 | /* if process_group is 1, we will end up calling 484 | * kill(-1), which kills everything conmon is allowed to. */ 485 | if (process_group > 1) 486 | kill(-process_group, SIGKILL); 487 | else 488 | kill(container_pid, SIGKILL); 489 | exit_message = TIMED_OUT_MESSAGE; 490 | } else { 491 | exit_status = get_exit_status(container_status); 492 | } 493 | 494 | /* Close down the signalfd */ 495 | g_source_remove(signal_fd_tag); 496 | close(signal_fd); 497 | 498 | /* 499 | * Podman injects some fd's into the conmon process so that exposed ports are kept busy while 500 | * the container runs. Close them before we notify the container exited, so that they can be 501 | * reused immediately. 502 | */ 503 | close_other_fds(); 504 | close_all_readers(); 505 | 506 | _cleanup_free_ char *status_str = g_strdup_printf("%d", exit_status); 507 | 508 | /* Write the exit file to container persistent directory if it is specified */ 509 | if (opt_persist_path) { 510 | _cleanup_free_ char *ctr_exit_file_path = g_build_filename(opt_persist_path, "exit", NULL); 511 | if (!g_file_set_contents(ctr_exit_file_path, status_str, -1, &err)) 512 | nexitf("Failed to write %s to container exit file: %s", status_str, err->message); 513 | } 514 | 515 | /* 516 | * Writing to this directory helps if a daemon process wants to monitor all container exits 517 | * using inotify. 518 | */ 519 | if (opt_exit_dir) { 520 | _cleanup_free_ char *exit_file_path = g_build_filename(opt_exit_dir, opt_cid, NULL); 521 | if (!g_file_set_contents(exit_file_path, status_str, -1, &err)) 522 | nexitf("Failed to write %s to exit file: %s", status_str, err->message); 523 | } 524 | if (seccomp_listener != NULL) 525 | unlink(seccomp_listener); 526 | 527 | /* Send the command exec exit code back to the parent */ 528 | if (opt_exec && sync_pipe_fd >= 0) 529 | write_or_close_sync_fd(&sync_pipe_fd, exit_status, exit_message); 530 | 531 | if (attach_symlink_dir_path != NULL && unlink(attach_symlink_dir_path) == -1 && errno != ENOENT) 532 | pexit("Failed to remove symlink for attach socket directory"); 533 | 534 | return exit_status; 535 | } 536 | -------------------------------------------------------------------------------- /src/conn_sock.h: -------------------------------------------------------------------------------- 1 | #if !defined(CONN_SOCK_H) 2 | #define CONN_SOCK_H 3 | 4 | #include /* gboolean */ 5 | #include "config.h" /* CONN_SOCK_BUF_SIZE */ 6 | 7 | #define SOCK_TYPE_CONSOLE 1 8 | #define SOCK_TYPE_NOTIFY 2 9 | #define SOCK_IS_CONSOLE(sock_type) ((sock_type) == SOCK_TYPE_CONSOLE) 10 | #define SOCK_IS_NOTIFY(sock_type) ((sock_type) == SOCK_TYPE_NOTIFY) 11 | #define SOCK_IS_STREAM(sock_type) ((sock_type) == SOCK_TYPE_CONSOLE) 12 | #define SOCK_IS_DGRAM(sock_type) ((sock_type) != SOCK_TYPE_CONSOLE) 13 | 14 | /* Used for attach */ 15 | /* The nomenclature here is decided but may not be entirely intuitive. 16 | in_sock and out_sock doesn't seem right, because ctr_stdio 17 | breaks encapsulation and writes directly to the console 18 | sockets. 19 | In most cases in conn_sock.c, this struct is "INPUT" and 20 | the next one is "OUTPUT". Really it's this struct is "one" 21 | and the next is "many", but I don't want the same fd in 22 | two different sockets. 23 | 24 | "remote" indicates "Remote User" i.e. attached console, or 25 | "container's /dev/log" or "container's /run/notify" 26 | "local" incidates "A socket we own, locally" i.e. mainfd_stdin 27 | or "host /dev/log" or "host /run/systemd/notify" 28 | */ 29 | struct remote_sock_s { 30 | int sock_type; 31 | int fd; 32 | struct local_sock_s *dest; 33 | gboolean listening; 34 | gboolean data_ready; 35 | gboolean readable; 36 | gboolean writable; 37 | size_t remaining; 38 | size_t off; 39 | char buf[CONN_SOCK_BUF_SIZE + 1]; // Extra byte allows null-termination 40 | }; 41 | 42 | struct local_sock_s { 43 | int *fd; 44 | gboolean is_stream; 45 | GPtrArray *readers; 46 | char *label; 47 | struct sockaddr_un *addr; 48 | }; 49 | 50 | char *setup_console_socket(void); 51 | char *setup_seccomp_socket(const char *socket); 52 | char *setup_attach_socket(void); 53 | void setup_notify_socket(char *); 54 | void schedule_main_stdin_write(); 55 | void write_back_to_remote_consoles(char *buf, int len); 56 | void close_all_readers(); 57 | 58 | #endif // CONN_SOCK_H 59 | -------------------------------------------------------------------------------- /src/ctr_exit.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include "ctr_exit.h" 4 | #include "cli.h" // opt_exit_command, opt_exit_delay, opt_socket_path, opt_cuuid 5 | #include "utils.h" 6 | #include "parent_pipe_fd.h" 7 | #include "globals.h" 8 | #include "ctr_logging.h" 9 | #include "close_fds.h" 10 | #include "oom.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | volatile pid_t container_pid = -1; 20 | volatile pid_t create_pid = -1; 21 | 22 | void on_sig_exit(int signal) 23 | { 24 | if (container_pid > 0) { 25 | if (kill(container_pid, signal) == 0) 26 | return; 27 | } else if (create_pid > 0) { 28 | if (kill(create_pid, signal) == 0) 29 | return; 30 | if (errno == ESRCH) { 31 | /* The create_pid process might have exited, so try container_pid again. */ 32 | if (container_pid > 0 && kill(container_pid, signal) == 0) 33 | return; 34 | } 35 | } 36 | /* Just force a check if we get here. */ 37 | raise(SIGUSR1); 38 | } 39 | 40 | static void check_child_processes(GHashTable *pid_to_handler, GHashTable *cache) 41 | { 42 | for (;;) { 43 | int status; 44 | pid_t pid = waitpid(-1, &status, WNOHANG); 45 | if (pid < 0 && errno == EINTR) 46 | continue; 47 | 48 | if (pid < 0 && errno == ECHILD) { 49 | g_main_loop_quit(main_loop); 50 | return; 51 | } 52 | if (pid < 0) 53 | pexit("Failed to read child process status"); 54 | 55 | if (pid == 0) 56 | return; 57 | 58 | /* If we got here, pid > 0, so we have a valid pid to check. */ 59 | void (*cb)(GPid, int, gpointer) = g_hash_table_lookup(pid_to_handler, &pid); 60 | if (cb) { 61 | cb(pid, status, 0); 62 | } else if (cache) { 63 | pid_t *k = g_malloc(sizeof(pid_t)); 64 | int *v = g_malloc(sizeof(int)); 65 | if (k == NULL || v == NULL) 66 | pexit("Failed to allocate memory"); 67 | *k = pid; 68 | *v = status; 69 | g_hash_table_insert(cache, k, v); 70 | } 71 | } 72 | } 73 | 74 | gboolean check_child_processes_cb(gpointer user_data) 75 | { 76 | struct pid_check_data *data = (struct pid_check_data *)user_data; 77 | check_child_processes(data->pid_to_handler, data->exit_status_cache); 78 | return G_SOURCE_REMOVE; 79 | } 80 | 81 | gboolean on_signalfd_cb(gint fd, G_GNUC_UNUSED GIOCondition condition, gpointer user_data) 82 | { 83 | struct pid_check_data *data = (struct pid_check_data *)user_data; 84 | 85 | /* drop the signal from the signalfd */ 86 | drop_signal_event(fd); 87 | 88 | check_child_processes(data->pid_to_handler, data->exit_status_cache); 89 | return G_SOURCE_CONTINUE; 90 | } 91 | 92 | gboolean timeout_cb(G_GNUC_UNUSED gpointer user_data) 93 | { 94 | timed_out = TRUE; 95 | ninfo("Timed out, killing main loop"); 96 | g_main_loop_quit(main_loop); 97 | return G_SOURCE_REMOVE; 98 | } 99 | 100 | int get_exit_status(int status) 101 | { 102 | if (WIFEXITED(status)) 103 | return WEXITSTATUS(status); 104 | if (WIFSIGNALED(status)) 105 | return 128 + WTERMSIG(status); 106 | return -1; 107 | } 108 | 109 | void runtime_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data) 110 | { 111 | runtime_status = status; 112 | create_pid = -1; 113 | g_main_loop_quit(main_loop); 114 | } 115 | 116 | void container_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data) 117 | { 118 | if (get_exit_status(status) != 0) { 119 | ninfof("container %d exited with status %d", pid, get_exit_status(status)); 120 | } 121 | container_status = status; 122 | container_pid = -1; 123 | /* In the case of a quickly exiting exec command, the container exit callback 124 | sometimes gets called earlier than the pid exit callback. If we quit the loop at that point 125 | we risk falsely telling the caller of conmon the runtime call failed (because runtime status 126 | wouldn't be set). Instead, don't quit the loop until runtime exit is also called, which should 127 | shortly after. */ 128 | if (opt_api_version >= 1 && create_pid > 0 && opt_exec && opt_terminal) { 129 | ndebugf("container pid return handled before runtime pid return. Not quitting yet."); 130 | return; 131 | } 132 | 133 | g_main_loop_quit(main_loop); 134 | } 135 | 136 | void do_exit_command() 137 | { 138 | if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) { 139 | _pexit("Failed to reset signal for SIGCHLD"); 140 | } 141 | 142 | /* 143 | * Close everything except stdin, stdout and stderr. 144 | */ 145 | close_all_fds_ge_than(3); 146 | 147 | /* 148 | * We don't want the exit command to be reaped by the parent conmon 149 | * as that would prevent double-fork from doing its job. 150 | * Unfortunately, that also means that any new subchildren from 151 | * still running processes could also get lost 152 | */ 153 | if (set_subreaper(false) != 0) { 154 | nwarn("Failed to disable self subreaper attribute - might wait for indirect children a long time"); 155 | } 156 | 157 | pid_t exit_pid = fork(); 158 | if (exit_pid < 0) { 159 | _pexit("Failed to fork"); 160 | } 161 | 162 | if (exit_pid) { 163 | int ret, exit_status = 0; 164 | 165 | /* 166 | * Make sure to cleanup any zombie process that the container runtime 167 | * could have left around. 168 | */ 169 | do { 170 | int tmp; 171 | 172 | exit_status = 0; 173 | ret = waitpid(-1, &tmp, 0); 174 | if (ret == exit_pid) 175 | exit_status = get_exit_status(tmp); 176 | } while ((ret < 0 && errno == EINTR) || ret > 0); 177 | 178 | if (exit_status) 179 | _exit(exit_status); 180 | 181 | return; 182 | } 183 | 184 | /* Count the additional args, if any. */ 185 | size_t n_args = 0; 186 | if (opt_exit_args) 187 | for (; opt_exit_args[n_args]; n_args++) 188 | ; 189 | 190 | gchar **args = malloc(sizeof(gchar *) * (n_args + 2)); 191 | if (args == NULL) 192 | _exit(EXIT_FAILURE); 193 | 194 | args[0] = opt_exit_command; 195 | if (opt_exit_args) 196 | for (n_args = 0; opt_exit_args[n_args]; n_args++) 197 | args[n_args + 1] = opt_exit_args[n_args]; 198 | args[n_args + 1] = NULL; 199 | 200 | if (opt_exit_delay) { 201 | ndebugf("Sleeping for %d seconds before executing exit command", opt_exit_delay); 202 | sleep(opt_exit_delay); 203 | } 204 | 205 | reset_oom_adjust(); 206 | 207 | execv(opt_exit_command, args); 208 | 209 | /* Should not happen, but better be safe. */ 210 | _exit(EXIT_FAILURE); 211 | } 212 | 213 | void reap_children() 214 | { 215 | /* We need to reap any zombies (from an OCI runtime that errored) before 216 | exiting */ 217 | while (waitpid(-1, NULL, WNOHANG) > 0) 218 | ; 219 | } 220 | 221 | void cleanup_socket_dir_symlink() 222 | { 223 | /* A symbolic link might be created at {opt_socket_path}/{opt_cuuid} if the container 224 | is created successfully. 225 | This function will take care of removing the link when the conmon process exits. */ 226 | _cleanup_free_ char *base_path = g_build_filename(opt_socket_path, opt_cuuid, NULL); 227 | if (unlink(base_path) == -1 && errno != ENOENT) 228 | pexitf("Failed to remove existing symlink for the socket directory %s", base_path); 229 | } 230 | 231 | void handle_signal(G_GNUC_UNUSED const int signum) 232 | { 233 | exit(EXIT_FAILURE); 234 | } 235 | -------------------------------------------------------------------------------- /src/ctr_exit.h: -------------------------------------------------------------------------------- 1 | #if !defined(CTR_EXIT_H) 2 | #define CTR_EXIT_H 3 | 4 | #include /* pid_t */ 5 | #include /* gpointer, gboolean, GHashTable, and GPid */ 6 | 7 | 8 | extern volatile pid_t container_pid; 9 | extern volatile pid_t create_pid; 10 | 11 | struct pid_check_data { 12 | GHashTable *pid_to_handler; 13 | GHashTable *exit_status_cache; 14 | }; 15 | 16 | void on_sig_exit(int signal); 17 | void container_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data); 18 | gboolean check_child_processes_cb(gpointer user_data); 19 | gboolean on_signalfd_cb(gint fd, GIOCondition condition, gpointer user_data); 20 | gboolean timeout_cb(G_GNUC_UNUSED gpointer user_data); 21 | int get_exit_status(int status); 22 | void runtime_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data); 23 | void container_exit_cb(G_GNUC_UNUSED GPid pid, int status, G_GNUC_UNUSED gpointer user_data); 24 | void do_exit_command(); 25 | void reap_children(); 26 | void cleanup_socket_dir_symlink(); 27 | void handle_signal(G_GNUC_UNUSED const int signum); 28 | 29 | #endif // CTR_EXIT_H 30 | -------------------------------------------------------------------------------- /src/ctr_logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if !defined(CTR_LOGGING_H) 3 | #define CTR_LOGGING_H 4 | 5 | #include "utils.h" /* stdpipe_t */ 6 | #include /* bool */ 7 | 8 | void reopen_log_files(void); 9 | bool write_to_logs(stdpipe_t pipe, char *buf, ssize_t num_read); 10 | void configure_log_drivers(gchar **log_drivers, int64_t log_size_max_, int64_t log_global_size_max_, char *cuuid_, char *name_, char *tag, 11 | gchar **labels); 12 | void sync_logs(void); 13 | gboolean logging_is_passthrough(void); 14 | void close_logging_fds(void); 15 | 16 | #endif /* !defined(CTR_LOGGING_H) */ 17 | -------------------------------------------------------------------------------- /src/ctr_stdio.c: -------------------------------------------------------------------------------- 1 | #include "ctr_stdio.h" 2 | #include "globals.h" 3 | #include "config.h" 4 | #include "conn_sock.h" 5 | #include "utils.h" 6 | #include "ctr_logging.h" 7 | #include "cli.h" 8 | 9 | #include 10 | #include 11 | 12 | static gboolean tty_hup_timeout_scheduled = false; 13 | static bool read_stdio(int fd, stdpipe_t pipe, gboolean *eof); 14 | static void drain_log_buffers(stdpipe_t pipe); 15 | static gboolean tty_hup_timeout_cb(G_GNUC_UNUSED gpointer user_data); 16 | 17 | 18 | gboolean stdio_cb(int fd, GIOCondition condition, gpointer user_data) 19 | { 20 | stdpipe_t pipe = GPOINTER_TO_INT(user_data); 21 | gboolean read_eof = FALSE; 22 | gboolean has_input = (condition & G_IO_IN) != 0; 23 | gboolean has_hup = (condition & G_IO_HUP) != 0; 24 | 25 | /* When we get here, condition can be G_IO_IN and/or G_IO_HUP. 26 | IN means there is some data to read. 27 | HUP means the other side closed the fd. In the case of a pine 28 | this in final, and we will never get more data. However, in the 29 | terminal case this just means that nobody has the terminal 30 | open at this point, and this can be change whenever someone 31 | opens the tty */ 32 | 33 | /* Read any data before handling hup */ 34 | if (has_input) { 35 | read_stdio(fd, pipe, &read_eof); 36 | } 37 | 38 | if (has_hup && opt_terminal && pipe == STDOUT_PIPE) { 39 | /* We got a HUP from the terminal main this means there 40 | are no open workers ptys atm, and we will get a lot 41 | of wakeups until we have one, switch to polling 42 | mode. */ 43 | 44 | /* If we read some data this cycle, wait one more, maybe there 45 | is more in the buffer before we handle the hup */ 46 | if (has_input && !read_eof) { 47 | return G_SOURCE_CONTINUE; 48 | } 49 | 50 | if (!tty_hup_timeout_scheduled) { 51 | g_timeout_add(100, tty_hup_timeout_cb, NULL); 52 | } 53 | tty_hup_timeout_scheduled = true; 54 | return G_SOURCE_REMOVE; 55 | } 56 | 57 | /* End of input */ 58 | if (read_eof || (has_hup && !has_input)) { 59 | /* There exists a case that the process has already exited 60 | * and we know about it (because we checked our child processes) 61 | * but we needed to run the main_loop to catch all the rest of the output 62 | * (specifically, when we are exec, but not terminal) 63 | * In this case, after both the stderr and stdout pipes have closed 64 | * we should quit the loop. Otherwise, conmon will hang forever 65 | * waiting for container_exit_cb that will never be called. 66 | */ 67 | if (pipe == STDOUT_PIPE) { 68 | mainfd_stdout = -1; 69 | if (container_status >= 0 && mainfd_stderr < 0) { 70 | g_main_loop_quit(main_loop); 71 | } 72 | } 73 | if (pipe == STDERR_PIPE) { 74 | mainfd_stderr = -1; 75 | if (container_status >= 0 && mainfd_stdout < 0) { 76 | g_main_loop_quit(main_loop); 77 | } 78 | } 79 | 80 | close(fd); 81 | return G_SOURCE_REMOVE; 82 | } 83 | 84 | return G_SOURCE_CONTINUE; 85 | } 86 | 87 | void drain_stdio() 88 | { 89 | if (mainfd_stdout != -1) { 90 | g_unix_set_fd_nonblocking(mainfd_stdout, TRUE, NULL); 91 | while (read_stdio(mainfd_stdout, STDOUT_PIPE, NULL)) 92 | ; 93 | } 94 | drain_log_buffers(STDOUT_PIPE); 95 | if (mainfd_stderr != -1) { 96 | g_unix_set_fd_nonblocking(mainfd_stderr, TRUE, NULL); 97 | while (read_stdio(mainfd_stderr, STDERR_PIPE, NULL)) 98 | ; 99 | } 100 | drain_log_buffers(STDERR_PIPE); 101 | } 102 | 103 | /* the journald log writer is buffering partial lines so that whole log lines are emitted 104 | * to the journal as a unit. this flushes those buffers */ 105 | static void drain_log_buffers(stdpipe_t pipe) 106 | { 107 | /* We pass a single byte buffer because write_to_logs expects that there is one 108 | byte of capacity beyond the buflen that we specify */ 109 | char buf[1]; 110 | write_to_logs(pipe, buf, 0); 111 | } 112 | 113 | static bool read_stdio(int fd, stdpipe_t pipe, gboolean *eof) 114 | { 115 | /* We use two extra bytes. One at the start, which we don't read into, instead 116 | we use that for marking the pipe when we write to the attached socket. 117 | One at the end to guarantee a null-terminated buffer for journald logging*/ 118 | 119 | char real_buf[STDIO_BUF_SIZE + 2]; 120 | char *buf = real_buf + 1; 121 | ssize_t num_read = 0; 122 | 123 | if (eof) 124 | *eof = false; 125 | 126 | num_read = read(fd, buf, STDIO_BUF_SIZE); 127 | if (num_read == 0) { 128 | if (eof) 129 | *eof = true; 130 | return false; 131 | } else if (num_read < 0) { 132 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 133 | // Non-blocking mode - no data available, return gracefully 134 | return true; 135 | } 136 | /* Ignore EIO if fd is a tty, since this can happen when the tty is closed 137 | while we are reading from it. */ 138 | if (errno == EIO && isatty(fd)) { 139 | if (eof) 140 | *eof = true; 141 | return false; 142 | } 143 | nwarnf("stdio_input read failed: %m"); 144 | return false; 145 | } else { 146 | // Always null terminate the buffer, just in case. 147 | buf[num_read] = '\0'; 148 | 149 | bool written = write_to_logs(pipe, buf, num_read); 150 | if (!written) 151 | return false; 152 | 153 | real_buf[0] = pipe; 154 | write_back_to_remote_consoles(real_buf, num_read + 1); 155 | return true; 156 | } 157 | } 158 | 159 | 160 | static gboolean tty_hup_timeout_cb(G_GNUC_UNUSED gpointer user_data) 161 | { 162 | tty_hup_timeout_scheduled = false; 163 | g_unix_fd_add(mainfd_stdout, G_IO_IN, stdio_cb, GINT_TO_POINTER(STDOUT_PIPE)); 164 | return G_SOURCE_REMOVE; 165 | } 166 | -------------------------------------------------------------------------------- /src/ctr_stdio.h: -------------------------------------------------------------------------------- 1 | #if !defined(CTR_STDIO_H) 2 | #define CTR_STDIO_H 3 | 4 | #include /* GIOCondition and gpointer */ 5 | #include /* int64_t */ 6 | 7 | gboolean stdio_cb(int fd, GIOCondition condition, gpointer user_data); 8 | void drain_stdio(); 9 | 10 | #endif // CTR_STDIO_H 11 | -------------------------------------------------------------------------------- /src/ctrl.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include "ctrl.h" 4 | #include "utils.h" 5 | #include "globals.h" 6 | #include "config.h" 7 | #include "ctr_logging.h" 8 | #include "conn_sock.h" 9 | #include "cmsg.h" 10 | #include "cli.h" // opt_bundle_path 11 | #include "seccomp_notify.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | static void resize_winsz(int height, int width); 20 | static gboolean read_from_ctrl_buffer(int fd, gboolean (*line_process_func)(char *)); 21 | static gboolean process_terminal_ctrl_line(char *line); 22 | static gboolean process_winsz_ctrl_line(char *line); 23 | static void setup_fifo(int *fifo_r, int *fifo_w, char *filename, char *error_var_name); 24 | 25 | gboolean terminal_accept_cb(int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data) 26 | { 27 | 28 | ndebugf("about to accept from console_socket_fd: %d", fd); 29 | 30 | int connfd; 31 | do { 32 | connfd = accept4(fd, NULL, NULL, SOCK_CLOEXEC); 33 | } while (connfd < 0 && errno == EINTR); 34 | if (connfd < 0) { 35 | pexit("Failed to accept console-socket connection"); 36 | } 37 | 38 | /* Not accepting anything else. */ 39 | const char *csname = user_data; 40 | if (unlink(csname) < 0) 41 | nwarnf("failed to unlink %s", csname); 42 | 43 | close(fd); 44 | 45 | /* We exit if this fails. */ 46 | ndebugf("about to recvfd from connfd: %d", connfd); 47 | struct file_t console = recvfd(connfd); 48 | 49 | if (console.fd < 0) { 50 | pexit("Failed to receive console file descriptor"); 51 | } 52 | 53 | ndebugf("console = {.name = '%s'; .fd = %d}", console.name, console.fd); 54 | free(console.name); 55 | 56 | /* We change the terminal settings to match kube settings */ 57 | struct termios tset; 58 | if (tcgetattr(console.fd, &tset) == -1) { 59 | nwarn("Failed to get console terminal settings"); 60 | goto exit; 61 | } 62 | 63 | tset.c_oflag |= ONLCR; 64 | 65 | if (tcsetattr(console.fd, TCSANOW, &tset) == -1) 66 | nwarn("Failed to set console terminal settings"); 67 | 68 | exit: 69 | /* We only have a single fd for both pipes, so we just treat it as 70 | * stdout. stderr is ignored. */ 71 | mainfd_stdin = console.fd; 72 | mainfd_stdout = dup(console.fd); 73 | if (mainfd_stdout < 0) 74 | pexit("Failed to dup console file descriptor"); 75 | 76 | /* Now that we have a fd to the tty, make sure we handle any pending data 77 | * that was already buffered. */ 78 | schedule_main_stdin_write(); 79 | 80 | /* now that we've set mainfd_stdout, we can register the ctrl_winsz_cb 81 | * if we didn't set it here, we'd risk attempting to run ioctl on 82 | * a negative fd, and fail to resize the window */ 83 | g_unix_fd_add(winsz_fd_r, G_IO_IN, ctrl_winsz_cb, NULL); 84 | 85 | /* Clean up everything */ 86 | close(connfd); 87 | 88 | /* Since we've gotten our console from the runtime, we no longer need to 89 | be listening on this callback. */ 90 | return G_SOURCE_REMOVE; 91 | } 92 | 93 | /* 94 | * ctrl_winsz_cb is a callback after a window resize event is sent along the winsz fd. 95 | */ 96 | gboolean ctrl_winsz_cb(int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data) 97 | { 98 | return read_from_ctrl_buffer(fd, process_winsz_ctrl_line); 99 | } 100 | 101 | /* 102 | * process_winsz_ctrl_line processes a line passed to the winsz fd 103 | * after the terminal_ctrl fd receives a winsz event. 104 | * It reads a height and length, and resizes the pty with it. 105 | */ 106 | static gboolean process_winsz_ctrl_line(char *line) 107 | { 108 | int height, width, ret = -1; 109 | ret = sscanf(line, "%d %d\n", &height, &width); 110 | ndebugf("Height: %d, Width: %d", height, width); 111 | if (ret != 2) { 112 | nwarn("Failed to sscanf message"); 113 | return FALSE; 114 | } 115 | resize_winsz(height, width); 116 | return TRUE; 117 | } 118 | 119 | /* 120 | * ctrl_cb is a callback for handling events directly from the caller 121 | */ 122 | gboolean ctrl_cb(int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data) 123 | { 124 | return read_from_ctrl_buffer(fd, process_terminal_ctrl_line); 125 | } 126 | 127 | /* 128 | * process_terminal_ctrl_line takes a line from the 129 | * caller program (received through the terminal ctrl fd) 130 | * and either writes to the winsz fd (to handle terminal resize events) 131 | * or reopens log files. 132 | */ 133 | static gboolean process_terminal_ctrl_line(char *line) 134 | { 135 | /* while the height and width won't be used in this function, 136 | * we want to remove them from the buffer anyway 137 | */ 138 | int ctl_msg_type, height, width, ret = -1; 139 | ret = sscanf(line, "%d %d %d\n", &ctl_msg_type, &height, &width); 140 | if (ret != 3) { 141 | nwarn("Failed to sscanf message"); 142 | return FALSE; 143 | } 144 | 145 | ndebugf("Message type: %d", ctl_msg_type); 146 | switch (ctl_msg_type) { 147 | case WIN_RESIZE_EVENT: { 148 | _cleanup_free_ char *hw_str = g_strdup_printf("%d %d\n", height, width); 149 | if (write(winsz_fd_w, hw_str, strlen(hw_str)) < 0) { 150 | nwarn("Failed to write to window resizing fd. A resize event may have been dropped"); 151 | return FALSE; 152 | } 153 | break; 154 | } 155 | case REOPEN_LOGS_EVENT: 156 | reopen_log_files(); 157 | break; 158 | default: 159 | nwarnf("Unknown message type: %d", ctl_msg_type); 160 | break; 161 | } 162 | return TRUE; 163 | } 164 | 165 | /* 166 | * read_from_ctrl_buffer reads a line (of no more than CTLBUFSZ) from an fd, 167 | * and calls line_process_func. It is a generic way to handle input on an fd 168 | * line_process_func should return TRUE if it succeeds, and FALSE if it fails 169 | * to process the line. 170 | */ 171 | static gboolean read_from_ctrl_buffer(int fd, gboolean (*line_process_func)(char *)) 172 | { 173 | #define CTLBUFSZ 200 174 | static char ctlbuf[CTLBUFSZ]; 175 | static int readsz = CTLBUFSZ - 1; 176 | static char *readptr = ctlbuf; 177 | ssize_t num_read = read(fd, readptr, readsz); 178 | if (num_read <= 0) { 179 | nwarnf("Failed to read from fd %d", fd); 180 | return G_SOURCE_CONTINUE; 181 | } 182 | 183 | readptr[num_read] = '\0'; 184 | ndebugf("Got ctl message: %s on fd %d", ctlbuf, fd); 185 | 186 | char *beg = ctlbuf; 187 | char *newline = strchrnul(beg, '\n'); 188 | /* Process each message which ends with a line */ 189 | while (*newline != '\0') { 190 | if (!line_process_func(ctlbuf)) 191 | return G_SOURCE_CONTINUE; 192 | 193 | beg = newline + 1; 194 | newline = strchrnul(beg, '\n'); 195 | } 196 | if (num_read == (CTLBUFSZ - 1) && beg == ctlbuf) { 197 | /* 198 | * We did not find a newline in the entire buffer. 199 | * This shouldn't happen as our buffer is larger than 200 | * the message that we expect to receive. 201 | */ 202 | nwarn("Could not find newline in entire buffer"); 203 | } else if (*beg == '\0') { 204 | /* We exhausted all messages that were complete */ 205 | readptr = ctlbuf; 206 | readsz = CTLBUFSZ - 1; 207 | } else { 208 | /* 209 | * We copy remaining data to beginning of buffer 210 | * and advance readptr after that. 211 | */ 212 | int cp_rem = 0; 213 | do { 214 | ctlbuf[cp_rem++] = *beg++; 215 | } while (*beg != '\0'); 216 | readptr = ctlbuf + cp_rem; 217 | readsz = CTLBUFSZ - 1 - cp_rem; 218 | } 219 | 220 | return G_SOURCE_CONTINUE; 221 | } 222 | 223 | /* 224 | * resize_winsz resizes the pty window size. 225 | */ 226 | static void resize_winsz(int height, int width) 227 | { 228 | struct winsize ws; 229 | ws.ws_row = height; 230 | ws.ws_col = width; 231 | 232 | int ret = ioctl(mainfd_stdout, TIOCSWINSZ, &ws); 233 | if (ret == -1) 234 | nwarnf("Failed to set process pty terminal size: %m"); 235 | } 236 | 237 | 238 | void setup_console_fifo() 239 | { 240 | setup_fifo(&winsz_fd_r, &winsz_fd_w, "winsz", "window resize control fifo"); 241 | ndebugf("winsz read side: %d, winsz write side: %d", winsz_fd_r, winsz_fd_w); 242 | } 243 | 244 | int setup_terminal_control_fifo() 245 | { 246 | /* 247 | * Open a dummy writer to prevent getting flood of POLLHUPs when 248 | * last writer closes. 249 | */ 250 | int dummyfd = -1; 251 | setup_fifo(&terminal_ctrl_fd, &dummyfd, "ctl", "terminal control fifo"); 252 | ndebugf("terminal_ctrl_fd: %d", terminal_ctrl_fd); 253 | g_unix_fd_add(terminal_ctrl_fd, G_IO_IN, ctrl_cb, NULL); 254 | 255 | return dummyfd; 256 | } 257 | 258 | static void setup_fifo(int *fifo_r, int *fifo_w, char *filename, char *error_var_name) 259 | { 260 | _cleanup_free_ char *fifo_path = g_build_filename(opt_bundle_path, filename, NULL); 261 | 262 | if (!fifo_r || !fifo_w) 263 | pexitf("setup fifo was passed a NULL pointer"); 264 | 265 | if (mkfifo(fifo_path, 0660) == -1) { 266 | if (errno == EEXIST) { 267 | unlink(fifo_path); 268 | if (mkfifo(fifo_path, 0660) == -1) 269 | pexitf("Failed to mkfifo at %s", fifo_path); 270 | } 271 | } 272 | 273 | if ((*fifo_r = open(fifo_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC)) == -1) 274 | pexitf("Failed to open %s read half", error_var_name); 275 | 276 | if ((*fifo_w = open(fifo_path, O_WRONLY | O_CLOEXEC)) == -1) 277 | pexitf("Failed to open %s write half", error_var_name); 278 | } 279 | -------------------------------------------------------------------------------- /src/ctrl.h: -------------------------------------------------------------------------------- 1 | #if !defined(CTRL_H) 2 | #define CTRL_H 3 | 4 | #include /* gpointer */ 5 | 6 | gboolean terminal_accept_cb(int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data); 7 | gboolean ctrl_winsz_cb(int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data); 8 | gboolean ctrl_cb(int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data); 9 | void setup_console_fifo(); 10 | int setup_terminal_control_fifo(); 11 | 12 | #endif // CTRL_H 13 | -------------------------------------------------------------------------------- /src/globals.c: -------------------------------------------------------------------------------- 1 | #include "globals.h" 2 | 3 | int runtime_status = -1; 4 | int container_status = -1; 5 | 6 | int mainfd_stdin = -1; 7 | int mainfd_stdout = -1; 8 | int mainfd_stderr = -1; 9 | 10 | int attach_socket_fd = -1; 11 | int console_socket_fd = -1; 12 | int seccomp_socket_fd = -1; 13 | int terminal_ctrl_fd = -1; 14 | int inotify_fd = -1; 15 | int winsz_fd_w = -1; 16 | int winsz_fd_r = -1; 17 | int attach_pipe_fd = -1; 18 | int dev_null_r = -1; 19 | int dev_null_w = -1; 20 | 21 | gboolean timed_out = FALSE; 22 | 23 | GMainLoop *main_loop = NULL; 24 | -------------------------------------------------------------------------------- /src/globals.h: -------------------------------------------------------------------------------- 1 | #if !defined(GLOBALS_H) 2 | #define GLOBALS_H 3 | 4 | #include /* gboolean and GMainLoop */ 5 | 6 | /* Global state */ 7 | // TODO FIXME not static 8 | extern int runtime_status; 9 | extern int container_status; 10 | 11 | extern int mainfd_stdin; 12 | extern int mainfd_stdout; 13 | extern int mainfd_stderr; 14 | 15 | extern int attach_socket_fd; 16 | extern int console_socket_fd; 17 | extern int seccomp_socket_fd; 18 | extern int terminal_ctrl_fd; 19 | extern int inotify_fd; 20 | extern int winsz_fd_w; 21 | extern int winsz_fd_r; 22 | extern int attach_pipe_fd; 23 | extern int dev_null_r; 24 | extern int dev_null_w; 25 | 26 | extern gboolean timed_out; 27 | 28 | extern GMainLoop *main_loop; 29 | 30 | 31 | #endif // GLOBALS_H 32 | -------------------------------------------------------------------------------- /src/oom.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include "oom.h" 4 | #include "utils.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int old_oom_score = 0; 11 | 12 | static void write_oom_adjust(int oom_score, int *old_value) 13 | { 14 | #ifdef __linux__ 15 | char fmt_oom_score[16]; 16 | int oom_score_fd = open("/proc/self/oom_score_adj", O_RDWR | O_CLOEXEC); 17 | if (oom_score_fd < 0) { 18 | ndebugf("failed to open /proc/self/oom_score_adj: %m"); 19 | return; 20 | } 21 | if (old_value) { 22 | if (read(oom_score_fd, fmt_oom_score, sizeof(fmt_oom_score)) < 0) { 23 | ndebugf("failed to read from /proc/self/oom_score_adj: %m"); 24 | } 25 | *old_value = atoi(fmt_oom_score); 26 | } 27 | sprintf(fmt_oom_score, "%d", oom_score); 28 | if (write(oom_score_fd, fmt_oom_score, strlen(fmt_oom_score)) < 0) { 29 | ndebugf("failed to write to /proc/self/oom_score_adj: %m"); 30 | } 31 | close(oom_score_fd); 32 | #else 33 | (void)oom_score; 34 | (void)old_value; 35 | #endif 36 | } 37 | 38 | void attempt_oom_adjust(int oom_score) 39 | { 40 | write_oom_adjust(oom_score, &old_oom_score); 41 | } 42 | 43 | void reset_oom_adjust() 44 | { 45 | write_oom_adjust(old_oom_score, NULL); 46 | } 47 | -------------------------------------------------------------------------------- /src/oom.h: -------------------------------------------------------------------------------- 1 | #if !defined(OOM_H) 2 | #define OOM_H 3 | 4 | void attempt_oom_adjust(int oom_score); 5 | void reset_oom_adjust(); 6 | 7 | #endif // OOM_H 8 | -------------------------------------------------------------------------------- /src/parent_pipe_fd.c: -------------------------------------------------------------------------------- 1 | #include "parent_pipe_fd.h" 2 | #include "utils.h" 3 | #include "cli.h" 4 | 5 | #include 6 | 7 | int sync_pipe_fd = -1; 8 | 9 | static char *escape_json_string(const char *str); 10 | 11 | int get_pipe_fd_from_env(const char *envname) 12 | { 13 | char *endptr = NULL; 14 | 15 | char *pipe_str = getenv(envname); 16 | if (pipe_str == NULL) 17 | return -1; 18 | 19 | errno = 0; 20 | int pipe_fd = strtol(pipe_str, &endptr, 10); 21 | if (errno != 0 || *endptr != '\0') 22 | pexitf("unable to parse %s", envname); 23 | if (fcntl(pipe_fd, F_SETFD, FD_CLOEXEC) == -1) 24 | pexitf("unable to make %s CLOEXEC", envname); 25 | 26 | return pipe_fd; 27 | } 28 | 29 | // Write a message to the sync pipe, or close the file descriptor if it's a broken pipe. 30 | void write_or_close_sync_fd(int *fd, int res, const char *message) 31 | { 32 | const char *res_key; 33 | if (opt_api_version >= 1) 34 | res_key = "data"; 35 | else if (opt_exec) 36 | res_key = "exit_code"; 37 | else 38 | res_key = "pid"; 39 | 40 | ssize_t len; 41 | 42 | if (*fd == -1) 43 | return; 44 | 45 | _cleanup_free_ char *json = NULL; 46 | if (message) { 47 | _cleanup_free_ char *escaped_message = escape_json_string(message); 48 | json = g_strdup_printf("{\"%s\": %d, \"message\": \"%s\"}\n", res_key, res, escaped_message); 49 | } else { 50 | json = g_strdup_printf("{\"%s\": %d}\n", res_key, res); 51 | } 52 | 53 | len = strlen(json); 54 | if (write_all(*fd, json, len) != len) { 55 | if (errno == EPIPE) { 56 | close(*fd); 57 | *fd = -1; 58 | return; 59 | } 60 | pexit("Unable to send container stderr message to parent"); 61 | } 62 | } 63 | 64 | static char *escape_json_string(const char *str) 65 | { 66 | const char *p = str; 67 | GString *escaped = g_string_sized_new(strlen(str)); 68 | 69 | while (*p != 0) { 70 | char c = *p++; 71 | if (c == '\\' || c == '"') { 72 | g_string_append_c(escaped, '\\'); 73 | g_string_append_c(escaped, c); 74 | } else if (c == '\n') { 75 | g_string_append_printf(escaped, "\\n"); 76 | } else if (c == '\t') { 77 | g_string_append_printf(escaped, "\\t"); 78 | } else if ((c > 0 && c < 0x1f) || c == 0x7f) { 79 | g_string_append_printf(escaped, "\\u00%02x", (guint)c); 80 | } else { 81 | g_string_append_c(escaped, c); 82 | } 83 | } 84 | 85 | return g_string_free(escaped, FALSE); 86 | } 87 | -------------------------------------------------------------------------------- /src/parent_pipe_fd.h: -------------------------------------------------------------------------------- 1 | #if !defined(PARENT_PIPE_FD_H) 2 | #define PARENT_PIPE_FD_H 3 | 4 | 5 | void write_or_close_sync_fd(int *fd, int res, const char *message); 6 | int get_pipe_fd_from_env(const char *envname); 7 | extern int sync_pipe_fd; 8 | 9 | 10 | #endif // PARENT_PIPE_FD_H 11 | -------------------------------------------------------------------------------- /src/runtime_args.c: -------------------------------------------------------------------------------- 1 | #include "runtime_args.h" 2 | #include "cli.h" 3 | #include "config.h" 4 | #include "utils.h" 5 | 6 | static void add_argv(GPtrArray *argv_array, ...) G_GNUC_NULL_TERMINATED; 7 | static void add_argv(GPtrArray *argv_array, ...); 8 | static void end_argv(GPtrArray *argv_array); 9 | 10 | static void print_argv(GPtrArray *argv); 11 | static void append_argv(gpointer data, gpointer user_data); 12 | 13 | GPtrArray *configure_runtime_args(const char *const csname) 14 | { 15 | GPtrArray *runtime_argv = g_ptr_array_new(); 16 | add_argv(runtime_argv, opt_runtime_path, NULL); 17 | 18 | /* Generate the cmdline. */ 19 | if (!opt_exec && opt_systemd_cgroup) 20 | add_argv(runtime_argv, "--systemd-cgroup", NULL); 21 | 22 | if (opt_runtime_args) { 23 | size_t n_runtime_args = 0; 24 | while (opt_runtime_args[n_runtime_args]) 25 | add_argv(runtime_argv, opt_runtime_args[n_runtime_args++], NULL); 26 | } 27 | 28 | /* Set the exec arguments. */ 29 | if (opt_exec) { 30 | add_argv(runtime_argv, "exec", "--pid-file", opt_container_pid_file, "--process", opt_exec_process_spec, "--detach", NULL); 31 | } else { 32 | char *command; 33 | if (opt_restore_path) 34 | command = "restore"; 35 | else 36 | command = "create"; 37 | 38 | add_argv(runtime_argv, command, "--bundle", opt_bundle_path, "--pid-file", opt_container_pid_file, NULL); 39 | if (opt_no_pivot) 40 | add_argv(runtime_argv, "--no-pivot", NULL); 41 | if (opt_no_new_keyring) 42 | add_argv(runtime_argv, "--no-new-keyring", NULL); 43 | 44 | if (opt_restore_path) { 45 | /* 46 | * 'runc restore' is different from 'runc create' 47 | * as the container is immediately running after 48 | * a restore. Therefore the '--detach is needed' 49 | * so that runc returns once the container is running. 50 | * 51 | * '--image-path' is the path to the checkpoint 52 | * which will be become important when using pre-copy 53 | * migration where multiple checkpoints can be created 54 | * to reduce the container downtime during migration. 55 | * 56 | * '--work-path' is the directory CRIU will run in and 57 | * also place its log files. 58 | */ 59 | add_argv(runtime_argv, "--detach", "--image-path", opt_restore_path, "--work-path", opt_bundle_path, NULL); 60 | } 61 | } 62 | /* 63 | * opt_runtime_opts can contain 'runc restore' or 'runc exec' options like 64 | * '--tcp-established' or '--preserve-fds'. Instead of listing each option as 65 | * a special conmon option, this (--runtime-opt) provides 66 | * a generic interface to pass all those options to conmon 67 | * without requiring a code change for each new option. 68 | */ 69 | if (opt_runtime_opts) { 70 | size_t n_runtime_opts = 0; 71 | while (opt_runtime_opts[n_runtime_opts]) 72 | add_argv(runtime_argv, opt_runtime_opts[n_runtime_opts++], NULL); 73 | } 74 | 75 | 76 | if (csname != NULL) { 77 | add_argv(runtime_argv, "--console-socket", csname, NULL); 78 | } 79 | 80 | /* Container name comes last. */ 81 | add_argv(runtime_argv, opt_cid, NULL); 82 | end_argv(runtime_argv); 83 | 84 | print_argv(runtime_argv); 85 | 86 | return runtime_argv; 87 | } 88 | 89 | static void print_argv(GPtrArray *runtime_argv) 90 | { 91 | if (log_level != TRACE_LEVEL) 92 | return; 93 | GString *runtime_args_string = g_string_sized_new(BUF_SIZE); 94 | 95 | g_ptr_array_foreach(runtime_argv, append_argv, runtime_args_string); 96 | 97 | ntracef("calling runtime args: %s", runtime_args_string->str); 98 | } 99 | 100 | static void append_argv(gpointer data, gpointer user_data) 101 | { 102 | if (!data) 103 | return; 104 | char *arg = (char *)data; 105 | GString *args = (GString *)user_data; 106 | 107 | g_string_append(args, arg); 108 | g_string_append_c(args, ' '); 109 | } 110 | 111 | 112 | static void add_argv(GPtrArray *argv_array, ...) G_GNUC_NULL_TERMINATED; 113 | 114 | static void add_argv(GPtrArray *argv_array, ...) 115 | { 116 | va_list args; 117 | char *arg; 118 | 119 | va_start(args, argv_array); 120 | while ((arg = va_arg(args, char *))) 121 | g_ptr_array_add(argv_array, arg); 122 | va_end(args); 123 | } 124 | 125 | static void end_argv(GPtrArray *argv_array) 126 | { 127 | g_ptr_array_add(argv_array, NULL); 128 | } 129 | -------------------------------------------------------------------------------- /src/runtime_args.h: -------------------------------------------------------------------------------- 1 | #if !defined(RUNTIME_ARGS_H) 2 | #define RUNTIME_ARGS_H 3 | 4 | #include 5 | 6 | GPtrArray *configure_runtime_args(const char *const csname); 7 | 8 | #endif // RUNTIME_ARGS_H 9 | -------------------------------------------------------------------------------- /src/seccomp_notify.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #if __STDC_VERSION__ >= 199901L 3 | /* C99 or later */ 4 | #else 5 | #error conmon.c requires C99 or later 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "cli.h" // opt_bundle_path 16 | #include "utils.h" 17 | #include "cmsg.h" 18 | 19 | #ifdef USE_SECCOMP 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "seccomp_notify.h" 27 | 28 | 29 | #ifndef SECCOMP_USER_NOTIF_FLAG_CONTINUE 30 | #define SECCOMP_USER_NOTIF_FLAG_CONTINUE (1UL << 0) 31 | #endif 32 | 33 | static struct seccomp_notify_context_s *seccomp_notify_ctx; 34 | 35 | struct plugin { 36 | void *handle; 37 | void *opaque; 38 | run_oci_seccomp_notify_handle_request_cb handle_request_cb; 39 | }; 40 | 41 | struct seccomp_notify_context_s { 42 | struct plugin *plugins; 43 | size_t n_plugins; 44 | 45 | struct seccomp_notif_resp *sresp; 46 | struct seccomp_notif *sreq; 47 | struct seccomp_notif_sizes sizes; 48 | }; 49 | 50 | static inline void *xmalloc0(size_t size); 51 | static void cleanup_seccomp_plugins(); 52 | 53 | static int seccomp_syscall(unsigned int op, unsigned int flags, void *args); 54 | 55 | gboolean seccomp_cb(int fd, GIOCondition condition, G_GNUC_UNUSED gpointer user_data) 56 | { 57 | if (condition & G_IO_IN) { 58 | if (seccomp_notify_ctx == NULL) 59 | return G_SOURCE_REMOVE; 60 | 61 | int ret = seccomp_notify_plugins_event(seccomp_notify_ctx, fd); 62 | return ret == 0 ? G_SOURCE_CONTINUE : G_SOURCE_REMOVE; 63 | } 64 | return G_SOURCE_CONTINUE; 65 | } 66 | 67 | gboolean seccomp_accept_cb(int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data) 68 | { 69 | ndebugf("about to accept from seccomp_socket_fd: %d", fd); 70 | int connfd = accept4(fd, NULL, NULL, SOCK_CLOEXEC); 71 | if (connfd < 0) { 72 | nwarn("Failed to accept console-socket connection"); 73 | return G_SOURCE_CONTINUE; 74 | } 75 | 76 | struct file_t listener = recvfd(connfd); 77 | close(connfd); 78 | 79 | if (listener.fd < 0) { 80 | pexit("Failed to receive socket listener file descriptor"); 81 | } 82 | free(listener.name); 83 | 84 | _cleanup_free_ char *oci_config_path = g_strdup_printf("%s/config.json", opt_bundle_path); 85 | if (oci_config_path == NULL) { 86 | nwarn("Failed to allocate memory"); 87 | return G_SOURCE_CONTINUE; 88 | } 89 | 90 | struct seccomp_notify_conf_s conf = { 91 | .runtime_root_path = NULL, 92 | .name = opt_name, 93 | .bundle_path = opt_bundle_path, 94 | .oci_config_path = oci_config_path, 95 | }; 96 | int ret = seccomp_notify_plugins_load(&seccomp_notify_ctx, opt_seccomp_notify_plugins, &conf); 97 | if (ret < 0) { 98 | nwarn("Failed to initialize seccomp notify plugins"); 99 | return G_SOURCE_CONTINUE; 100 | } 101 | 102 | g_unix_set_fd_nonblocking(listener.fd, TRUE, NULL); 103 | g_unix_fd_add(listener.fd, G_IO_IN | G_IO_HUP, seccomp_cb, NULL); 104 | atexit(cleanup_seccomp_plugins); 105 | 106 | return G_SOURCE_CONTINUE; 107 | } 108 | 109 | int seccomp_notify_plugins_load(struct seccomp_notify_context_s **out, const char *plugins, struct seccomp_notify_conf_s *conf) 110 | { 111 | cleanup_seccomp_notify_context struct seccomp_notify_context_s *ctx = xmalloc0(sizeof *ctx); 112 | _cleanup_free_ char *b = NULL; 113 | char *it, *saveptr; 114 | size_t s; 115 | 116 | if (seccomp_syscall(SECCOMP_GET_NOTIF_SIZES, 0, &ctx->sizes) < 0) { 117 | pexit("Failed to get notifications size"); 118 | } 119 | 120 | ctx->sreq = xmalloc0(ctx->sizes.seccomp_notif); 121 | ctx->sresp = xmalloc0(ctx->sizes.seccomp_notif_resp); 122 | 123 | ctx->n_plugins = 1; 124 | for (it = b; it; it = strchr(it, ':')) 125 | ctx->n_plugins++; 126 | 127 | ctx->plugins = xmalloc0(sizeof(struct plugin) * (ctx->n_plugins + 1)); 128 | 129 | b = strdup(plugins); 130 | if (b == NULL) { 131 | pexit("Failed to strdup"); 132 | } 133 | for (s = 0, it = strtok_r(b, ":", &saveptr); it; s++, it = strtok_r(NULL, ":", &saveptr)) { 134 | run_oci_seccomp_notify_plugin_version_cb version_cb; 135 | run_oci_seccomp_notify_start_cb start_cb; 136 | void *opq = NULL; 137 | 138 | ctx->plugins[s].handle = dlopen(it, RTLD_NOW); 139 | if (ctx->plugins[s].handle == NULL) { 140 | pexitf("cannot load `%s`: %s", it, dlerror()); 141 | } 142 | 143 | version_cb = (run_oci_seccomp_notify_plugin_version_cb)dlsym(ctx->plugins[s].handle, "run_oci_seccomp_notify_version"); 144 | if (version_cb != NULL) { 145 | int version; 146 | 147 | version = version_cb(); 148 | if (version != 1) { 149 | pexitf("invalid version supported by the plugin `%s`", it); 150 | } 151 | } 152 | 153 | ctx->plugins[s].handle_request_cb = 154 | (run_oci_seccomp_notify_handle_request_cb)dlsym(ctx->plugins[s].handle, "run_oci_seccomp_notify_handle_request"); 155 | if (ctx->plugins[s].handle_request_cb == NULL) { 156 | pexitf("plugin `%s` doesn't export `run_oci_seccomp_notify_handle_request`", it); 157 | } 158 | 159 | start_cb = (run_oci_seccomp_notify_start_cb)dlsym(ctx->plugins[s].handle, "run_oci_seccomp_notify_start"); 160 | if (start_cb) { 161 | int ret; 162 | 163 | ret = start_cb(&opq, conf, sizeof(*conf)); 164 | if (ret != 0) { 165 | pexitf("error loading `%s`", it); 166 | } 167 | } 168 | ctx->plugins[s].opaque = opq; 169 | } 170 | 171 | /* Change ownership. */ 172 | *out = ctx; 173 | ctx = NULL; 174 | return 0; 175 | } 176 | 177 | int seccomp_notify_plugins_event(struct seccomp_notify_context_s *ctx, int seccomp_fd) 178 | { 179 | size_t i; 180 | int ret; 181 | bool handled = false; 182 | 183 | memset(ctx->sreq, 0, ctx->sizes.seccomp_notif); 184 | memset(ctx->sresp, 0, ctx->sizes.seccomp_notif_resp); 185 | 186 | ret = ioctl(seccomp_fd, SECCOMP_IOCTL_NOTIF_RECV, ctx->sreq); 187 | if (ret < 0) { 188 | if (errno == ENOENT) 189 | return 0; 190 | nwarnf("Failed to read notification from %d", seccomp_fd); 191 | return -1; 192 | } 193 | 194 | for (i = 0; i < ctx->n_plugins; i++) { 195 | if (ctx->plugins[i].handle_request_cb) { 196 | int resp_handled = 0; 197 | int ret; 198 | 199 | ret = ctx->plugins[i].handle_request_cb(ctx->plugins[i].opaque, &ctx->sizes, ctx->sreq, ctx->sresp, seccomp_fd, 200 | &resp_handled); 201 | if (ret != 0) { 202 | nwarnf("Failed to handle seccomp notification from fd %d", seccomp_fd); 203 | return -1; 204 | } 205 | 206 | switch (resp_handled) { 207 | case RUN_OCI_SECCOMP_NOTIFY_HANDLE_NOT_HANDLED: 208 | break; 209 | 210 | case RUN_OCI_SECCOMP_NOTIFY_HANDLE_SEND_RESPONSE: 211 | handled = true; 212 | break; 213 | 214 | /* The plugin will take care of it. */ 215 | case RUN_OCI_SECCOMP_NOTIFY_HANDLE_DELAYED_RESPONSE: 216 | return 0; 217 | 218 | case RUN_OCI_SECCOMP_NOTIFY_HANDLE_SEND_RESPONSE_AND_CONTINUE: 219 | ctx->sresp->flags |= SECCOMP_USER_NOTIF_FLAG_CONTINUE; 220 | handled = true; 221 | break; 222 | 223 | default: 224 | pexitf("Unknown handler action specified %d", handled); 225 | } 226 | } 227 | } 228 | 229 | /* No plugin could handle the request. */ 230 | if (!handled) { 231 | ctx->sresp->error = -ENOTSUP; 232 | ctx->sresp->flags = 0; 233 | } 234 | 235 | ctx->sresp->id = ctx->sreq->id; 236 | ret = ioctl(seccomp_fd, SECCOMP_IOCTL_NOTIF_SEND, ctx->sresp); 237 | if (ret < 0) { 238 | if (errno == ENOENT) 239 | return 0; 240 | nwarnf("Failed to send seccomp notification on fd %d", seccomp_fd); 241 | return -errno; 242 | } 243 | return 0; 244 | } 245 | 246 | int seccomp_notify_plugins_free(struct seccomp_notify_context_s *ctx) 247 | { 248 | size_t i; 249 | 250 | if (ctx == NULL) { 251 | nwarnf("Invalid seccomp notification context"); 252 | return -1; 253 | } 254 | 255 | free(ctx->sreq); 256 | free(ctx->sresp); 257 | 258 | for (i = 0; i < ctx->n_plugins; i++) { 259 | if (ctx->plugins && ctx->plugins[i].handle) { 260 | run_oci_seccomp_notify_stop_cb cb; 261 | 262 | cb = (run_oci_seccomp_notify_stop_cb)dlsym(ctx->plugins[i].handle, "run_oci_seccomp_notify_stop"); 263 | if (cb) 264 | cb(ctx->plugins[i].opaque); 265 | dlclose(ctx->plugins[i].handle); 266 | } 267 | } 268 | 269 | free(ctx); 270 | 271 | return 0; 272 | } 273 | 274 | static void cleanup_seccomp_plugins() 275 | { 276 | if (seccomp_notify_ctx) { 277 | seccomp_notify_plugins_free(seccomp_notify_ctx); 278 | seccomp_notify_ctx = NULL; 279 | } 280 | } 281 | 282 | void cleanup_seccomp_notify_pluginsp(void *p) 283 | { 284 | struct seccomp_notify_context_s **pp = p; 285 | if (*pp) { 286 | seccomp_notify_plugins_free(*pp); 287 | *pp = NULL; 288 | } 289 | } 290 | 291 | static inline void *xmalloc0(size_t size) 292 | { 293 | void *res = calloc(1, size); 294 | if (res == NULL) 295 | pexitf("calloc"); 296 | return res; 297 | } 298 | 299 | static int seccomp_syscall(unsigned int op, unsigned int flags, void *args) 300 | { 301 | errno = 0; 302 | return syscall(__NR_seccomp, op, flags, args); 303 | } 304 | #else 305 | gboolean seccomp_accept_cb(G_GNUC_UNUSED int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data) 306 | { 307 | pexit("seccomp support not available"); 308 | return G_SOURCE_REMOVE; 309 | } 310 | #endif 311 | -------------------------------------------------------------------------------- /src/seccomp_notify.h: -------------------------------------------------------------------------------- 1 | #ifndef SECCOMP_NOTIFY_H 2 | #define SECCOMP_NOTIFY_H 3 | 4 | #include "seccomp_notify_plugin.h" 5 | 6 | #ifdef USE_SECCOMP 7 | 8 | struct seccomp_notify_context_s; 9 | 10 | gboolean seccomp_cb(int fd, GIOCondition condition, G_GNUC_UNUSED gpointer user_data); 11 | 12 | int seccomp_notify_plugins_load(struct seccomp_notify_context_s **out, const char *plugins, struct seccomp_notify_conf_s *conf); 13 | int seccomp_notify_plugins_event(struct seccomp_notify_context_s *ctx, int seccomp_fd); 14 | int seccomp_notify_plugins_free(struct seccomp_notify_context_s *ctx); 15 | 16 | #define cleanup_seccomp_notify_context __attribute__((cleanup(cleanup_seccomp_notify_pluginsp))) 17 | void cleanup_seccomp_notify_pluginsp(void *p); 18 | 19 | #endif // USE_SECCOMP 20 | gboolean seccomp_accept_cb(int fd, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data); 21 | #endif // SECCOMP_NOTIFY_H 22 | -------------------------------------------------------------------------------- /src/seccomp_notify_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef SECCOMP_NOTIFY_PLUGIN_H 2 | 3 | #ifdef USE_SECCOMP 4 | 5 | #include 6 | 7 | struct seccomp_notify_conf_s { 8 | const char *runtime_root_path; 9 | const char *name; 10 | const char *bundle_path; 11 | const char *oci_config_path; 12 | }; 13 | 14 | /* The plugin doesn't know how to handle the request. */ 15 | #define RUN_OCI_SECCOMP_NOTIFY_HANDLE_NOT_HANDLED 0 16 | /* The plugin filled the response and it is ready to write. */ 17 | #define RUN_OCI_SECCOMP_NOTIFY_HANDLE_SEND_RESPONSE 1 18 | /* The plugin will handle the request and write directly to the fd. */ 19 | #define RUN_OCI_SECCOMP_NOTIFY_HANDLE_DELAYED_RESPONSE 2 20 | /* Specify SECCOMP_USER_NOTIF_FLAG_CONTINUE in the flags. */ 21 | #define RUN_OCI_SECCOMP_NOTIFY_HANDLE_SEND_RESPONSE_AND_CONTINUE 3 22 | 23 | /* Configure the plugin. Return an opaque pointer that will be used for successive calls. */ 24 | typedef int (*run_oci_seccomp_notify_start_cb)(void **opaque, struct seccomp_notify_conf_s *conf, size_t size_configuration); 25 | 26 | /* Try to handle a single request. It MUST be defined. 27 | HANDLED specifies how the request was handled by the plugin: 28 | 0: not handled, try next plugin or return ENOTSUP if it is the last plugin. 29 | RUN_OCI_SECCOMP_NOTIFY_HANDLE_SEND_RESPONSE: sresp filled and ready to be notified to seccomp. 30 | RUN_OCI_SECCOMP_NOTIFY_HANDLE_DELAYED_RESPONSE: the notification will be handled internally by the plugin and forwarded to seccomp_fd. It 31 | is useful for asynchronous handling. 32 | */ 33 | typedef int (*run_oci_seccomp_notify_handle_request_cb)(void *opaque, struct seccomp_notif_sizes *sizes, struct seccomp_notif *sreq, 34 | struct seccomp_notif_resp *sresp, int seccomp_fd, int *handled); 35 | 36 | /* Stop the plugin. The opaque value is the return value from run_oci_seccomp_notify_start. */ 37 | typedef int (*run_oci_seccomp_notify_stop_cb)(void *opaque); 38 | 39 | /* Retrieve the API version used by the plugin. It MUST return 1. */ 40 | typedef int (*run_oci_seccomp_notify_plugin_version_cb)(); 41 | 42 | #endif // USE_SECCOMP 43 | #endif // SECCOMP_NOTIFY_PLUGIN_H 44 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include "utils.h" 4 | #include 5 | #include 6 | #ifdef __linux__ 7 | #include 8 | #include 9 | #endif 10 | #ifdef __FreeBSD__ 11 | #include 12 | #include 13 | #endif 14 | 15 | log_level_t log_level = WARN_LEVEL; 16 | char *log_cid = NULL; 17 | gboolean use_syslog = FALSE; 18 | 19 | /* Set the log level for this call. log level defaults to warning. 20 | parse the string value of level_name to the appropriate log_level_t enum value 21 | */ 22 | void set_conmon_logs(char *level_name, char *cid_, gboolean syslog_, char *tag) 23 | { 24 | if (tag == NULL) 25 | log_cid = cid_; 26 | else 27 | log_cid = g_strdup_printf("%s: %s", cid_, tag); 28 | use_syslog = syslog_; 29 | // log_level is initialized as Warning, no need to set anything 30 | if (level_name == NULL) 31 | return; 32 | if (!strcasecmp(level_name, "error") || !strcasecmp(level_name, "fatal") || !strcasecmp(level_name, "panic")) { 33 | log_level = EXIT_LEVEL; 34 | return; 35 | } else if (!strcasecmp(level_name, "warn") || !strcasecmp(level_name, "warning")) { 36 | log_level = WARN_LEVEL; 37 | return; 38 | } else if (!strcasecmp(level_name, "info")) { 39 | log_level = INFO_LEVEL; 40 | return; 41 | } else if (!strcasecmp(level_name, "debug")) { 42 | log_level = DEBUG_LEVEL; 43 | return; 44 | } else if (!strcasecmp(level_name, "trace")) { 45 | log_level = TRACE_LEVEL; 46 | return; 47 | } 48 | ntracef("set log level to %s", level_name); 49 | nexitf("No such log level %s", level_name); 50 | } 51 | 52 | static bool retryable_error(int err) 53 | { 54 | return err == EINTR || err == EAGAIN || err == ENOBUFS; 55 | } 56 | 57 | static void get_signal_descriptor_mask(sigset_t *set) 58 | { 59 | sigemptyset(set); 60 | sigaddset(set, SIGCHLD); 61 | sigaddset(set, SIGUSR1); 62 | sigprocmask(SIG_BLOCK, set, NULL); 63 | } 64 | 65 | ssize_t write_all(int fd, const void *buf, size_t count) 66 | { 67 | size_t remaining = count; 68 | const char *p = buf; 69 | ssize_t res; 70 | 71 | while (remaining > 0) { 72 | do { 73 | res = write(fd, p, remaining); 74 | } while (res == -1 && retryable_error(errno)); 75 | 76 | if (res <= 0) 77 | return -1; 78 | 79 | remaining -= res; 80 | p += res; 81 | } 82 | 83 | return count; 84 | } 85 | 86 | #ifdef __linux__ 87 | 88 | int set_subreaper(gboolean enabled) 89 | { 90 | return prctl(PR_SET_CHILD_SUBREAPER, enabled, 0, 0, 0); 91 | } 92 | 93 | int set_pdeathsig(int sig) 94 | { 95 | return prctl(PR_SET_PDEATHSIG, sig); 96 | } 97 | 98 | int get_signal_descriptor() 99 | { 100 | sigset_t set; 101 | get_signal_descriptor_mask(&set); 102 | return signalfd(-1, &set, SFD_CLOEXEC); 103 | } 104 | 105 | void drop_signal_event(int fd) 106 | { 107 | struct signalfd_siginfo siginfo; 108 | ssize_t s = read(fd, &siginfo, sizeof siginfo); 109 | g_assert_cmpint(s, ==, sizeof siginfo); 110 | } 111 | 112 | #endif 113 | 114 | #ifdef __FreeBSD__ 115 | 116 | int set_subreaper(gboolean enabled) 117 | { 118 | if (enabled) { 119 | return procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL); 120 | } else { 121 | return procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL); 122 | } 123 | } 124 | 125 | int set_pdeathsig(int sig) 126 | { 127 | return procctl(P_PID, getpid(), PROC_PDEATHSIG_CTL, &sig); 128 | } 129 | 130 | int get_signal_descriptor() 131 | { 132 | sigset_t set; 133 | get_signal_descriptor_mask(&set); 134 | 135 | int kq = kqueue(); 136 | fcntl(kq, F_SETFD, FD_CLOEXEC); 137 | for (int sig = 1; sig < SIGRTMIN; sig++) { 138 | if (sigismember(&set, sig)) { 139 | struct kevent kev; 140 | EV_SET(&kev, sig, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); 141 | if (kevent(kq, &kev, 1, NULL, 0, NULL)) { 142 | pexitf("failed to add kevent signal %d", sig); 143 | } 144 | } 145 | } 146 | return kq; 147 | } 148 | 149 | void drop_signal_event(int kq) 150 | { 151 | struct kevent kev; 152 | int n = kevent(kq, NULL, 0, &kev, 1, NULL); 153 | if (n != 1) { 154 | pexit("failed to read signal event"); 155 | } 156 | } 157 | 158 | #endif 159 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if !defined(UTILS_H) 3 | #define UTILS_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /* stdpipe_t represents one of the std pipes (or NONE). 15 | * Sync with const in container_attach.go */ 16 | typedef enum { 17 | NO_PIPE, 18 | STDIN_PIPE, /* unused */ 19 | STDOUT_PIPE, 20 | STDERR_PIPE, 21 | } stdpipe_t; 22 | 23 | /* Different levels of logging */ 24 | typedef enum { 25 | EXIT_LEVEL, 26 | WARN_LEVEL, 27 | INFO_LEVEL, 28 | DEBUG_LEVEL, 29 | TRACE_LEVEL, 30 | } log_level_t; 31 | 32 | // Default log level is Warning, This will be configured before any logging 33 | // should happen 34 | extern log_level_t log_level; 35 | extern char *log_cid; 36 | extern gboolean use_syslog; 37 | 38 | #define _pexit(s) \ 39 | do { \ 40 | fprintf(stderr, "[conmon:e]: %s %m\n", s); \ 41 | if (use_syslog) \ 42 | syslog(LOG_ERR, "conmon %.20s : %s %m\n", log_cid, s); \ 43 | _exit(EXIT_FAILURE); \ 44 | } while (0) 45 | 46 | #define _pexitf(fmt, ...) \ 47 | do { \ 48 | fprintf(stderr, "[conmon:e]: " fmt " %m\n", ##__VA_ARGS__); \ 49 | if (use_syslog) \ 50 | syslog(LOG_ERR, "conmon %.20s : " fmt ": %m\n", log_cid, ##__VA_ARGS__); \ 51 | _exit(EXIT_FAILURE); \ 52 | } while (0) 53 | 54 | #define pexit(s) \ 55 | do { \ 56 | fprintf(stderr, "[conmon:e]: %s %m\n", s); \ 57 | if (use_syslog) \ 58 | syslog(LOG_ERR, "conmon %.20s : %s %m\n", log_cid, s); \ 59 | exit(EXIT_FAILURE); \ 60 | } while (0) 61 | 62 | #define pexitf(fmt, ...) \ 63 | do { \ 64 | fprintf(stderr, "[conmon:e]: " fmt " %m\n", ##__VA_ARGS__); \ 65 | if (use_syslog) \ 66 | syslog(LOG_ERR, "conmon %.20s : " fmt ": %m\n", log_cid, ##__VA_ARGS__); \ 67 | exit(EXIT_FAILURE); \ 68 | } while (0) 69 | 70 | #define nexit(s) \ 71 | do { \ 72 | fprintf(stderr, "[conmon:e] %s\n", s); \ 73 | if (use_syslog) \ 74 | syslog(LOG_ERR, "conmon %.20s : %s\n", log_cid, s); \ 75 | exit(EXIT_FAILURE); \ 76 | } while (0) 77 | 78 | #define nexitf(fmt, ...) \ 79 | do { \ 80 | fprintf(stderr, "[conmon:e]: " fmt "\n", ##__VA_ARGS__); \ 81 | if (use_syslog) \ 82 | syslog(LOG_ERR, "conmon %.20s : " fmt " \n", log_cid, ##__VA_ARGS__); \ 83 | exit(EXIT_FAILURE); \ 84 | } while (0) 85 | 86 | #define pwarn(s) \ 87 | do { \ 88 | fprintf(stderr, "[conmon:w]: %s %s\n", s, strerror(errno)); \ 89 | if (use_syslog) \ 90 | syslog(LOG_INFO, "conmon %.20s : %s %s\n", log_cid, s, strerror(errno)); \ 91 | } while (0) 92 | 93 | #define pwarnf(fmt, ...) \ 94 | if (log_level >= WARN_LEVEL) { \ 95 | do { \ 96 | fprintf(stderr, "[conmon:w]: " fmt " %s\n", ##__VA_ARGS__, strerror(errno)); \ 97 | if (use_syslog) \ 98 | syslog(LOG_INFO, "conmon %.20s : " fmt ": %s\n", log_cid, ##__VA_ARGS__, strerror(errno)); \ 99 | } while (0); \ 100 | } 101 | 102 | #define nwarn(s) \ 103 | if (log_level >= WARN_LEVEL) { \ 104 | do { \ 105 | fprintf(stderr, "[conmon:w]: %s\n", s); \ 106 | if (use_syslog) \ 107 | syslog(LOG_INFO, "conmon %.20s : %s\n", log_cid, s); \ 108 | } while (0); \ 109 | } 110 | 111 | #define nwarnf(fmt, ...) \ 112 | if (log_level >= WARN_LEVEL) { \ 113 | do { \ 114 | fprintf(stderr, "[conmon:w]: " fmt "\n", ##__VA_ARGS__); \ 115 | if (use_syslog) \ 116 | syslog(LOG_INFO, "conmon %.20s : " fmt " \n", log_cid, ##__VA_ARGS__); \ 117 | } while (0); \ 118 | } 119 | 120 | #define ninfo(s) \ 121 | if (log_level >= INFO_LEVEL) { \ 122 | do { \ 123 | fprintf(stderr, "[conmon:i]: %s\n", s); \ 124 | if (use_syslog) \ 125 | syslog(LOG_INFO, "conmon %.20s : %s\n", log_cid, s); \ 126 | } while (0); \ 127 | } 128 | 129 | #define ninfof(fmt, ...) \ 130 | if (log_level >= INFO_LEVEL) { \ 131 | do { \ 132 | fprintf(stderr, "[conmon:i]: " fmt "\n", ##__VA_ARGS__); \ 133 | if (use_syslog) \ 134 | syslog(LOG_INFO, "conmon %.20s : " fmt " \n", log_cid, ##__VA_ARGS__); \ 135 | } while (0); \ 136 | } 137 | 138 | #define ndebug(s) \ 139 | if (log_level >= DEBUG_LEVEL) { \ 140 | do { \ 141 | fprintf(stderr, "[conmon:d]: %s\n", s); \ 142 | if (use_syslog) \ 143 | syslog(LOG_INFO, "conmon %.20s : %s\n", log_cid, s); \ 144 | } while (0); \ 145 | } 146 | 147 | #define ndebugf(fmt, ...) \ 148 | if (log_level >= DEBUG_LEVEL) { \ 149 | do { \ 150 | fprintf(stderr, "[conmon:d]: " fmt "\n", ##__VA_ARGS__); \ 151 | if (use_syslog) \ 152 | syslog(LOG_INFO, "conmon %.20s : " fmt " \n", log_cid, ##__VA_ARGS__); \ 153 | } while (0); \ 154 | } 155 | 156 | #define ntrace(s) \ 157 | if (log_level >= TRACE_LEVEL) { \ 158 | do { \ 159 | fprintf(stderr, "[conmon:d]: %s\n", s); \ 160 | if (use_syslog) \ 161 | syslog(LOG_INFO, "conmon %.20s : %s\n", log_cid, s); \ 162 | } while (0); \ 163 | } 164 | 165 | #define ntracef(fmt, ...) \ 166 | if (log_level >= TRACE_LEVEL) { \ 167 | do { \ 168 | fprintf(stderr, "[conmon:d]: " fmt "\n", ##__VA_ARGS__); \ 169 | if (use_syslog) \ 170 | syslog(LOG_INFO, "conmon %.20s : " fmt " \n", log_cid, ##__VA_ARGS__); \ 171 | } while (0); \ 172 | } 173 | 174 | /* Set the log level for this call. log level defaults to warning. 175 | parse the string value of level_name to the appropriate log_level_t enum value 176 | */ 177 | void set_conmon_logs(char *level_name, char *cid_, gboolean syslog_, char *tag); 178 | 179 | #define _cleanup_(x) __attribute__((cleanup(x))) 180 | 181 | static inline void freep(void *p) 182 | { 183 | free(*(void **)p); 184 | } 185 | 186 | static inline void closep(int *fd) 187 | { 188 | if (*fd >= 0) 189 | close(*fd); 190 | *fd = -1; 191 | } 192 | 193 | static inline void fclosep(FILE **fp) 194 | { 195 | if (*fp) 196 | fclose(*fp); 197 | *fp = NULL; 198 | } 199 | 200 | static inline void gstring_free_cleanup(GString **string) 201 | { 202 | if (*string) 203 | g_string_free(*string, TRUE); 204 | } 205 | 206 | static inline void gerror_free_cleanup(GError **err) 207 | { 208 | if (*err) 209 | g_error_free(*err); 210 | } 211 | 212 | static inline void strv_cleanup(char ***strv) 213 | { 214 | if (strv) 215 | g_strfreev(*strv); 216 | } 217 | 218 | static inline void hashtable_free_cleanup(GHashTable **tbl) 219 | { 220 | if (tbl) 221 | g_hash_table_destroy(*tbl); 222 | } 223 | 224 | #define _cleanup_free_ _cleanup_(freep) 225 | #define _cleanup_close_ _cleanup_(closep) 226 | #define _cleanup_fclose_ _cleanup_(fclosep) 227 | #define _cleanup_gstring_ _cleanup_(gstring_free_cleanup) 228 | #define _cleanup_gerror_ _cleanup_(gerror_free_cleanup) 229 | #define _cleanup_strv_ _cleanup_(strv_cleanup) 230 | #define _cleanup_hashtable_ _cleanup_(hashtable_free_cleanup) 231 | 232 | 233 | #define WRITEV_BUFFER_N_IOV 128 234 | 235 | ssize_t write_all(int fd, const void *buf, size_t count); 236 | 237 | int set_subreaper(gboolean enabled); 238 | 239 | int set_pdeathsig(int sig); 240 | 241 | int get_signal_descriptor(); 242 | void drop_signal_event(int fd); 243 | 244 | #endif /* !defined(UTILS_H) */ 245 | --------------------------------------------------------------------------------