From f97ac2f7c53e6a38ccf3a502e6ec4b9c63ae41f3 Mon Sep 17 00:00:00 2001 From: Meghdad Date: Tue, 12 May 2026 19:12:02 +0330 Subject: [PATCH] Implemented Bionic/libc compatibility. --- .github/workflows/release.yml | 14 +++- .github/workflows/test.yml | 2 +- README.md | 2 + dockerized/Dockerfile | 9 ++- dockerized/README.md | 6 ++ dockerized/build.sh | 4 + dockerized/docker-compose.yml | 2 + docs/ARCHITECTURE.md | 2 + docs/RELEASES.md | 11 ++- src/ngx_http_monitoring_collectors.c | 108 ++++++++++++++++++++++++--- 10 files changed, 140 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb7c72a..304891d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,7 +34,8 @@ env: DEFAULT_NGINX_VERSIONS: "1.22.1 1.24.0 1.26.3 1.28.0 1.30.0" DEFAULT_TARGET_ARCHES: "linux-x86_64 linux-arm64 linux-armv7" MODULE_NAME: ngx_http_monitoring_module - RELEASE_BUILD_IMAGE: ubuntu:22.04 + RELEASE_BUILD_IMAGE: ubuntu:18.04 + RELEASE_LIBC_BASELINE: ubuntu-bionic-glibc-2.27 NGINX_CONFIGURE_ARGS: >- --with-compat --with-http_ssl_module @@ -226,6 +227,7 @@ jobs: -e NEXT_VERSION \ -e NGINX_CONFIGURE_ARGS \ -e RELEASE_BUILD_IMAGE \ + -e RELEASE_LIBC_BASELINE \ -e TARGET_ARCH="$target_arch" \ -v "$GITHUB_WORKSPACE:/work" \ -w /work \ @@ -306,6 +308,7 @@ jobs: printf 'target=%s\n' "$TARGET_ARCH" printf 'docker_platform=%s\n' "$DOCKER_PLATFORM" printf 'build_image=%s\n' "$RELEASE_BUILD_IMAGE" + printf 'libc_baseline=%s\n' "$RELEASE_LIBC_BASELINE" printf 'build_machine=%s\n' "$(uname -m)" printf 'runner=ubuntu-22.04\n' printf 'git_ref=%s\n' "$GITHUB_REF" @@ -318,6 +321,7 @@ jobs: printf '# %s for Nginx %s\n\n' "$MODULE_NAME" "$nginx_version" printf 'Release: `%s`\n\n' "$NEXT_TAG" printf 'Target: `%s` (`%s`)\n\n' "$TARGET_ARCH" "$DOCKER_PLATFORM" + printf 'Libc baseline: `%s`\n\n' "$RELEASE_LIBC_BASELINE" printf 'This package contains a prebuilt Linux dynamic module:\n\n' printf '```text\n' printf 'modules/%s.so\n' "$MODULE_NAME" @@ -326,8 +330,8 @@ jobs: printf '```nginx\n' printf 'load_module modules/%s.so;\n' "$MODULE_NAME" printf '```\n\n' - printf 'This binary is built with `--with-compat` against Nginx %s on Ubuntu 22.04 for %s. ' "$nginx_version" "$TARGET_ARCH" - printf 'For safest production use, run the same Nginx version, CPU architecture, and a compatible Linux/glibc environment. ' + printf 'This binary is built with `--with-compat` against Nginx %s on Ubuntu Bionic 18.04 for %s. ' "$nginx_version" "$TARGET_ARCH" + printf 'For safest production use, run the same Nginx version, CPU architecture, and a Linux/glibc environment compatible with `%s`. ' "$RELEASE_LIBC_BASELINE" printf 'If your Nginx package has a materially different module ABI, rebuild from source with this repository.\n' } > "$package_dir/INSTALL.md" @@ -354,6 +358,7 @@ jobs: { printf '# ngx_http_monitoring_module %s\n\n' "$NEXT_TAG" printf 'Previous tag: `%s`\n\n' "$LATEST_TAG" + printf 'Libc baseline: `%s`\n\n' "$RELEASE_LIBC_BASELINE" printf 'Prebuilt Linux dynamic module packages are attached for targets: `%s`.\n\n' "$NORMALIZED_TARGET_ARCHES" printf 'Each tarball is built against the Nginx version and target architecture in its filename and includes:\n\n' printf -- '- `modules/%s.so`\n' "$MODULE_NAME" @@ -417,6 +422,9 @@ jobs: file: dockerized/Dockerfile platforms: ${{ steps.targets.outputs.docker_platforms }} push: true + build-args: | + BASE_IMAGE=ubuntu:18.04 + OPENSSL_RUNTIME_PACKAGE=libssl1.1 tags: | ${{ steps.image.outputs.repo }}:${{ steps.version.outputs.next_tag }} ${{ steps.image.outputs.repo }}:latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d5ba0f9..6c56a95 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ permissions: contents: read env: - TEST_BUILD_IMAGE: ubuntu:22.04 + TEST_BUILD_IMAGE: ubuntu:18.04 NGINX_CONFIGURE_ARGS: >- --with-compat --with-http_ssl_module diff --git a/README.md b/README.md index e85092e..21e500c 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ The compiled module is written to: build/ngx_http_monitoring_module.so ``` +The source avoids glibc-only mount parsing APIs such as `getmntent()` and parses `/proc/mounts` directly, which keeps the collectors portable across Linux libc implementations that expose the required `/proc`, `statvfs()`, and `getifaddrs()` interfaces. + ## Docker Image A Dockerized Nginx image is available in [dockerized](): diff --git a/dockerized/Dockerfile b/dockerized/Dockerfile index 6b64e2f..62880ac 100644 --- a/dockerized/Dockerfile +++ b/dockerized/Dockerfile @@ -1,7 +1,7 @@ -ARG DEBIAN_VERSION=bookworm +ARG BASE_IMAGE=debian:bookworm-slim ARG NGINX_VERSION=1.28.0 -FROM debian:${DEBIAN_VERSION}-slim AS builder +FROM ${BASE_IMAGE} AS builder ARG NGINX_VERSION ARG NGINX_CONFIGURE_ARGS="--with-compat --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_v2_module --with-http_gzip_static_module" @@ -48,9 +48,10 @@ RUN ./configure \ && cp objs/ngx_http_monitoring_module.so /usr/local/nginx/modules/ -FROM debian:${DEBIAN_VERSION}-slim AS runtime +FROM ${BASE_IMAGE} AS runtime ARG NGINX_VERSION +ARG OPENSSL_RUNTIME_PACKAGE=libssl3 LABEL org.opencontainers.image.title="ngx_http_monitoring_module nginx image" \ org.opencontainers.image.description="Nginx built with ngx_http_monitoring_module and embedded monitoring dashboard" \ @@ -60,7 +61,7 @@ RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ libpcre2-8-0 \ - libssl3 \ + ${OPENSSL_RUNTIME_PACKAGE} \ zlib1g \ && rm -rf /var/lib/apt/lists/* \ && groupadd --system --gid 101 nginx \ diff --git a/dockerized/README.md b/dockerized/README.md index c6797e6..9591b67 100644 --- a/dockerized/README.md +++ b/dockerized/README.md @@ -22,6 +22,12 @@ To choose a different Nginx version: NGINX_VERSION=1.28.0 sh dockerized/build.sh ``` +To build the image on an Ubuntu Bionic 18.04 libc baseline: + +```sh +BASE_IMAGE=ubuntu:18.04 OPENSSL_RUNTIME_PACKAGE=libssl1.1 sh dockerized/build.sh +``` + ## Run ```sh diff --git a/dockerized/build.sh b/dockerized/build.sh index 5907f86..2814473 100644 --- a/dockerized/build.sh +++ b/dockerized/build.sh @@ -3,11 +3,15 @@ set -eu IMAGE="${IMAGE:-ngx-http-monitoring-module:local}" NGINX_VERSION="${NGINX_VERSION:-1.28.0}" +BASE_IMAGE="${BASE_IMAGE:-debian:bookworm-slim}" +OPENSSL_RUNTIME_PACKAGE="${OPENSSL_RUNTIME_PACKAGE:-libssl3}" cd "$(dirname "$0")/.." docker build \ --build-arg "NGINX_VERSION=$NGINX_VERSION" \ + --build-arg "BASE_IMAGE=$BASE_IMAGE" \ + --build-arg "OPENSSL_RUNTIME_PACKAGE=$OPENSSL_RUNTIME_PACKAGE" \ -f dockerized/Dockerfile \ -t "$IMAGE" \ . diff --git a/dockerized/docker-compose.yml b/dockerized/docker-compose.yml index 38a5c96..4c3f5ea 100644 --- a/dockerized/docker-compose.yml +++ b/dockerized/docker-compose.yml @@ -4,7 +4,9 @@ services: context: .. dockerfile: dockerized/Dockerfile args: + BASE_IMAGE: "debian:bookworm-slim" NGINX_VERSION: "1.28.0" + OPENSSL_RUNTIME_PACKAGE: "libssl3" image: ngx-http-monitoring-module:local container_name: ngx-http-monitoring-module ports: diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5eca9e2..9f7e0ec 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -58,3 +58,5 @@ Upstream metrics are passively observed from Nginx upstream state recorded on pr ## Compatibility The module is designed as an Nginx dynamic HTTP module and should be built with `--with-compat` against the target Nginx source tree. Stub-status counters are compiled in when the target Nginx build includes `--with-http_stub_status_module`; otherwise request counters still work but low-level active/reading/writing/waiting counters remain zero. + +Collectors avoid glibc-only helpers such as `getmntent()` and parse `/proc/mounts` directly. This keeps filesystem collection compatible with libc implementations that do not ship ``, while still requiring Linux `/proc`, `statvfs()`, and `getifaddrs()`. diff --git a/docs/RELEASES.md b/docs/RELEASES.md index be1cd30..338e759 100644 --- a/docs/RELEASES.md +++ b/docs/RELEASES.md @@ -1,6 +1,6 @@ # Release Builds -The GitHub Actions workflow in `.github/workflows/release.yml` builds ready-to-use Linux dynamic module packages for x86_64 and ARM targets. +The GitHub Actions workflow in `.github/workflows/release.yml` builds ready-to-use Linux dynamic module packages for x86_64 and ARM targets. Release binaries are built in Ubuntu Bionic 18.04 containers, giving the module artifacts a `ubuntu-bionic-glibc-2.27` libc baseline. ## Manual Release Only @@ -71,6 +71,13 @@ ghcr.io//:latest The image is built with Docker Buildx for the same normalized target platforms requested by `target_arches`. +The release image build passes: + +```text +BASE_IMAGE=ubuntu:18.04 +OPENSSL_RUNTIME_PACKAGE=libssl1.1 +``` + ## Compatibility Warning -Nginx dynamic modules are ABI-sensitive. The release packages are built with `--with-compat` on Ubuntu 22.04 against the Nginx version and CPU architecture in the filename. Use the matching Nginx version, CPU architecture, and a compatible Linux/glibc runtime. Rebuild locally when using a vendor Nginx package with materially different module ABI or hardening options. +Nginx dynamic modules are ABI-sensitive. The release packages are built with `--with-compat` on Ubuntu Bionic 18.04 against the Nginx version and CPU architecture in the filename. Use the matching Nginx version, CPU architecture, and a Linux/glibc runtime compatible with `ubuntu-bionic-glibc-2.27`. Rebuild locally when using a vendor Nginx package with materially different module ABI or hardening options. diff --git a/src/ngx_http_monitoring_collectors.c b/src/ngx_http_monitoring_collectors.c index 5b93ebc..4c14e37 100644 --- a/src/ngx_http_monitoring_collectors.c +++ b/src/ngx_http_monitoring_collectors.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -41,6 +40,9 @@ static ngx_atomic_uint_t ngx_http_monitoring_sum_disk( static ngx_atomic_uint_t ngx_http_monitoring_delta(ngx_atomic_uint_t now, ngx_atomic_uint_t prev); static ngx_uint_t ngx_http_monitoring_skip_fs(const char *type); +static char *ngx_http_monitoring_next_mount_field(char **cursor); +static void ngx_http_monitoring_copy_mount_field(u_char *dst, size_t size, + char *src); ngx_int_t @@ -599,25 +601,45 @@ static void ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh) { FILE *fp; - struct mntent *mnt; + char line[4096], *cursor, *source, *mount, *type; + u_char mount_path[4096], fs_type[NGX_HTTP_MONITORING_NAME_LEN]; struct statvfs st; ngx_uint_t count; uint64_t total, free_bytes, avail, used; - fp = setmntent("/proc/mounts", "r"); + fp = fopen("/proc/mounts", "r"); if (fp == NULL) { return; } count = 0; - while ((mnt = getmntent(fp)) != NULL - && count < NGX_HTTP_MONITORING_FILESYSTEMS_MAX) + while (count < NGX_HTTP_MONITORING_FILESYSTEMS_MAX + && fgets(line, sizeof(line), fp) != NULL) { - if (ngx_http_monitoring_skip_fs(mnt->mnt_type)) { + cursor = line; + source = ngx_http_monitoring_next_mount_field(&cursor); + mount = ngx_http_monitoring_next_mount_field(&cursor); + type = ngx_http_monitoring_next_mount_field(&cursor); + + if (source == NULL || mount == NULL || type == NULL) { continue; } - if (statvfs(mnt->mnt_dir, &st) != 0 || st.f_blocks == 0) { + (void) source; + + ngx_http_monitoring_copy_mount_field(mount_path, sizeof(mount_path), + mount); + ngx_http_monitoring_copy_mount_field(fs_type, sizeof(fs_type), type); + + if (mount_path[0] == '\0' || fs_type[0] == '\0') { + continue; + } + + if (ngx_http_monitoring_skip_fs((char *) fs_type)) { + continue; + } + + if (statvfs((char *) mount_path, &st) != 0 || st.f_blocks == 0) { continue; } @@ -626,9 +648,9 @@ ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh) avail = (uint64_t) st.f_bavail * st.f_frsize; used = total - free_bytes; - ngx_cpystrn(sh->filesystems[count].path, (u_char *) mnt->mnt_dir, + ngx_cpystrn(sh->filesystems[count].path, mount_path, NGX_HTTP_MONITORING_KEY_LEN); - ngx_cpystrn(sh->filesystems[count].type, (u_char *) mnt->mnt_type, + ngx_cpystrn(sh->filesystems[count].type, fs_type, NGX_HTTP_MONITORING_NAME_LEN); sh->filesystems[count].total = (ngx_atomic_uint_t) total; sh->filesystems[count].used = (ngx_atomic_uint_t) used; @@ -640,7 +662,7 @@ ngx_http_monitoring_collect_filesystems(ngx_http_monitoring_shctx_t *sh) count++; } - endmntent(fp); + fclose(fp); sh->fs_count = count; } @@ -822,6 +844,72 @@ ngx_http_monitoring_delta(ngx_atomic_uint_t now, ngx_atomic_uint_t prev) } +static char * +ngx_http_monitoring_next_mount_field(char **cursor) +{ + char *p, *start; + + p = *cursor; + + while (*p == ' ' || *p == '\t') { + p++; + } + + if (*p == '\0' || *p == '\n') { + *cursor = p; + return NULL; + } + + start = p; + + while (*p != '\0' && *p != '\n' && *p != ' ' && *p != '\t') { + p++; + } + + if (*p != '\0') { + *p++ = '\0'; + } + + *cursor = p; + + return start; +} + + +static void +ngx_http_monitoring_copy_mount_field(u_char *dst, size_t size, char *src) +{ + u_char *p, *last; + ngx_uint_t value; + + if (size == 0) { + return; + } + + p = dst; + last = dst + size - 1; + + while (*src != '\0' && p < last) { + if (src[0] == '\\' + && src[1] >= '0' && src[1] <= '7' + && src[2] >= '0' && src[2] <= '7' + && src[3] >= '0' && src[3] <= '7') + { + value = (ngx_uint_t) ((src[1] - '0') * 64 + + (src[2] - '0') * 8 + + (src[3] - '0')); + *p++ = (u_char) value; + src += 4; + continue; + } + + *p++ = (u_char) *src++; + } + + *p = '\0'; +} + + static ngx_uint_t ngx_http_monitoring_skip_fs(const char *type) {