public inbox for dash@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] options: don't error when unsetting OPTIND
@ 2022-12-14  2:31 наб
  2022-12-14 14:18 ` Michael Greenberg
  2023-01-03  9:39 ` [PATCH] var: Do not add 1 to return value of strchrnul Herbert Xu
  0 siblings, 2 replies; 18+ messages in thread
From: наб @ 2022-12-14  2:31 UTC (permalink / raw)
  To: dash

[-- Attachment #1: Type: text/plain, Size: 3688 bytes --]

unset OPTIND ends up calling getoptsreset("") which errors out with
  sh: 1: unset: Illegal number:
or even
  sh: 1: unset: Illegal number: leweli/bin:/usr/l��

Pass the current flags to struct var->func, set the getopts optind to 1
and continue with allowing the unset.

We still forbid OPTIND=, OPTIND=-1, OPTIND=abc, &c.

Fixes: https://bugs.debian.org/985478
---
 src/exec.c    | 2 +-
 src/exec.h    | 2 +-
 src/mail.c    | 2 +-
 src/mail.h    | 2 +-
 src/options.c | 5 ++---
 src/options.h | 2 +-
 src/var.c     | 4 ++--
 src/var.h     | 2 +-
 8 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/src/exec.c b/src/exec.c
index 87354d4..68fa8ab 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -565,7 +565,7 @@ hashcd(void)
  */
 
 void
-changepath(const char *newval)
+changepath(const char *newval, int unused)
 {
 	const char *new;
 	int idx;
diff --git a/src/exec.h b/src/exec.h
index 423b07e..0f74be4 100644
--- a/src/exec.h
+++ b/src/exec.h
@@ -69,7 +69,7 @@ int hashcmd(int, char **);
 void find_command(char *, struct cmdentry *, int, const char *);
 struct builtincmd *find_builtin(const char *);
 void hashcd(void);
-void changepath(const char *);
+void changepath(const char *, int);
 #ifdef notdef
 void getcmdentry(char *, struct cmdentry *);
 #endif
diff --git a/src/mail.c b/src/mail.c
index 8eacb2d..e81d2b4 100644
--- a/src/mail.c
+++ b/src/mail.c
@@ -109,7 +109,7 @@ chkmail(void)
 
 
 void
-changemail(const char *val)
+changemail(const char *val, int unused)
 {
 	changed++;
 }
diff --git a/src/mail.h b/src/mail.h
index 3c6b21d..70b54a4 100644
--- a/src/mail.h
+++ b/src/mail.h
@@ -35,4 +35,4 @@
  */
 
 void chkmail(void);
-void changemail(const char *);
+void changemail(const char *, int);
diff --git a/src/options.c b/src/options.c
index a46c23b..81f2c4b 100644
--- a/src/options.c
+++ b/src/options.c
@@ -390,10 +390,9 @@ setcmd(int argc, char **argv)
 
 
 void
-getoptsreset(value)
-	const char *value;
+getoptsreset(const char *value, int flags)
 {
-	shellparam.optind = number(value) ?: 1;
+	shellparam.optind = (flags & VUNSET) ? 1 : number(value) ?: 1;
 	shellparam.optoff = -1;
 }
 
diff --git a/src/options.h b/src/options.h
index 975fe33..10bcb88 100644
--- a/src/options.h
+++ b/src/options.h
@@ -83,4 +83,4 @@ int shiftcmd(int, char **);
 int setcmd(int, char **);
 int getoptscmd(int, char **);
 int nextopt(const char *);
-void getoptsreset(const char *);
+void getoptsreset(const char *, int);
diff --git a/src/var.c b/src/var.c
index ef9c2bd..a7d4a92 100644
--- a/src/var.c
+++ b/src/var.c
@@ -266,7 +266,7 @@ struct var *setvareq(char *s, int flags)
 			goto out;
 
 		if (vp->func && (flags & VNOFUNC) == 0)
-			(*vp->func)(strchrnul(s, '=') + 1);
+			(*vp->func)(strchrnul(s, '=') + 1, flags);
 
 		if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 			ckfree(vp->text);
@@ -531,7 +531,7 @@ poplocalvars(void)
 			unsetvar(vp->text);
 		} else {
 			if (vp->func)
-				(*vp->func)(strchrnul(lvp->text, '=') + 1);
+				(*vp->func)(strchrnul(lvp->text, '=') + 1, lvp->flags);
 			if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 				ckfree(vp->text);
 			vp->flags = lvp->flags;
diff --git a/src/var.h b/src/var.h
index aa7575a..4329e22 100644
--- a/src/var.h
+++ b/src/var.h
@@ -56,7 +56,7 @@ struct var {
 	struct var *next;		/* next entry in hash list */
 	int flags;			/* flags are defined above */
 	const char *text;		/* name=value */
-	void (*func)(const char *);
+	void (*func)(const char *, int flags);
 					/* function to be called when  */
 					/* the variable gets set/unset */
 };
-- 
2.30.2

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] options: don't error when unsetting OPTIND
  2022-12-14  2:31 [PATCH] options: don't error when unsetting OPTIND наб
@ 2022-12-14 14:18 ` Michael Greenberg
  2022-12-14 17:49   ` Steffen Nurpmeso
  2023-01-03  9:39 ` [PATCH] var: Do not add 1 to return value of strchrnul Herbert Xu
  1 sibling, 1 reply; 18+ messages in thread
From: Michael Greenberg @ 2022-12-14 14:18 UTC (permalink / raw)
  To: наб, dash

While we're thinking about it, is this the behavior we want? Or should
`readonly` produce an error?

```
$ getopts abc ARG -ab -c foo
$ echo $OPTIND
2
$ echo $ARG
a
$ readonly OPTIND
$ getopts abc ARG -ab -c foo
/Users/mgree/smoosh/libdash/src/dash: 5: getopts: OPTIND: is read only
$ echo $ARG
a
$ echo $OPTIND
2
```

There are similar questions for PWD and other shell-set nameable
variables:

```
$ readonly PWD
$ pwd
/Users/mgree/pash-medium/budgeting
$ cd ..
/Users/mgree/smoosh/libdash/src/dash: 13: cd: PWD: is read only
$ pwd
/Users/mgree/pash-medium
```

But special parameters (like @ and ?) are outright rejected by
`readonly`.

Bafflingly, the POSIX spec gives the example `readonly HOME PWD`, but
with no explanation about why one might want to do that. (I get making
`HOME` readonly, but `PWD`? Is that supposed to stop `cd` from working?)

Cheers,
Michael

On 2022-12-14 at 03:31:30 AM, наб wrote:

> unset OPTIND ends up calling getoptsreset("") which errors out with
>   sh: 1: unset: Illegal number:
> or even
>   sh: 1: unset: Illegal number: leweli/bin:/usr/l��
>
> Pass the current flags to struct var->func, set the getopts optind to 1
> and continue with allowing the unset.
>
> We still forbid OPTIND=, OPTIND=-1, OPTIND=abc, &c.
>
> Fixes: https://bugs.debian.org/985478
> ---
>  src/exec.c    | 2 +-
>  src/exec.h    | 2 +-
>  src/mail.c    | 2 +-
>  src/mail.h    | 2 +-
>  src/options.c | 5 ++---
>  src/options.h | 2 +-
>  src/var.c     | 4 ++--
>  src/var.h     | 2 +-
>  8 files changed, 10 insertions(+), 11 deletions(-)
>
> diff --git a/src/exec.c b/src/exec.c
> index 87354d4..68fa8ab 100644
> --- a/src/exec.c
> +++ b/src/exec.c
> @@ -565,7 +565,7 @@ hashcd(void)
>   */
>  
>  void
> -changepath(const char *newval)
> +changepath(const char *newval, int unused)
>  {
>  	const char *new;
>  	int idx;
> diff --git a/src/exec.h b/src/exec.h
> index 423b07e..0f74be4 100644
> --- a/src/exec.h
> +++ b/src/exec.h
> @@ -69,7 +69,7 @@ int hashcmd(int, char **);
>  void find_command(char *, struct cmdentry *, int, const char *);
>  struct builtincmd *find_builtin(const char *);
>  void hashcd(void);
> -void changepath(const char *);
> +void changepath(const char *, int);
>  #ifdef notdef
>  void getcmdentry(char *, struct cmdentry *);
>  #endif
> diff --git a/src/mail.c b/src/mail.c
> index 8eacb2d..e81d2b4 100644
> --- a/src/mail.c
> +++ b/src/mail.c
> @@ -109,7 +109,7 @@ chkmail(void)
>  
>  
>  void
> -changemail(const char *val)
> +changemail(const char *val, int unused)
>  {
>  	changed++;
>  }
> diff --git a/src/mail.h b/src/mail.h
> index 3c6b21d..70b54a4 100644
> --- a/src/mail.h
> +++ b/src/mail.h
> @@ -35,4 +35,4 @@
>   */
>  
>  void chkmail(void);
> -void changemail(const char *);
> +void changemail(const char *, int);
> diff --git a/src/options.c b/src/options.c
> index a46c23b..81f2c4b 100644
> --- a/src/options.c
> +++ b/src/options.c
> @@ -390,10 +390,9 @@ setcmd(int argc, char **argv)
>  
>  
>  void
> -getoptsreset(value)
> -	const char *value;
> +getoptsreset(const char *value, int flags)
>  {
> -	shellparam.optind = number(value) ?: 1;
> +	shellparam.optind = (flags & VUNSET) ? 1 : number(value) ?: 1;
>  	shellparam.optoff = -1;
>  }
>  
> diff --git a/src/options.h b/src/options.h
> index 975fe33..10bcb88 100644
> --- a/src/options.h
> +++ b/src/options.h
> @@ -83,4 +83,4 @@ int shiftcmd(int, char **);
>  int setcmd(int, char **);
>  int getoptscmd(int, char **);
>  int nextopt(const char *);
> -void getoptsreset(const char *);
> +void getoptsreset(const char *, int);
> diff --git a/src/var.c b/src/var.c
> index ef9c2bd..a7d4a92 100644
> --- a/src/var.c
> +++ b/src/var.c
> @@ -266,7 +266,7 @@ struct var *setvareq(char *s, int flags)
>  			goto out;
>  
>  		if (vp->func && (flags & VNOFUNC) == 0)
> -			(*vp->func)(strchrnul(s, '=') + 1);
> +			(*vp->func)(strchrnul(s, '=') + 1, flags);
>  
>  		if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
>  			ckfree(vp->text);
> @@ -531,7 +531,7 @@ poplocalvars(void)
>  			unsetvar(vp->text);
>  		} else {
>  			if (vp->func)
> -				(*vp->func)(strchrnul(lvp->text, '=') + 1);
> +				(*vp->func)(strchrnul(lvp->text, '=') + 1, lvp->flags);
>  			if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
>  				ckfree(vp->text);
>  			vp->flags = lvp->flags;
> diff --git a/src/var.h b/src/var.h
> index aa7575a..4329e22 100644
> --- a/src/var.h
> +++ b/src/var.h
> @@ -56,7 +56,7 @@ struct var {
>  	struct var *next;		/* next entry in hash list */
>  	int flags;			/* flags are defined above */
>  	const char *text;		/* name=value */
> -	void (*func)(const char *);
> +	void (*func)(const char *, int flags);
>  					/* function to be called when  */
>  					/* the variable gets set/unset */
>  };
> -- 
> 2.30.2

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] options: don't error when unsetting OPTIND
  2022-12-14 14:18 ` Michael Greenberg
@ 2022-12-14 17:49   ` Steffen Nurpmeso
  2022-12-14 22:36     ` Michael Greenberg
  0 siblings, 1 reply; 18+ messages in thread
From: Steffen Nurpmeso @ 2022-12-14 17:49 UTC (permalink / raw)
  To: Michael Greenberg; +Cc: наб, dash

Michael Greenberg wrote in
 <m2edt2nk5q.fsf@greenberg.science>:
 ...
 |Bafflingly, the POSIX spec gives the example `readonly HOME PWD`, but
 |with no explanation about why one might want to do that. (I get making
 |`HOME` readonly, but `PWD`? Is that supposed to stop `cd` from working?)

PWD is a beast with lots of "behaviors of the cd and pwd utilities
are unspecified" in case of user edits, see "2.5.3 Shell Variables".

--steffen
|
|Der Kragenbaer,                The moon bear,
|der holt sich munter           he cheerfully and one by one
|einen nach dem anderen runter  wa.ks himself off
|(By Robert Gernhardt)

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] options: don't error when unsetting OPTIND
  2022-12-14 17:49   ` Steffen Nurpmeso
@ 2022-12-14 22:36     ` Michael Greenberg
  2022-12-14 23:07       ` наб
  0 siblings, 1 reply; 18+ messages in thread
From: Michael Greenberg @ 2022-12-14 22:36 UTC (permalink / raw)
  To: Steffen Nurpmeso; +Cc: наб, dash

On 2022-12-14 at 06:49:46 PM, Steffen Nurpmeso wrote:

> Michael Greenberg wrote in
>  <m2edt2nk5q.fsf@greenberg.science>:
>  ...
>  |Bafflingly, the POSIX spec gives the example `readonly HOME PWD`, but
>  |with no explanation about why one might want to do that. (I get making
>  |`HOME` readonly, but `PWD`? Is that supposed to stop `cd` from working?)
>
> PWD is a beast with lots of "behaviors of the cd and pwd utilities
> are unspecified" in case of user edits, see "2.5.3 Shell Variables".

Is running `readonly PWD` the same as setting `PWD`?

The language in the documentation for `cd` and in 2.5.3 only talks about setting or
unsetting `PWD`, but not the export/readonly bits.

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] options: don't error when unsetting OPTIND
  2022-12-14 22:36     ` Michael Greenberg
@ 2022-12-14 23:07       ` наб
  2022-12-15 18:39         ` Michael Greenberg
  0 siblings, 1 reply; 18+ messages in thread
From: наб @ 2022-12-14 23:07 UTC (permalink / raw)
  To: Michael Greenberg; +Cc: Steffen Nurpmeso, dash

[-- Attachment #1: Type: text/plain, Size: 2728 bytes --]

Hi!

On Wed, Dec 14, 2022 at 05:36:22PM -0500, Michael Greenberg wrote:
> On 2022-12-14 at 06:49:46 PM, Steffen Nurpmeso wrote:
> 
> > Michael Greenberg wrote in
> >  <m2edt2nk5q.fsf@greenberg.science>:
> >  ...
> >  |Bafflingly, the POSIX spec gives the example `readonly HOME PWD`, but
> >  |with no explanation about why one might want to do that. (I get making
> >  |`HOME` readonly, but `PWD`? Is that supposed to stop `cd` from working?)
> >
> > PWD is a beast with lots of "behaviors of the cd and pwd utilities
> > are unspecified" in case of user edits, see "2.5.3 Shell Variables".
> 
> Is running `readonly PWD` the same as setting `PWD`?
> 
> The language in the documentation for `cd` and in 2.5.3 only talks about setting or
> unsetting `PWD`, but not the export/readonly bits.

Isssue 8 Draft 2.1, XBD, 8.1 Environment Variable Definition para. 11
(p. 156, ll. 5381-5390) says:
  Additionally, a subset of the above variables are manipulated by shell built-in utilities outside of
  shell assignments. If an attempt is made to mark any of the following variables as readonly, then
  either the readonly utility shall reject the attempt, or readonly shall succeed but the shell can still
  modify the variables outside of assignment context, or readonly shall succeed but use of a shell
  built-in that would otherwise modify such a variable shall fail.
    LINENO
    OLDPWD
    OPTARG
    OPTIND
    PWD

I.e., readonly PWD; cd /; PWD=dupa must:
  * fail in readonly, or
  * fail in the cd, or
  * fail in the assignment
at implementer's choice. Which is just a long-winded way of saying "just
do whatever but only accept readonly if it does anything".

Dash appears to do option 2:
  $ ./dash -c 'readonly PWD; cd /'
  ./dash: 1: cd: PWD: is read only
  $ ./dash -c 'readonly OPTIND; getopts a a -a'
  ./dash: 1: getopts: OPTIND: is read only
which is fine.
OLDPWD behaves like PWD and OPTARG like OPTIND.
LINENO behaves like option 3 for obvious reasons.

(For completeness: in ibid., para. 12 (ll. 5391-5392)
 there's a provision for special treatment for any non-standard
 variable we want for our built-ins:
   Implementations may provide an implementation-defined set of additional variables which are
   manipulated by implementation-specific built-in utilities not defined in this standard. The
   readonly utility shall not reject marking these additional variables as readonly, but when marked
   readonly, those extension utilities shall either continue to modify the variables, or shall fail
   because the variable is readonly. None of the variables defined by this standard shall be in this
   implementation-defined set.)

Best,
наб

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] options: don't error when unsetting OPTIND
  2022-12-14 23:07       ` наб
@ 2022-12-15 18:39         ` Michael Greenberg
  0 siblings, 0 replies; 18+ messages in thread
From: Michael Greenberg @ 2022-12-15 18:39 UTC (permalink / raw)
  To: наб; +Cc: Steffen Nurpmeso, dash

On 2022-12-15 at 12:07:38 AM, наб wrote:

> Hi!
>
> On Wed, Dec 14, 2022 at 05:36:22PM -0500, Michael Greenberg wrote:
>> On 2022-12-14 at 06:49:46 PM, Steffen Nurpmeso wrote:
>> 
>> > Michael Greenberg wrote in
>> >  <m2edt2nk5q.fsf@greenberg.science>:
>> >  ...
>> >  |Bafflingly, the POSIX spec gives the example `readonly HOME PWD`, but
>> >  |with no explanation about why one might want to do that. (I get making
>> >  |`HOME` readonly, but `PWD`? Is that supposed to stop `cd` from working?)
>> >
>> > PWD is a beast with lots of "behaviors of the cd and pwd utilities
>> > are unspecified" in case of user edits, see "2.5.3 Shell Variables".
>> 
>> Is running `readonly PWD` the same as setting `PWD`?
>> 
>> The language in the documentation for `cd` and in 2.5.3 only talks about setting or
>> unsetting `PWD`, but not the export/readonly bits.
>
> Isssue 8 Draft 2.1, XBD, 8.1 Environment Variable Definition para. 11
> (p. 156, ll. 5381-5390) says:

Thank you for finding this! I didn't think to check the current draft.

Cheers,
Michael

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH] var: Do not add 1 to return value of strchrnul
  2022-12-14  2:31 [PATCH] options: don't error when unsetting OPTIND наб
  2022-12-14 14:18 ` Michael Greenberg
@ 2023-01-03  9:39 ` Herbert Xu
  2023-01-03  9:51   ` [v2 PATCH] " Herbert Xu
  2023-01-03 12:26   ` [PATCH] " наб
  1 sibling, 2 replies; 18+ messages in thread
From: Herbert Xu @ 2023-01-03  9:39 UTC (permalink / raw)
  To: наб; +Cc: dash

наб <nabijaczleweli@nabijaczleweli.xyz> wrote:
>
> --- a/src/var.c
> +++ b/src/var.c
> @@ -266,7 +266,7 @@ struct var *setvareq(char *s, int flags)
>                        goto out;
> 
>                if (vp->func && (flags & VNOFUNC) == 0)
> -                       (*vp->func)(strchrnul(s, '=') + 1);
> +                       (*vp->func)(strchrnul(s, '=') + 1, flags);

Yes this was definitely broken.  strchrnul returns a pointer to the
final NUL character so adding one to it is just wrong.

However, I don't think we need to pass the flags to the action
function since none of them care about whether it's unset.  We
just need to pass a pointer to an empty string rather than some
bogus pointer.

---8<---
When a variable like OPTIND is unset dash may call the action
function with a bogus pointer because it tries to add one to
the return value of strchrnul unconditionally.

Use strchr and nullstr instead.

Link: https://bugs.debian.org/985478
Reported-by: наб <nabijaczleweli@nabijaczleweli.xyz>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

diff --git a/src/var.c b/src/var.c
index ef9c2bd..f42bfd7 100644
--- a/src/var.c
+++ b/src/var.c
@@ -266,7 +266,7 @@ struct var *setvareq(char *s, int flags)
 			goto out;
 
 		if (vp->func && (flags & VNOFUNC) == 0)
-			(*vp->func)(strchrnul(s, '=') + 1);
+			(*vp->func)((strchr(s, '=') ?: nullstr - 1) + 1);
 
 		if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 			ckfree(vp->text);
@@ -531,7 +531,8 @@ poplocalvars(void)
 			unsetvar(vp->text);
 		} else {
 			if (vp->func)
-				(*vp->func)(strchrnul(lvp->text, '=') + 1);
+				(*vp->func)((strchr(lvp->text, '=') ?:
+					     nullstr - 1) + 1);
 			if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 				ckfree(vp->text);
 			vp->flags = lvp->flags;
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [v2 PATCH] var: Do not add 1 to return value of strchrnul
  2023-01-03  9:39 ` [PATCH] var: Do not add 1 to return value of strchrnul Herbert Xu
@ 2023-01-03  9:51   ` Herbert Xu
  2023-01-03 12:26   ` [PATCH] " наб
  1 sibling, 0 replies; 18+ messages in thread
From: Herbert Xu @ 2023-01-03  9:51 UTC (permalink / raw)
  To: наб; +Cc: dash

When a variable like OPTIND is unset dash may call the action
function with a bogus pointer because it tries to add one to
the return value of strchrnul unconditionally.

Use strchr and nullstr instead.

Link: https://bugs.debian.org/985478
Reported-by: наб <nabijaczleweli@nabijaczleweli.xyz>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

diff --git a/src/var.c b/src/var.c
index ef9c2bd..b70d72c 100644
--- a/src/var.c
+++ b/src/var.c
@@ -154,6 +154,10 @@ RESET {
 }
 #endif
 
+static char *varnull(const char *s)
+{
+	return (strchr(s, '=') ?: nullstr - 1) + 1;
+}
 
 /*
  * This routine initializes the builtin variables.  It is called when the
@@ -266,7 +270,7 @@ struct var *setvareq(char *s, int flags)
 			goto out;
 
 		if (vp->func && (flags & VNOFUNC) == 0)
-			(*vp->func)(strchrnul(s, '=') + 1);
+			(*vp->func)(varnull(s));
 
 		if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 			ckfree(vp->text);
@@ -531,7 +535,7 @@ poplocalvars(void)
 			unsetvar(vp->text);
 		} else {
 			if (vp->func)
-				(*vp->func)(strchrnul(lvp->text, '=') + 1);
+				(*vp->func)(varnull(lvp->text));
 			if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 				ckfree(vp->text);
 			vp->flags = lvp->flags;
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] var: Do not add 1 to return value of strchrnul
  2023-01-03  9:39 ` [PATCH] var: Do not add 1 to return value of strchrnul Herbert Xu
  2023-01-03  9:51   ` [v2 PATCH] " Herbert Xu
@ 2023-01-03 12:26   ` наб
  2023-01-04  9:59     ` Herbert Xu
  1 sibling, 1 reply; 18+ messages in thread
From: наб @ 2023-01-03 12:26 UTC (permalink / raw)
  To: Herbert Xu; +Cc: dash

[-- Attachment #1: Type: text/plain, Size: 5271 bytes --]

Hi!

On Tue, Jan 03, 2023 at 05:39:41PM +0800, Herbert Xu wrote:
> наб <nabijaczleweli@nabijaczleweli.xyz> wrote:
> > --- a/src/var.c
> > +++ b/src/var.c
> > @@ -266,7 +266,7 @@ struct var *setvareq(char *s, int flags)
> >                        goto out;
> > 
> >                if (vp->func && (flags & VNOFUNC) == 0)
> > -                       (*vp->func)(strchrnul(s, '=') + 1);
> > +                       (*vp->func)(strchrnul(s, '=') + 1, flags);
> Yes this was definitely broken.  strchrnul returns a pointer to the
> final NUL character so adding one to it is just wrong.
That originally gave me pause but I assumed this is an intentional
muddling for a known memory lay-out or w/e,
but that explains the corruption I saw sometimes, yeah.

> However, I don't think we need to pass the flags to the action
> function since none of them care about whether it's unset.  We
> just need to pass a pointer to an empty string rather than some
> bogus pointer.
This, unsurprisingly, produces the same error, because getoptsreset()
needs a valid number for OPTIND:
  $ src/dash -c 'unset OPTIND'
  src/dash: 1: unset: Illegal number:
and the empty string isn't a valid number
(in the simple case of src/dash -c 'unset OPTIND' &a. the original
 broken approach also produced an empty string on accident
 in most cases for me).

We do need to at least to /some/how signal to getoptsreset() that we're
unsetting while preserving the requirement that OPTIND is an integer,
and passing a flag is the only sensible way, I think.

Scissor-patch rebased on top of your v2 below.

Best,
-- >8 --
Subject: [PATCH] options: don't error when unsetting OPTIND

unset OPTIND ends up calling getoptsreset("") which errors out with
  sh: 1: unset: Illegal number:

Pass the current flags to struct var->func, set the getopts optind to 1
and continue with allowing the unset.

We still forbid OPTIND=, OPTIND=-1, OPTIND=abc, &c.

Fixes: https://bugs.debian.org/985478
---
 src/exec.c    | 2 +-
 src/exec.h    | 2 +-
 src/mail.c    | 2 +-
 src/mail.h    | 2 +-
 src/options.c | 5 ++---
 src/options.h | 2 +-
 src/var.c     | 4 ++--
 src/var.h     | 2 +-
 8 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/src/exec.c b/src/exec.c
index 87354d4..68fa8ab 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -565,7 +565,7 @@ hashcd(void)
  */
 
 void
-changepath(const char *newval)
+changepath(const char *newval, int unused)
 {
 	const char *new;
 	int idx;
diff --git a/src/exec.h b/src/exec.h
index 423b07e..0f74be4 100644
--- a/src/exec.h
+++ b/src/exec.h
@@ -69,7 +69,7 @@ int hashcmd(int, char **);
 void find_command(char *, struct cmdentry *, int, const char *);
 struct builtincmd *find_builtin(const char *);
 void hashcd(void);
-void changepath(const char *);
+void changepath(const char *, int);
 #ifdef notdef
 void getcmdentry(char *, struct cmdentry *);
 #endif
diff --git a/src/mail.c b/src/mail.c
index 8eacb2d..e81d2b4 100644
--- a/src/mail.c
+++ b/src/mail.c
@@ -109,7 +109,7 @@ chkmail(void)
 
 
 void
-changemail(const char *val)
+changemail(const char *val, int unused)
 {
 	changed++;
 }
diff --git a/src/mail.h b/src/mail.h
index 3c6b21d..70b54a4 100644
--- a/src/mail.h
+++ b/src/mail.h
@@ -35,4 +35,4 @@
  */
 
 void chkmail(void);
-void changemail(const char *);
+void changemail(const char *, int);
diff --git a/src/options.c b/src/options.c
index a46c23b..81f2c4b 100644
--- a/src/options.c
+++ b/src/options.c
@@ -390,10 +390,9 @@ setcmd(int argc, char **argv)
 
 
 void
-getoptsreset(value)
-	const char *value;
+getoptsreset(const char *value, int flags)
 {
-	shellparam.optind = number(value) ?: 1;
+	shellparam.optind = (flags & VUNSET) ? 1 : number(value) ?: 1;
 	shellparam.optoff = -1;
 }
 
diff --git a/src/options.h b/src/options.h
index 975fe33..10bcb88 100644
--- a/src/options.h
+++ b/src/options.h
@@ -83,4 +83,4 @@ int shiftcmd(int, char **);
 int setcmd(int, char **);
 int getoptscmd(int, char **);
 int nextopt(const char *);
-void getoptsreset(const char *);
+void getoptsreset(const char *, int);
diff --git a/src/var.c b/src/var.c
index b70d72c..13a946b 100644
--- a/src/var.c
+++ b/src/var.c
@@ -270,7 +270,7 @@ struct var *setvareq(char *s, int flags)
 			goto out;
 
 		if (vp->func && (flags & VNOFUNC) == 0)
-			(*vp->func)(varnull(s));
+			(*vp->func)(varnull(s), flags);
 
 		if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 			ckfree(vp->text);
@@ -535,7 +535,7 @@ poplocalvars(void)
 			unsetvar(vp->text);
 		} else {
 			if (vp->func)
-				(*vp->func)(varnull(lvp->text));
+				(*vp->func)(varnull(lvp->text), lvp->flags);
 			if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
 				ckfree(vp->text);
 			vp->flags = lvp->flags;
diff --git a/src/var.h b/src/var.h
index aa7575a..4329e22 100644
--- a/src/var.h
+++ b/src/var.h
@@ -56,7 +56,7 @@ struct var {
 	struct var *next;		/* next entry in hash list */
 	int flags;			/* flags are defined above */
 	const char *text;		/* name=value */
-	void (*func)(const char *);
+	void (*func)(const char *, int flags);
 					/* function to be called when  */
 					/* the variable gets set/unset */
 };
-- 
2.30.2

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] var: Do not add 1 to return value of strchrnul
  2023-01-03 12:26   ` [PATCH] " наб
@ 2023-01-04  9:59     ` Herbert Xu
  2023-01-04 12:54       ` наб
  0 siblings, 1 reply; 18+ messages in thread
From: Herbert Xu @ 2023-01-04  9:59 UTC (permalink / raw)
  To: наб; +Cc: dash

On Tue, Jan 03, 2023 at 01:26:19PM +0100, наб wrote:
>
> > However, I don't think we need to pass the flags to the action
> > function since none of them care about whether it's unset.  We
> > just need to pass a pointer to an empty string rather than some
> > bogus pointer.
> This, unsurprisingly, produces the same error, because getoptsreset()
> needs a valid number for OPTIND:
>   $ src/dash -c 'unset OPTIND'
>   src/dash: 1: unset: Illegal number:

You get the same result with OPTIND="" so I don't see the issue.

Am I missing something with respect to POSIX?

Thanks,
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] var: Do not add 1 to return value of strchrnul
  2023-01-04  9:59     ` Herbert Xu
@ 2023-01-04 12:54       ` наб
  2023-01-04 14:12         ` Herbert Xu
  0 siblings, 1 reply; 18+ messages in thread
From: наб @ 2023-01-04 12:54 UTC (permalink / raw)
  To: Herbert Xu; +Cc: dash

[-- Attachment #1: Type: text/plain, Size: 1970 bytes --]

Hi!

On Wed, Jan 04, 2023 at 05:59:47PM +0800, Herbert Xu wrote:
> On Tue, Jan 03, 2023 at 01:26:19PM +0100, наб wrote:
> > > However, I don't think we need to pass the flags to the action
> > > function since none of them care about whether it's unset.  We
> > > just need to pass a pointer to an empty string rather than some
> > > bogus pointer.
> > This, unsurprisingly, produces the same error, because getoptsreset()
> > needs a valid number for OPTIND:
> >   $ src/dash -c 'unset OPTIND'
> >   src/dash: 1: unset: Illegal number:
> You get the same result with OPTIND="" so I don't see the issue.
Which is an unrelated operation, and obviously nonsensical.
It's good that it fails.

> Am I missing something with respect to POSIX?
AFAICT, after trawling through the Issue 8 Draft 2.1,
there are no special provisions for unset/OPTIND,
like there are for readonly.
(See the other branch of this thread, but to summarise:
 setting LINENO, OLDPWD, OPTARG, OPTIND, or PWD readonly /must/ fail in:
 (a) readonly xor (b) the builtin that edits it xor (c) on assignment.)
This indicates that "unset OPTIND" must behave as-specified,
i.e. unset OPTIND and remove it from the environment.

In dash, this really only affects OPTIND, since the only other special
variable that parses an int is sethistsize(), which explicitly has
-- >8 --
        if (hist != NULL) {
                if (hs == NULL || *hs == '\0' ||
                   (histsize = atoi(hs)) < 0)
                        histsize = 100;
                history(hist, &he, H_SETSIZE, histsize);
        }
-- >8 --
which considers HISTSIZE= and unset HISTSIZE to be equivalent;
the only other setter function that inspects the new value is for PATH,
and working out to the empty string works for that case.

We could also pass NULL when unsetting, I guess, since sethistsize() is
already equipped for it, if you prefer to not have a flags argument?

Best,
наб

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] var: Do not add 1 to return value of strchrnul
  2023-01-04 12:54       ` наб
@ 2023-01-04 14:12         ` Herbert Xu
  2023-01-04 16:05           ` наб
  0 siblings, 1 reply; 18+ messages in thread
From: Herbert Xu @ 2023-01-04 14:12 UTC (permalink / raw)
  To: наб; +Cc: dash

On Wed, Jan 04, 2023 at 01:54:21PM +0100, наб wrote:
> 
> AFAICT, after trawling through the Issue 8 Draft 2.1,
> there are no special provisions for unset/OPTIND,

Well it says this regarding OPTIND:

: If the application sets OPTIND to the value 1, a new set of parameters
: can be used: either the current positional parameters or new arg
: values. Any other attempt to invoke getopts multiple times in a single
: shell execution environment with parameters (positional parameters
: or arg operands) that are not the same in all invocations, or with an
: OPTIND value modified to be a value other than 1, produces unspecified
: results.

Unsetting OPTIND ought to do the same thing as setting it to "",
which produces unspecified results and I see nothing wrong with
printing an error in this case.

Cheers,
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] var: Do not add 1 to return value of strchrnul
  2023-01-04 14:12         ` Herbert Xu
@ 2023-01-04 16:05           ` наб
  2023-01-04 16:50             ` Harald van Dijk
  0 siblings, 1 reply; 18+ messages in thread
From: наб @ 2023-01-04 16:05 UTC (permalink / raw)
  To: Herbert Xu; +Cc: dash

[-- Attachment #1: Type: text/plain, Size: 7083 bytes --]

Hi!

On Wed, Jan 04, 2023 at 10:12:36PM +0800, Herbert Xu wrote:
> On Wed, Jan 04, 2023 at 01:54:21PM +0100, наб wrote:
> > AFAICT, after trawling through the Issue 8 Draft 2.1,
> > there are no special provisions for unset/OPTIND,
> Well it says this regarding OPTIND:
> 
> : If the application sets OPTIND to the value 1, a new set of parameters
> : can be used: either the current positional parameters or new arg
> : values. Any other attempt to invoke getopts multiple times in a single
> : shell execution environment with parameters (positional parameters
> : or arg operands) that are not the same in all invocations, or with an
> : OPTIND value modified to be a value other than 1, produces unspecified
> : results.
> 
> Unsetting OPTIND ought to do the same thing as setting it to "",
On the basis of? Those are fundamentally unrelated operations,
and when they are to do the same thing, this is explicitly noted.

Additionally, this is a description of /getopts/, and therefore only
applies if you do "OPTIND=dupa; getopts ..." ‒ the getopts call is
unspecified.

/Any/ operation on OPTIND is legal, and the current behaviour is wrong
(but it seems that no-one has actually cared) ‒ POSIX defines this in
terms of getopts inspecting OPTIND when it's run.

"OPTIND=whatever" is legal and must result in OPTIND becoming whatever.
bash, ksh, and zsh all reduce it to OPTIND=0, so it appears that no-one
actually cares about this so erroring out is fine, I guess.
(mksh does the same but folds to 1; posh refuses the assignment),

"unset OPTIND" is legal, and subsequent getopts calls are well-defined
(it's unclear to me what it oughta do /per se/, but it's not "been set",
 so it's unclear what it's well-defined /to/, but since "the index of
 the next argument to be processed in the shell variable OPTIND" and
 that value doesn't exist, erroring out is fine I guess),
and all the above shells agree
(as does mksh; posh ignores the unset).
and this fell out of a debbug where someone used it
(and additionally cites a few more shells),
so people actually care about this behaviour.

In testing the poster's shells,
I found that yash behaves close to correct here:
-- >8 --
nabijaczleweli@tarta dash 2$ OPTIND=dupa
nabijaczleweli@tarta dash 2$ getopts a a a
getopts: $OPTIND has an invalid value
nabijaczleweli@tarta dash 1 2$ OPTIND=300
nabijaczleweli@tarta dash 2$ getopts a a a
nabijaczleweli@tarta dash 1 2$ unset OPTIND
nabijaczleweli@tarta dash 2$ set | grep -i optind
nabijaczleweli@tarta dash 1 2$ getopts a a a
getopts: $OPTIND has an invalid value
-- >8 --

As does busybox ash:
-- >8 --
~/code/dash $ OPTIND=dupa
~/code/dash $ set | grep -i optind
OPTIND='dupa'
~/code/dash $ getopts a a a
~/code/dash $ set | grep -i optind
OPTIND='1'
~/code/dash $ unset OPTIND
~/code/dash $ set | grep -i optind
_='OPTIND'
~/code/dash $ getopts a a a
~/code/dash $ set | grep -i optind
OPTIND='1'
-- >8 --

The minimal change required to achieve compatibility is my patch,
or some equivalent variant of it, like passing NULL instead of flags.
The minimal change required to achieve conformance would be
to get parse OPTIND in getopts like yash.

There's code in the wild that uses "unset OPTIND; getopts ..."
instead of "OPTIND=1; getopts ...":
  https://codesearch.debian.net/search?q=unset+OPTIND&literal=1&perpkg=1
but those are all bash programs, so it's up to you whether they should
receive explicit provisions for this.

I'm including a PoC scissor-patch below; in particular the ternary
should either be turned into an equivalent of ': "1"' if you want to
support "unset OPTIND; getopts ..." or produce a better error than
"./dash: 1: getopts: Illegal number: unset", but you get the idea
(and shellparam should get fully boolified
 and dash.1 wants to have an explicit note that reset is OPTIND=1,
 but I can follow up with that if we're in agreement here).

Best,
-- >8 --
Subject: [PATCH] options: lazy-parse OPTIND in getopts, per POSIX

This allows "unset OPTIND" and "OPTIND=dupa" to execute as-written
(and as required by POSIX), and catches them as an error in getopts,
which exits 2 ("An error occurred.").

Fixes: https://bugs.debian.org/985478
---
 src/options.c | 30 +++++++++++++++++-------------
 src/options.h |  5 ++++-
 src/var.h     |  1 +
 3 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/src/options.c b/src/options.c
index a46c23b..578a13f 100644
--- a/src/options.c
+++ b/src/options.c
@@ -164,7 +164,7 @@ setarg0:
 	shellparam.p = xargv;
 	shellparam.optind = 1;
 	shellparam.optoff = -1;
-	/* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */
+	/* assert(!shellparam.malloc && !shellparam.optreparse && shellparam.nparam == 0); */
 	while (*xargv) {
 		shellparam.nparam++;
 		xargv++;
@@ -390,11 +390,9 @@ setcmd(int argc, char **argv)
 
 
 void
-getoptsreset(value)
-	const char *value;
+getoptsreset(const char *value)
 {
-	shellparam.optind = number(value) ?: 1;
-	shellparam.optoff = -1;
+	shellparam.optreparse = true;
 }
 
 /*
@@ -408,22 +406,28 @@ int
 getoptscmd(int argc, char **argv)
 {
 	char **optbase;
+	unsigned max;
 
 	if (argc < 3)
 		sh_error("Usage: getopts optstring var [arg]");
 	else if (argc == 3) {
 		optbase = shellparam.p;
-		if ((unsigned)shellparam.optind > shellparam.nparam + 1) {
-			shellparam.optind = 1;
-			shellparam.optoff = -1;
-		}
+		max = shellparam.nparam + 1;
 	}
 	else {
 		optbase = &argv[3];
-		if ((unsigned)shellparam.optind > argc - 2) {
-			shellparam.optind = 1;
-			shellparam.optoff = -1;
-		}
+		max = argc - 2;
+	}
+
+	if (shellparam.optreparse) {
+		shellparam.optind = number(optindset() ? optindval() : "unset") ?: 1;
+		shellparam.optoff = -1;
+		shellparam.optreparse = false;
+	}
+
+	if ((unsigned)shellparam.optind > max) {
+		shellparam.optind = 1;
+		shellparam.optoff = -1;
 	}
 
 	return getopts(argv[1], argv[2], optbase);
diff --git a/src/options.h b/src/options.h
index 975fe33..8aebf9f 100644
--- a/src/options.h
+++ b/src/options.h
@@ -34,9 +34,12 @@
  *	@(#)options.h	8.2 (Berkeley) 5/4/95
  */
 
+#include <stdbool.h>
+
 struct shparam {
 	int nparam;		/* # of positional parameters (without $0) */
-	unsigned char malloc;	/* if parameter list dynamically allocated */
+	bool malloc;		/* if parameter list dynamically allocated */
+	bool optreparse;	/* re-parse optind/optoff from $OPTIND */
 	char **p;		/* parameter list */
 	int optind;		/* next parameter to be processed by getopts */
 	int optoff;		/* used by getopts */
diff --git a/src/var.h b/src/var.h
index aa7575a..ca90a68 100644
--- a/src/var.h
+++ b/src/var.h
@@ -133,6 +133,7 @@ extern char linenovar[];
 #define attyset()	((vatty.flags & VUNSET) == 0)
 #endif
 #define mpathset()	((vmpath.flags & VUNSET) == 0)
+#define optindset()	((voptind.flags & VUNSET) == 0)
 
 void initvar(void);
 struct var *setvar(const char *name, const char *val, int flags);
-- 
2.30.2

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] var: Do not add 1 to return value of strchrnul
  2023-01-04 16:05           ` наб
@ 2023-01-04 16:50             ` Harald van Dijk
  2024-05-19 11:19               ` [PATCH] options: Always reset OPTIND in getoptsreset Herbert Xu
  0 siblings, 1 reply; 18+ messages in thread
From: Harald van Dijk @ 2023-01-04 16:50 UTC (permalink / raw)
  To: наб, Herbert Xu; +Cc: dash

On 04/01/2023 16:05, наб wrote:
> Hi!
> 
> On Wed, Jan 04, 2023 at 10:12:36PM +0800, Herbert Xu wrote:
>> On Wed, Jan 04, 2023 at 01:54:21PM +0100, наб wrote:
>>> AFAICT, after trawling through the Issue 8 Draft 2.1,
>>> there are no special provisions for unset/OPTIND,
>> Well it says this regarding OPTIND:
>>
>> : If the application sets OPTIND to the value 1, a new set of parameters
>> : can be used: either the current positional parameters or new arg
>> : values. Any other attempt to invoke getopts multiple times in a single
>> : shell execution environment with parameters (positional parameters
>> : or arg operands) that are not the same in all invocations, or with an
>> : OPTIND value modified to be a value other than 1, produces unspecified
>> : results.
>>
>> Unsetting OPTIND ought to do the same thing as setting it to "",
> On the basis of? Those are fundamentally unrelated operations,
> and when they are to do the same thing, this is explicitly noted.

On the basis of common sense, keeping in mind that different people's 
ideas of common sense will be different. In this one, I'm tempted to 
agree with Herbert Xu, $OPTIND would expand to an empty string whether 
OPTIND is unset or set to an empty string, assuming set -u is not in 
effect, so for the purpose of the getopts command it would be good for 
it to behave the same way there too.

> Additionally, this is a description of /getopts/, and therefore only
> applies if you do "OPTIND=dupa; getopts ..." ‒ the getopts call is
> unspecified.
> 
> /Any/ operation on OPTIND is legal, and the current behaviour is wrong
> (but it seems that no-one has actually cared) ‒ POSIX defines this in
> terms of getopts inspecting OPTIND when it's run.

Indeed, but dash isn't the only shell that behaves this way (posh does 
too) and I'm not sure whether this is what POSIX intends to specify. I 
agree with your interpretation of what POSIX actually says, but it would 
be good for POSIX to reword it regardless of what is intended to be 
clearer that this case was actually considered.

Personally, I do think it is better if shells allow assigning arbitrary 
values to OPTIND, including unsetting it, and only have the getopts 
command raise an error if the value is non-numeric, but that is my 
personal opinion and I don't see much of a problem if dash decides to go 
the other way, unless POSIX makes it explicit that it is not permitted 
for shells to do this. FWIW, I did make that change for my version, 
<https://github.com/hvdijk/gwsh/commit/0df0ba33748eb3881b07cb724fd4fa54422ef2bc>, 
if that change is desired for dash it is easy to apply.

Cheers,
Harald van Dijk

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH] options: Always reset OPTIND in getoptsreset
  2023-01-04 16:50             ` Harald van Dijk
@ 2024-05-19 11:19               ` Herbert Xu
  2024-05-19 13:23                 ` Harald van Dijk
  0 siblings, 1 reply; 18+ messages in thread
From: Herbert Xu @ 2024-05-19 11:19 UTC (permalink / raw)
  To: Harald van Dijk; +Cc: наб, dash, 985478

On Wed, Jan 04, 2023 at 04:50:34PM +0000, Harald van Dijk wrote:
>
> Personally, I do think it is better if shells allow assigning arbitrary
> values to OPTIND, including unsetting it, and only have the getopts command
> raise an error if the value is non-numeric, but that is my personal opinion
> and I don't see much of a problem if dash decides to go the other way,
> unless POSIX makes it explicit that it is not permitted for shells to do
> this. FWIW, I did make that change for my version, <https://github.com/hvdijk/gwsh/commit/0df0ba33748eb3881b07cb724fd4fa54422ef2bc>,
> if that change is desired for dash it is easy to apply.

I've decided to make getoptsreset always do the reset, regardless
of the value of OPTIND.  Why else would you assign it anyway?

---8<---
Always reset OPTIND if it is modified by the user, regardless of
its value.

Reported-by: наб <nabijaczleweli@nabijaczleweli.xyz>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

diff --git a/src/options.c b/src/options.c
index 4d0a53a..c74e4fe 100644
--- a/src/options.c
+++ b/src/options.c
@@ -393,7 +393,7 @@ setcmd(int argc, char **argv)
 
 void getoptsreset(const char *value)
 {
-	shellparam.optind = number(value) ?: 1;
+	shellparam.optind = 1;
 	shellparam.optoff = -1;
 }
 
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] options: Always reset OPTIND in getoptsreset
  2024-05-19 11:19               ` [PATCH] options: Always reset OPTIND in getoptsreset Herbert Xu
@ 2024-05-19 13:23                 ` Harald van Dijk
  2024-05-19 14:21                   ` [v2 PATCH] " Herbert Xu
  0 siblings, 1 reply; 18+ messages in thread
From: Harald van Dijk @ 2024-05-19 13:23 UTC (permalink / raw)
  To: Herbert Xu; +Cc: наб, dash, 985478

On 19/05/2024 12:19, Herbert Xu wrote:
> On Wed, Jan 04, 2023 at 04:50:34PM +0000, Harald van Dijk wrote:
>>
>> Personally, I do think it is better if shells allow assigning arbitrary
>> values to OPTIND, including unsetting it, and only have the getopts command
>> raise an error if the value is non-numeric, but that is my personal opinion
>> and I don't see much of a problem if dash decides to go the other way,
>> unless POSIX makes it explicit that it is not permitted for shells to do
>> this. FWIW, I did make that change for my version, <https://github.com/hvdijk/gwsh/commit/0df0ba33748eb3881b07cb724fd4fa54422ef2bc>,
>> if that change is desired for dash it is easy to apply.
> 
> I've decided to make getoptsreset always do the reset, regardless
> of the value of OPTIND.  Why else would you assign it anyway?

This interacts terribly with the not fully reliable but nevertheless 
fairly commonly used "local OPTIND". When the local OPTIND goes out of 
scope and the prior value of OPTIND is restored, the expectation is not 
that option processing restarts from the beginning.

Cheers,
Harald van Dijk

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [v2 PATCH] options: Always reset OPTIND in getoptsreset
  2024-05-19 13:23                 ` Harald van Dijk
@ 2024-05-19 14:21                   ` Herbert Xu
  2024-05-21 14:38                     ` наб
  0 siblings, 1 reply; 18+ messages in thread
From: Herbert Xu @ 2024-05-19 14:21 UTC (permalink / raw)
  To: Harald van Dijk; +Cc: наб, dash, 985478

On Sun, May 19, 2024 at 02:23:23PM +0100, Harald van Dijk wrote:
>
> This interacts terribly with the not fully reliable but nevertheless fairly
> commonly used "local OPTIND". When the local OPTIND goes out of scope and
> the prior value of OPTIND is restored, the expectation is not that option
> processing restarts from the beginning.

Dash doesn't actually need "local OPTIND".  In fact, if you do
"local OPTIND" then dash will already be broken because even
if OPTIND is reset to the same value, the other internal state
optoff will end up being wrong.

However, it's not hard to make dash ignore "local OPTIND" and
make it work as if it wasn't there.

---8<---
Always reset OPTIND if it is modified by the user, regardless of
its value.

Do not call getoptsreset when returning from a function because
of "local OPTIND" as this simply trashes the caller's getopts
state.

Reported-by: наб <nabijaczleweli@nabijaczleweli.xyz>
Reported-by: Harald van Dijk <harald@gigawatt.nl>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>

diff --git a/src/options.c b/src/options.c
index 4d0a53a..c74e4fe 100644
--- a/src/options.c
+++ b/src/options.c
@@ -393,7 +393,7 @@ setcmd(int argc, char **argv)
 
 void getoptsreset(const char *value)
 {
-	shellparam.optind = number(value) ?: 1;
+	shellparam.optind = 1;
 	shellparam.optoff = -1;
 }
 
diff --git a/src/var.c b/src/var.c
index df432b5..b06b36c 100644
--- a/src/var.c
+++ b/src/var.c
@@ -93,7 +93,7 @@ struct var varinit[] = {
 	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS1=$ ",	0 },
 	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS2=> ",	0 },
 	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS4=+ ",	0 },
-	{ 0,	VSTRFIXED|VTEXTFIXED,		defoptindvar,	getoptsreset },
+	{ 0,	VSTRFIXED|VTEXTFIXED|VNOFUNC,	defoptindvar,	getoptsreset },
 #ifdef WITH_LINENO
 	{ 0,	VSTRFIXED|VTEXTFIXED,		linenovar,	0 },
 #endif
@@ -535,7 +535,7 @@ poplocalvars(void)
 				ckfree(vp->text);
 			vp->flags = lvp->flags;
 			vp->text = lvp->text;
-			if (vp->func)
+			if (vp->func && !(vp->flags & VNOFUNC))
 				(*vp->func)(varnull(vp->text));
 		}
 		ckfree(lvp);
-- 
Email: Herbert Xu <herbert@gondor.apana.org.au>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt

^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [v2 PATCH] options: Always reset OPTIND in getoptsreset
  2024-05-19 14:21                   ` [v2 PATCH] " Herbert Xu
@ 2024-05-21 14:38                     ` наб
  0 siblings, 0 replies; 18+ messages in thread
From: наб @ 2024-05-21 14:38 UTC (permalink / raw)
  To: Herbert Xu; +Cc: Harald van Dijk, dash, 985478

[-- Attachment #1: Type: text/plain, Size: 2007 bytes --]

On Sun, May 19, 2024 at 10:21:42PM +0800, Herbert Xu wrote:
> Always reset OPTIND if it is modified by the user, regardless of
> its value.
I disagree in principle, but I think this is basically fine actually;
strictly, we are allowed to do this
  98842  If the application sets OPTIND to the value 1, a new set of parameters can be used: either the
  98843  current positional parameters or new arg values. Any other attempt to invoke getopts multiple
  98844  times in a single shell execution environment with parameters (positional parameters or arg
  98845  operands) that are not the same in all invocations, or with an OPTIND value modified to be a
  98846  value other than 1, produces unspecified results.
but I cannot prove this breaks existing code in the wild.

I mean such code most likely definitely exists,
but I cannot prove this; but even if it does,
it relies on "unspecified" behaviour, which means it
"cannot be assured to be portable across conforming implementations"
such as dash 1 and dash 2.
I still think it should keep working, since it had worked.

While I found at least one changelog entry in DCS saying
"remove unset OPTIND to work around broken dash shell"
by searching for "unset.*OPTIND\b",
searching "OPTIND=[^01]" gives me heaps of OPTIND=digit
and OPTIND=expression users, but most of them are explicit bash users;
the first one with /bin/sh is
	https://sources.debian.org/src/sptk/3.9-3/debian/scripts/raw2wav/?hl=74#L74
reading
	OPTIND=0
	OPT=""
	for i in "$@"
	do
	    OPTIND=`expr $OPTIND + 1`
	    if [ "$OPT" = "" ]
	    then
		OPTARG=""
but this program doesn't use getopts.

This turns "unset OPTIND must work but doesn't"
         + "OPTIND=asd is invalid but probably shouldn't be"
         + "OPTIND=3 makes getopts parse the third argument (as a happy accident)"
into "unset OPTIND works"
   + "OPTIND=asd works (and restarts getopts as a happy accident)"
   + "OPTIND=3 restarts getopts (as a happy accident)".

So, overall, reasonable, if a tad solomonic.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2024-05-21 14:44 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-12-14  2:31 [PATCH] options: don't error when unsetting OPTIND наб
2022-12-14 14:18 ` Michael Greenberg
2022-12-14 17:49   ` Steffen Nurpmeso
2022-12-14 22:36     ` Michael Greenberg
2022-12-14 23:07       ` наб
2022-12-15 18:39         ` Michael Greenberg
2023-01-03  9:39 ` [PATCH] var: Do not add 1 to return value of strchrnul Herbert Xu
2023-01-03  9:51   ` [v2 PATCH] " Herbert Xu
2023-01-03 12:26   ` [PATCH] " наб
2023-01-04  9:59     ` Herbert Xu
2023-01-04 12:54       ` наб
2023-01-04 14:12         ` Herbert Xu
2023-01-04 16:05           ` наб
2023-01-04 16:50             ` Harald van Dijk
2024-05-19 11:19               ` [PATCH] options: Always reset OPTIND in getoptsreset Herbert Xu
2024-05-19 13:23                 ` Harald van Dijk
2024-05-19 14:21                   ` [v2 PATCH] " Herbert Xu
2024-05-21 14:38                     ` наб

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox