Using environment variables without leaking memory?

Rob Landley rob at landley.net
Tue Oct 24 19:51:35 UTC 2006


On Tuesday 24 October 2006 12:54 pm, David Daney wrote:
> > Except for the part where execvp looks at $PATH in a library function 
that's 
> > using getenv() behind the scenes, you mean? 
> 
> No I meant to call the system call execve() which knows nothing about 
> $PATH and as far as I can see does is not influenced by the caller's 
> environment in any way.

Yeah, but the one I was _using_ was execvp() and since there isn't an 
orthogonal "find executable in $PATH" function I now have to reimplement 
that.

> > it exists it's global, although I haven't implemented
> > "PATH=blah:$PATH ./thingy" yet and expect that will require careful 
sequencing 
> > but that _is_ an implicit post-fork export, but still pre-execvp)...
> 
> Basically you are saying you would like libc to take care of managing 
> the environment in a manner that will not leak memory so that writing a 
> new shell is easier.

It'd be darn nice, but I see now that it doesn't do it.

But I might submit a patch to fix up uClibc to do this, because it _is_ a 
solvable problem.  It does need a new function added, but oh well.

You start with a fixed number of non-freeable variables allocated by exec() 
outside of your heap.  All the others are allocated with malloc() and we can 
free 'em.  (At least in the setenv() case, passing a constant string to 
putenv() and then calling envfree() instead of unsetenv() is "pilot error".)

First thing to do is maintain a count of the number of unfreeable entries.  
This is initialized to the number of entries in __environ before we do our 
first modification of the environment (and we know which modification is our 
first because last_environ is null).  Whenever you add a new environment 
variable, put it at the end of the array (already the case).  Whenever you 
remove an environment variable whose index in __environ is less than this 
count, decrement the count.  Whenever you remove an environment variable 
that's greater than or equal to this count, it's something we added and thus 
something we could potentially free().  (Corner case: whenever you replace an 
environment variable that's less than this count, remove the old one and then 
add the new one to the end rather than updating in place.)

This keeps track of when you _can_ free an environment variable, but doesn't 
say when you should.  Just doing this blindly screws up existing programs, 
because what if somebody did a getenv() and kept the pointer around after 
doing an unsetenv()?  So we create a new function, envfree(name), which acts 
like unsetenv(name) but doesn't leak memory.  Then it's the caller's job to 
get the usage right.  (I.E. don't keep pointers from getenv() past the 
corresponding envfree(), and if you putenv() something that can't be freed 
use unsetenv() instead of envfree() to get rid of it.  Or just always use 
setenv() which creates a copy already.)

Not actually all that hard to implement, really.  Why people have accepted 
such a screwed up status-quo for 15 years (forget about Unix, why have the 
_linux_ people accepted it?) is an open question.

At the moment, I'm implementing my own xsetenv() and envfree() in toybox's 
library.  (It would be freeenv() except that the three consecutive e's are a 
bit much.)  I'll happily post them here LGPL when I've got 'em working, 
though...

> > So your suggestion for modifying the environment is "don't"?
> 
> Correct.

Not good enough. :)

> > Is this inherently broken and unworkable?
> 
> Assuming you are referring to the libc and not my idea, I have no idea. 
>   My suggestion was just off the top of my head.  My suggestion is of 
> course bug free :)

My implementation of an unrelated solution to the larger problem isn't yet, 
but I'm working on it...

> David Daney

Rob
-- 
"Perfection is reached, not when there is no longer anything to add, but
when there is no longer anything to take away." - Antoine de Saint-Exupery



More information about the uClibc mailing list