diff mbox series

[v3,1/5] wrapper.c: add x{un,}setenv(), and use xsetenv() in environment.c

Message ID patch-v3-1.5-4b320edc933-20210919T084703Z-avarab@gmail.com (mailing list archive)
State Superseded
Headers show
Series repo-settings.c: refactor for clarity, get rid of hacks etc. | expand

Commit Message

Ævar Arnfjörð Bjarmason Sept. 19, 2021, 8:47 a.m. UTC
Add fatal wrappers for setenv() and unsetenv(). In d7ac12b25d3 (Add
set_git_dir() function, 2007-08-01) we started checking its return
value, and since 48988c4d0c3 (set_git_dir: die when setenv() fails,
2018-03-30) we've had set_git_dir_1() die if we couldn't set it.

Let's provide a wrapper for both, this will be useful in many other
places, a subsequent patch will make another use of xsetenv().

The checking of the return value here is over-eager according to
setenv(3) and POSIX. It's documented as returning just -1 or 0, so
perhaps we should be checking -1 explicitly.

Let's just instead die on any non-zero, if our C library is so broken
as to return something else than -1 on error (and perhaps not set
errno?) the worst we'll do is die with a nonsensical errno value, but
we'll want to die in either case.

We could make these return "void" (as far as I can tell there's no
other x*() wrappers that needed to make that decision before),
i.e. our "return 0" is only to indicate that we didn't error, which we
would have died on. Let's return "int" instead to be consistent with
the C library function signatures, including for any future code that
expects a pointer to a setenv()-like function.

I think it would be OK skip the NULL check of the "name" here for the
calls to die_errno(). Almost all of our setenv() callers are taking a
constant string hardcoded in the source as the first argument, and for
the rest we can probably assume they've done the NULL check
themselves. Even if they didn't, modern C libraries are forgiving
about it (e.g. glibc formatting it as "(null)"), on those that aren't,
well, we were about to die anyway. But let's include the check anyway
for good measure.

1. https://pubs.opengroup.org/onlinepubs/009604499/functions/setenv.html

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 environment.c     |  3 +--
 git-compat-util.h |  2 ++
 wrapper.c         | 15 +++++++++++++++
 3 files changed, 18 insertions(+), 2 deletions(-)

Comments

Taylor Blau Sept. 20, 2021, 9:53 p.m. UTC | #1
On Sun, Sep 19, 2021 at 10:47:15AM +0200, Ævar Arnfjörð Bjarmason wrote:
> Add fatal wrappers for setenv() and unsetenv(). In d7ac12b25d3 (Add
> set_git_dir() function, 2007-08-01) we started checking its return
> value, and since 48988c4d0c3 (set_git_dir: die when setenv() fails,
> 2018-03-30) we've had set_git_dir_1() die if we couldn't set it.
>
> Let's provide a wrapper for both, this will be useful in many other
> places, a subsequent patch will make another use of xsetenv().

Makes sense.

> We could make these return "void" (as far as I can tell there's no
> other x*() wrappers that needed to make that decision before),
> i.e. our "return 0" is only to indicate that we didn't error, which we
> would have died on. Let's return "int" instead to be consistent with
> the C library function signatures, including for any future code that
> expects a pointer to a setenv()-like function.

This may be a little over-clever ;). It is cute, but returning an int
makes xsetenv a drop-in replacement for setenv. Which is nice, but it
makes it all too-easy to take code like:

  if (setenv(...) < 0)
    die(_("..."));

and replace it with

  if (xsetenv(...) < 0)

which makes the whole conditional redundant, since the wrappers are
guaranteed not to return an error.

In other words, I like the idea that s/setenv/x&/ causes a compile-time
error, and returning an int from these wrappers prevents that from
happening.

This may be a little too-theoretical, and you're certainly free to
disagree, just my $0.02.

> I think it would be OK skip the NULL check of the "name" here for the
> calls to die_errno(). Almost all of our setenv() callers are taking a
> constant string hardcoded in the source as the first argument, and for
> the rest we can probably assume they've done the NULL check
> themselves. Even if they didn't, modern C libraries are forgiving
> about it (e.g. glibc formatting it as "(null)"), on those that aren't,
> well, we were about to die anyway. But let's include the check anyway
> for good measure.

This I think is a good call. I agree in practice that most times we'd be
just fine to pass null to printf() (as we have seen from 88617d11f9
(multi-pack-index: fix potential segfault without sub-command,
2021-07-19) ;)). But there's no reason to rely on risky assumptions when
it's easy to avoid doing so.

> diff --git a/wrapper.c b/wrapper.c
> index 7c6586af321..95f989260cd 100644
> --- a/wrapper.c
> +++ b/wrapper.c
> @@ -145,6 +145,21 @@ void *xcalloc(size_t nmemb, size_t size)
>  	return ret;
>  }
>
> +int xsetenv(const char *name, const char *value, int overwrite)
> +{
> +	if (setenv(name, value, overwrite))
> +		die_errno("setenv(%s, '%s', %d) failed", name ? name : "(null)",
> +			  value, overwrite);
> +	return 0;
> +}
> +
> +int xunsetenv(const char *name)
> +{
> +	if (!unsetenv(name))
> +		die_errno("unsetenv(%s) failed", name ? name : "(null)");
> +	return 0;
> +}
> +

For what it's worth, I find these new messages a little wordy. Maybe
we should just sticky "could not (un)set %s"?

Thanks,
Taylor
Ævar Arnfjörð Bjarmason Sept. 20, 2021, 11:17 p.m. UTC | #2
On Mon, Sep 20 2021, Taylor Blau wrote:

> On Sun, Sep 19, 2021 at 10:47:15AM +0200, Ævar Arnfjörð Bjarmason wrote:
>> Add fatal wrappers for setenv() and unsetenv(). In d7ac12b25d3 (Add
>> set_git_dir() function, 2007-08-01) we started checking its return
>> value, and since 48988c4d0c3 (set_git_dir: die when setenv() fails,
>> 2018-03-30) we've had set_git_dir_1() die if we couldn't set it.
>>
>> Let's provide a wrapper for both, this will be useful in many other
>> places, a subsequent patch will make another use of xsetenv().
>
> Makes sense.
>
>> We could make these return "void" (as far as I can tell there's no
>> other x*() wrappers that needed to make that decision before),
>> i.e. our "return 0" is only to indicate that we didn't error, which we
>> would have died on. Let's return "int" instead to be consistent with
>> the C library function signatures, including for any future code that
>> expects a pointer to a setenv()-like function.
>
> This may be a little over-clever ;). It is cute, but returning an int
> makes xsetenv a drop-in replacement for setenv. Which is nice, but it
> makes it all too-easy to take code like:
>
>   if (setenv(...) < 0)
>     die(_("..."));
>
> and replace it with
>
>   if (xsetenv(...) < 0)
>
> which makes the whole conditional redundant, since the wrappers are
> guaranteed not to return an error.
>
> In other words, I like the idea that s/setenv/x&/ causes a compile-time
> error, and returning an int from these wrappers prevents that from
> happening.
>
> This may be a little too-theoretical, and you're certainly free to
> disagree, just my $0.02.

I'm fine with doing that in principle, I couldn't find any "x*()"
wrappers with different signatures. Yes, having the compiler complain
because you used a "void" return value would be nice.

I did have some vague notion that this might interact badly with
something in compat, i.e. if setenv() or unsetenv() was a fallback, but
I can't think now of why that wouldn't work...

>> I think it would be OK skip the NULL check of the "name" here for the
>> calls to die_errno(). Almost all of our setenv() callers are taking a
>> constant string hardcoded in the source as the first argument, and for
>> the rest we can probably assume they've done the NULL check
>> themselves. Even if they didn't, modern C libraries are forgiving
>> about it (e.g. glibc formatting it as "(null)"), on those that aren't,
>> well, we were about to die anyway. But let's include the check anyway
>> for good measure.
>
> This I think is a good call. I agree in practice that most times we'd be
> just fine to pass null to printf() (as we have seen from 88617d11f9
> (multi-pack-index: fix potential segfault without sub-command,
> 2021-07-19) ;)). But there's no reason to rely on risky assumptions when
> it's easy to avoid doing so.

*nod*

>> diff --git a/wrapper.c b/wrapper.c
>> index 7c6586af321..95f989260cd 100644
>> --- a/wrapper.c
>> +++ b/wrapper.c
>> @@ -145,6 +145,21 @@ void *xcalloc(size_t nmemb, size_t size)
>>  	return ret;
>>  }
>>
>> +int xsetenv(const char *name, const char *value, int overwrite)
>> +{
>> +	if (setenv(name, value, overwrite))
>> +		die_errno("setenv(%s, '%s', %d) failed", name ? name : "(null)",
>> +			  value, overwrite);
>> +	return 0;
>> +}
>> +
>> +int xunsetenv(const char *name)
>> +{
>> +	if (!unsetenv(name))
>> +		die_errno("unsetenv(%s) failed", name ? name : "(null)");
>> +	return 0;
>> +}
>> +
>
> For what it's worth, I find these new messages a little wordy. Maybe
> we should just sticky "could not (un)set %s"?

Sure, will change it.

I think I may have wanted to include the "overwrite" in the setenv() in
some way though, so you'd know if it failed because the libc call
"really" failed, v.s. we just had an existing value in the environment
and didn't set "overwrite".

But reading the docs again that won't work, since it'll succeed if it
can't write that key, i.e. the caller with overwrite=0 needs to
follow-up with a getenv() to see if their value or someone else's was
set (or more probably, they don't care, which is why they used
overwrite=0).

So it doesn't matter either way for xsetenv(), will make it less chatty.
diff mbox series

Patch

diff --git a/environment.c b/environment.c
index d6b22ede7ea..7d8a949285c 100644
--- a/environment.c
+++ b/environment.c
@@ -330,8 +330,7 @@  char *get_graft_file(struct repository *r)
 
 static void set_git_dir_1(const char *path)
 {
-	if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
-		die(_("could not set GIT_DIR to '%s'"), path);
+	xsetenv(GIT_DIR_ENVIRONMENT, path, 1);
 	setup_git_env(path);
 }
 
diff --git a/git-compat-util.h b/git-compat-util.h
index b46605300ab..0b0c0305165 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -875,6 +875,8 @@  void *xmemdupz(const void *data, size_t len);
 char *xstrndup(const char *str, size_t len);
 void *xrealloc(void *ptr, size_t size);
 void *xcalloc(size_t nmemb, size_t size);
+int xsetenv(const char *name, const char *value, int overwrite);
+int xunsetenv(const char *name);
 void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
 const char *mmap_os_err(void);
 void *xmmap_gently(void *start, size_t length, int prot, int flags, int fd, off_t offset);
diff --git a/wrapper.c b/wrapper.c
index 7c6586af321..95f989260cd 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -145,6 +145,21 @@  void *xcalloc(size_t nmemb, size_t size)
 	return ret;
 }
 
+int xsetenv(const char *name, const char *value, int overwrite)
+{
+	if (setenv(name, value, overwrite))
+		die_errno("setenv(%s, '%s', %d) failed", name ? name : "(null)",
+			  value, overwrite);
+	return 0;
+}
+
+int xunsetenv(const char *name)
+{
+	if (!unsetenv(name))
+		die_errno("unsetenv(%s) failed", name ? name : "(null)");
+	return 0;
+}
+
 /*
  * Limit size of IO chunks, because huge chunks only cause pain.  OS X
  * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in