Hi Jason, On 2026-07-07T06:53:26-0400, Jason Yundt wrote: > Before this change, environ(7) said this: > > > By convention, the strings in environ have the form "name=value". The > > name is case-sensitive and may not contain the character "=". The > > value can be anything that can be represented as a string. The name > > and the value may not contain an embedded null byte ('\0'), since this > > is assumed to terminate the string. > > That description has a few problems: > > 1. It talks about ‘the character "="’, but it doesn’t specify what > character encoding would be used to represent that character. Two > different character encodings could represent that same “=” character > using two different bytes (or even sequences of bytes). > > 2. It mentions that ‘The name is case-sensitive and may not contain the > character "=".’ It doesn’t clearly say what what is allowed to be in > a name. It only says that those two things are explicitly > disallowed. > > This change fixes those two problems. For the first problem, this > change makes it so that the description is all about bytes, not > characters. Describing the format in terms of bytes allows us to > sidestep the question of character encoding entirely. Additionally, it > is more accurate to describe strings in environ as being sequences of > bytes instead of sequences of characters. Both the name and value of an > environment variable could be sequences of bytes that don’t contain any > characters at all. > > For the second problem, this change clarifies that the name of an > environment variable can contain any byte except for 0x3D. It also > clarifies that while it’s OK for environment variable values to be > empty, it’s not OK for environment variable names to be empty. > > Additionally, this change replaces "=" with '='. In the C programming > language, "=" refers to two bytes: one for the equals character plus one > for the terminating null byte. In the C programming language, '=' > refers to a single byte. In this particular instance, we’re talking > about a single byte, so it’s better to use '='. Using '=' also makes > environ(7) more internally consistent. Before this change, environ(7) > used '\0' and "=". This change makes it so that environ(7) uses '\0' > and '='. I like the change from this paragraph. If you send a separate patch with only this change, I'll apply it. > > I was able to obtain obtain the information that I needed in order to > create this change by writing two test programs: > > show-env-var-bytes.c: > > #include > #include > > void print_string_bytes(char *s) { > printf("\""); > for (size_t i = 0; s[i] != '\0'; i++) { > printf("\\x%02hhX", s[i]); > } > printf("\"\n"); > } > > int main(int argc, char *argv[]) { > if (argc != 2) { > fprintf(stderr, "USAGE: %s \n", argv[0]); > return EXIT_FAILURE; > } > printf("Name: "); > print_string_bytes(argv[1]); > char *getenv_result = getenv(argv[1]); > if (getenv_result == NULL) { > fprintf(stderr, "Environment variable not found.\n"); > return EXIT_FAILURE; > } > printf("Value: "); > print_string_bytes(getenv_result); > } > > set-env-var-then-show-bytes.c: > > #include > #include > #include > #include > #include > > char *every_nonnull_byte(bool exclude_3d_byte); > char *environ_item(char *name, char *value); > > char *every_nonnull_byte(bool exclude_3d_byte) { > char *ret; > size_t i = 0, size = 256; Unused variable 'i'. > unsigned char byte = '\1'; > > if (exclude_3d_byte) > size -= 1; > ret = malloc(size); > for (size_t i = 0; i < (size - 1); i++) { > if (exclude_3d_byte && byte == '\x3D') { > byte++; > } > ret[i] = byte; > byte++; > } > ret[size - 1] = '\0'; > return ret; > } > > char *environ_item(char *name, char *value) { > char *ret = malloc(strlen(name) + 1 + strlen(value) + 1); > > sprintf(ret, "%s\x3D%s", name, value); > return ret; > } > > int main(int argc, char *argv[]) { > char *name = every_nonnull_byte(true); > char *value = every_nonnull_byte(false); > char *env[] = { environ_item(name, value), NULL }; > > if (argc != 2) { > fprintf(stderr, "USAGE: %s \n", argv[0]); > return EXIT_FAILURE; > } > > execle(argv[1], argv[1], name, NULL, env); > perror("execle() failed"); > free(name); > free(value); > free(env[0]); > return EXIT_FAILURE; > } > > Once those two programs are compiled you can run them by running this > command: > > ./set-env-var-then-show-bytes ./show-env-var-bytes I think these programs don't prove what happens on a system with an encoding incompatible with ASCII. I think if you compile glibc on an EBDIC-based system (if that is possible at all, which I ignore), you'll get the behavior based on '=' and not 0x3D. I think this is required by POSIX. 0x3D is not special. Of course, if your system is a mix of ASCII and EBDIC, you have a problem: glibc might have been compiled as ASCII (and thus use 0x3D) and your program might be using something else. Cheers, Alex > Additionally, I got the information about the setenv(3) and unsetenv(3) > functions from their man pages (specifically, the parts of their man pages > that talk about EINVAL). > > Signed-off-by: Jason Yundt > --- > man/man7/environ.7 | 16 +++++++++++++--- > 1 file changed, 13 insertions(+), 3 deletions(-) > > diff --git a/man/man7/environ.7 b/man/man7/environ.7 > index 31a69017cf75..89d48991e8b7 100644 > --- a/man/man7/environ.7 > +++ b/man/man7/environ.7 > @@ -28,9 +28,19 @@ .SH DESCRIPTION > .I environ > have the form > .RI \[dq] name\f[B]=\f[]value \[dq]. > -The name is case-sensitive and may not contain > -the character > -.RB \[dq] = \[dq]. > +The name is case-sensitive > +and may contain any nonnull byte > +other than 0x3D (the > +.BR ascii (7) > +.RB \[aq] = \[aq] > +character). > +The name must be at least one byte long, > +or else programs will not be able to manipulate it using the > +.BR setenv (3) > +or > +.BR unsetenv (3) > +functions. > +Immediately after the name, there must be a 0x3D byte. > The value can be anything that can be represented as a string. > The name and the value may not contain an embedded null byte (\[aq]\[rs]0\[aq]), > since this is assumed to terminate the string. > > Range-diff against v1: > 1: d5b0d9b86029 ! 1: 74bea4adebbd man/man7/environ.7: Fix underspecification of "name=value" strings > @@ Commit message > and '='. > > I was able to obtain obtain the information that I needed in order to > - create this change by writing a test program. You can find the test > - program here [1]. Additionally, I got the information about the setenv(3) > - and unsetenv(3) functions from their man pages (specifically, the parts > - of their man pages that talk about EINVAL). > + create this change by writing two test programs: > > - [1]: > + show-env-var-bytes.c: > + > + #include > + #include > + > + void print_string_bytes(char *s) { > + printf("\""); > + for (size_t i = 0; s[i] != '\0'; i++) { > + printf("\\x%02hhX", s[i]); > + } > + printf("\"\n"); > + } > + > + int main(int argc, char *argv[]) { > + if (argc != 2) { > + fprintf(stderr, "USAGE: %s \n", argv[0]); > + return EXIT_FAILURE; > + } > + printf("Name: "); > + print_string_bytes(argv[1]); > + char *getenv_result = getenv(argv[1]); > + if (getenv_result == NULL) { > + fprintf(stderr, "Environment variable not found.\n"); > + return EXIT_FAILURE; > + } > + printf("Value: "); > + print_string_bytes(getenv_result); > + } > + > + set-env-var-then-show-bytes.c: > + > + #include > + #include > + #include > + #include > + #include > + > + char *every_nonnull_byte(bool exclude_3d_byte); > + char *environ_item(char *name, char *value); > + > + char *every_nonnull_byte(bool exclude_3d_byte) { > + char *ret; > + size_t i = 0, size = 256; > + unsigned char byte = '\1'; > + > + if (exclude_3d_byte) > + size -= 1; > + ret = malloc(size); > + for (size_t i = 0; i < (size - 1); i++) { > + if (exclude_3d_byte && byte == '\x3D') { > + byte++; > + } > + ret[i] = byte; > + byte++; > + } > + ret[size - 1] = '\0'; > + return ret; > + } > + > + char *environ_item(char *name, char *value) { > + char *ret = malloc(strlen(name) + 1 + strlen(value) + 1); > + > + sprintf(ret, "%s\x3D%s", name, value); > + return ret; > + } > + > + int main(int argc, char *argv[]) { > + char *name = every_nonnull_byte(true); > + char *value = every_nonnull_byte(false); > + char *env[] = { environ_item(name, value), NULL }; > + > + if (argc != 2) { > + fprintf(stderr, "USAGE: %s \n", argv[0]); > + return EXIT_FAILURE; > + } > + > + execle(argv[1], argv[1], name, NULL, env); > + perror("execle() failed"); > + free(name); > + free(value); > + free(env[0]); > + return EXIT_FAILURE; > + } > + > + Once those two programs are compiled you can run them by running this > + command: > + > + ./set-env-var-then-show-bytes ./show-env-var-bytes > + > + Additionally, I got the information about the setenv(3) and unsetenv(3) > + functions from their man pages (specifically, the parts of their man pages > + that talk about EINVAL). > > Signed-off-by: Jason Yundt > > @@ man/man7/environ.7: .SH DESCRIPTION > -The name is case-sensitive and may not contain > -the character > -.RB \[dq] = \[dq]. > --The value can be anything that can be represented as a string. > --The name and the value may not contain an embedded null byte (\[aq]\[rs]0\[aq]), > --since this is assumed to terminate the string. > +The name is case-sensitive > -+and may contain any byte > -+other than null (\[aq]\[rs]0\[aq]) and 0x3D (the > ++and may contain any nonnull byte > ++other than 0x3D (the > +.BR ascii (7) > +.RB \[aq] = \[aq] > +character). > @@ man/man7/environ.7: .SH DESCRIPTION > +or > +.BR unsetenv (3) > +functions. > -+Immediately after the name, there should be a 0x3D byte. > -+Immediately after the 0x3D byte is the value. > -+The value may contain any byte except for null. > -+The value may be zero bytes long. > -+Immediately after the value, there must be a terminating null byte. > - .P > - Environment variables may be placed in the shell's environment by the > - .I export > ++Immediately after the name, there must be a 0x3D byte. > + The value can be anything that can be represented as a string. > + The name and the value may not contain an embedded null byte (\[aq]\[rs]0\[aq]), > + since this is assumed to terminate the string. > -- > 2.54.0 > --