[php-src] Issue #21901: `php_getopt()` does not clear `*optarg` for long options with optional argument when value is absent — `--ini` regression in 8.5
[email protected] (mellonella)
| Newsgroups | php.bugs |
|---|---|
| Message-ID | <[email protected]> |
Issue: https://github.com/php/php-src/issues/21901
Author: mellonella
### Description
In PHP 8.5 the `--ini` CLI option was changed from `no_argument` to `optional_argument` to support the new `--ini=diff` subcommand. As a side effect, `--ini` (without a value) now fails with `Unknown argument for --ini` whenever it is preceded on the command line by another option that takes a required argument.
**Reproducer (PHP 8.5):**
```
$ php --ini # OK
$ php -n --ini # OK (-n takes no argument)
$ php -c /etc/php.ini --ini # FAIL: Unknown argument for --ini
$ php -d foo=bar --ini # FAIL: Unknown argument for --ini
$ php --ini -c /etc/php.ini # OK (workaround: reorder)
```
The same commands work correctly in PHP 8.4 and earlier.
**Expected behavior:**
`--ini` without a value should be treated identically regardless of its position on the command line, equivalent to `--ini=`.
**Root cause:**
In `main/getopt.c`, `php_getopt()` does not reset `*optarg` between calls. When a long option is declared with `need_param == 2` (optional) and is supplied without `=value`, the function falls through without assigning `*optarg`, leaving it pointing at the value consumed by the previous option (e.g. the path passed to `-c`, or the assignment passed to `-d`). The CLI SAPI then interprets that stale value as the `--ini` subcommand, fails to match it against `""` or `"diff"`, and emits the error.
The bug existed before 8.5 but was never observable because no long option used `optional_argument`. The introduction of `--ini=diff` (and the corresponding flip of `--ini` to `optional_argument`) exposed it.
**Affected versions:** 8.5.0 and later (any build with `--ini=diff` support).
---
# Proposed Patch
The simplest and most defensive fix is to clear `*optarg` at the top of `php_getopt()` so that callers never observe a stale pointer from a previous invocation, regardless of which code path is taken later.
```diff
--- main/getopt.c.orig 2026-03-10 19:15:23.000000000 -0400
+++ main/getopt.c 2026-04-24 06:11:15.038033405 -0400
@@ -59,6 +59,7 @@
static char **prev_optarg = NULL;
php_optidx = -1;
+ *optarg = NULL;
if(prev_optarg && prev_optarg != optarg) {
/* reset the state */
```
### PHP Version
```plain
PHP 8.5.4 (cli) (built: Apr 24 2026 07:24:54) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.5.4, Copyright (c) Zend Technologies
with Zend OPcache v8.5.4, Copyright (c), by Zend Technologies
```
### Operating System
Debian 12.13