diff --git a/static/ngx_http_zstd_static_module.c b/static/ngx_http_zstd_static_module.c index 9035521..9f553bd 100644 --- a/static/ngx_http_zstd_static_module.c +++ b/static/ngx_http_zstd_static_module.c @@ -250,7 +250,17 @@ ngx_http_zstd_static_handler(ngx_http_request_t *r) * fall back to a read+lseek pair that would mutate the shared fd * offset. Every modern POSIX target has it; this guard is * essentially a build-time tripwire. + * + * Skipped when the file was opened with O_DIRECT (of.is_directio, set by + * ngx_open_cached_file when "directio " is configured and the file + * meets the threshold). An O_DIRECT read requires the buffer, offset and + * length all aligned to the device block size; this deliberately tiny, + * unaligned 4-byte pread would fail with EINVAL on every request, wrongly + * declining the .zst and spamming NGX_LOG_CRIT. The probe is only a + * best-effort corruption guard, so forgoing it under directio is the + * correct trade — the file is still served, just without the sanity check. */ + if (!of.is_directio) { if (of.size < 4) { ngx_log_error(NGX_LOG_ERR, log, 0, "zstd static: \"%s\" too small to be a zstd frame " @@ -289,6 +299,11 @@ ngx_http_zstd_static_handler(ngx_http_request_t *r) return NGX_DECLINED; } } + } else { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, + "zstd static: skipping magic probe on directio " + "file \"%s\" (O_DIRECT alignment)", path.data); + } #endif /* NGX_HAVE_PREAD */ #endif diff --git a/t/01-static.t b/t/01-static.t index d4de94c..ba6bafa 100644 --- a/t/01-static.t +++ b/t/01-static.t @@ -570,3 +570,33 @@ Accept-Encoding: gzip Vary: Accept-Encoding --- no_error_log [error] + + + +=== TEST 26: zstd_static serves a .zst under directio without a failed pread +# Regression for the O_DIRECT magic-probe bug. With "directio" active the +# open_file_cache opens the .zst with O_DIRECT; the 4-byte, unaligned +# magic pread() then fails EINVAL, wrongly declining every .zst above the +# threshold and spamming NGX_LOG_CRIT. The fix skips the probe when +# of.is_directio. Assert the precompressed frame is still served (200 + +# Content-Encoding: zstd) and no CRIT/[error] appears. +# +# NOTE: O_DIRECT support is filesystem-dependent (tmpfs may not honour it), +# so this deterministically catches the "declined + CRIT" regression only +# where O_DIRECT actually engages; everywhere else it still asserts correct +# serving. directio 1 = every file >= 1 byte is eligible (test.zst is 3717B). +--- config + location /test { + zstd_static on; + directio 1; + root ../../t/suite; + } +--- request +GET /test +--- more_headers +Accept-Encoding: zstd +--- response_headers +Content-Encoding: zstd +--- error_code: 200 +--- no_error_log +[error]