[comp.unix.wizards] bug in putenv

asmodeus@tree.UUCP (Jonathan Ballard) (02/17/89)

Hi!

For some reason putenv() is not setting the environment variables right
with certain array declarations.  I wrote a putenv() replacement which 
works fine.  So I assume that either putenv() has a bug in it or that 
the format is different then normal in this version of UNIX.  Which is it?
I'm using System V/AT 2.4.

Here are some examples of what went wrong.

In this example, putenv will return ok but it never sets $USERNAME.
>	char envbuf[100];
>
>	sprintf(envbuf,"USERNAME=%s","hi");
>	putenv(envbuf);

But it works right if done like this...
>	putenv("USERNAME=hi");

The strange part is it works with numbers when doing a sprintf...
>	sprintf(envbuf,"USERNUM=%d",13);
>	putenv(envbuf);

I made my own putenv() routine (called "PutEnv()") that does what putenv()
is susposed to do and it works fine.  If you need to use this then
go ahead and modify it in any way.  I'm not responsible for any damage
done if you do use this.  I have no way of throughly testing it against
everything that putenv() does so this shouldn't be used as total replacement
of putenv(), unless someone can say it does the full job.

-----------------------------------8<----------------------------------------

PutEnv(str)
char *str;
{
	
	int i,len;
	extern char **environ;
	char **tmpenvp,*envp;  /* this is used just in case malloc or realloc
				fails and the orginal pointer value could
				be saved */

	len=strcspn(str,"=");
	/* check first to see if there is an environment, 
		if not then make one */
	if(environ==(char **)0) {
		if((environ=(char **)malloc(sizeof(char *)))==(char **)0)
			return(-1);
		environ[0]=(char *)0;
	}

	/* now check to see if the enviromental varible is already there
		if it is then replace the value with the new one */
	for(i=0;environ[i]!=(char *)0;i++) 
		if(strncmp(str,environ[i],len+1)==0) {
			envp=environ[i];
			if((environ[i]=(char *)realloc(environ[i],strlen(str)+1))==(char *)0) {
				environ[i]=envp;
				return(-1);
			}
			strcpy(environ[i],str);
			return(0);
		}
	tmpenvp=environ;
	/* if there isn't a matching environmental varible then create it */
	if((environ=(char **)realloc(environ,(i+2)*sizeof(char *)))==(char **)0)
	{
		environ=tmpenvp;
		return(-1);
	}
	if((environ[i]=(char *)malloc(strlen(str)+1))==(char *)0)
		return(-1);
	environ[i+1]=(char *)0;
	strcpy(environ[i],str);
	return(0);
}
-- 
----Asmodeus - Jonathan Ballard  ..!csusac!tree!asmodeus
				 ..!pacbell!sactoh0!tree!asmodeus
"I'm going to create the best game ever heard of!
	Might take a few years thou..." -me

mlandau@bbn.com (Matt Landau) (02/18/89)

In comp.unix.wizards, asmodeus@tree.UUCP (Jonathan Ballard) writes:
>For some reason putenv() is not setting the environment variables right
>In this example, putenv will return ok but it never sets $USERNAME.
>>	char envbuf[100];
>>
>>	sprintf(envbuf,"USERNAME=%s","hi");
>>	putenv(envbuf);

Is it possible your version of putenv doesn't actually copy the buffer
passed in, but instead maintains a pointer to the original argument
in the environment?  If that's true, you cannot pass an automatic to
putenv and expect anything reasonable to happen - you have to pass
either global or allocated storage.

The SunOS 3.5 man page on putenv, for example, says this:

  	A potential error is to call putenv with an automatic vari-
        able as the argument, then exit the calling function while
	string is still part of the environment.

Don't know if SysV/AT does the same thing or not, but it's worth a look.
--
 Matt Landau			Waiting for a flash of enlightenment
 mlandau@bbn.com			  in all this blood and thunder

guy@auspex.UUCP (Guy Harris) (02/18/89)

>>	char envbuf[100];
>>
>>	sprintf(envbuf,"USERNAME=%s","hi");
>>	putenv(envbuf);

To quote from the SunOS 4.0 PUTENV(3) man page (which probably describes
most "putenv" implementations, since the code hasn't changed much as far
as I know - I think the 4.0 one is just the S5R2 one with a Sun SCCS
header stuck in...):

DESCRIPTION
     'string' points to a string  of  the  form  `name  =  value'
     putenv()  makes  the  value of the environment variable name
     equal to value by altering an existing variable or  creating
     a  new  one.   In  either  case,  the  string  pointed to by
     'string' becomes part of the environment,  so  altering  the
     string  will  change  the  environment.   The  space used by
     'string' is no longer used once a new  string-defining  name
     is passed to putenv.

WARNINGS
     ...

     A potential error is to  call  putenv()  with  an  automatic
     variable  as  the  argument,  then exit the calling function
     while 'string' is still part of the environment.

It sure looks as if you're handing "putenv" an automatic variable in
your example; do you return from the function in which that automatic is
declared before doing another "putenv" to modify "USERNAME"?  If so,
that's probably your problem.

>But it works right if done like this...
>>	putenv("USERNAME=hi");

The argument is static, not automatic, so the string stays around after
the function returns.

>The strange part is it works with numbers when doing a sprintf...
>>	sprintf(envbuf,"USERNUM=%d",13);
>>	putenv(envbuf);

Sheer luck, possibly.

asmodeus@tree.UUCP (Jonathan Ballard) (02/21/89)

In article <12649@jade.BBN.COM>, mlandau@bbn.com (Matt Landau) writes:
>  In comp.unix.wizards, asmodeus@tree.UUCP (Jonathan Ballard) writes:
[stuff deleted]
> putenv and expect anything reasonable to happen - you have to pass
> either global or allocated storage.
> 
> The SunOS 3.5 man page on putenv, for example, says this:
> 
>   	A potential error is to call putenv with an automatic vari-
>         able as the argument, then exit the calling function while
> 	string is still part of the environment.

Ours says the same thing.  I've tried global varible and pointers also.
They didn't work right either.
The strange part is everybody says the a automatic varible won't work.
Why is it then when I use this next example that it works fine.  Notice
it is defining the enviromental varible to a number.
	sprintf(envbuf,"UID=%d",12);
	putenv(envbuf);
It worked fine and stayed in the enviroment.

-- 
----Asmodeus - Jonathan Ballard  ..!csusac!tree!asmodeus
				 ..!pacbell!sactoh0!tree!asmodeus
"I'm going to create the best game ever heard of!
	Might take a few years thou..." -me

asmodeus@tree.UUCP (Jonathan Ballard) (02/21/89)

From what you people have been saying is that I can't pass in a automatic
varible because putenv uses that space, right?
This seems to be a dumb way to go.  Why doesn't putenv just alloc a new
spot for the enviroment varible to go? (Like mine does.)
But if I do understand this right - I have to make several global or
static varible just to handle space for the enviroment.  This to me 
seems to make things clutter up.
-- 
----Asmodeus - Jonathan Ballard  ..!csusac!tree!asmodeus
				 ..!pacbell!sactoh0!tree!asmodeus
"I'm going to create the best game ever heard of!
	Might take a few years thou..." -me

cjc@ulysses.homer.nj.att.com (Chris Calabrese[mav]) (02/22/89)

In article <233@tree.UUCP>, asmodeus@tree.UUCP (Jonathan Ballard) writes:
> From what you people have been saying is that I can't pass in a automatic
> varible because putenv uses that space, right?
> This seems to be a dumb way to go.  Why doesn't putenv just alloc a new
> spot for the enviroment varible to go? (Like mine does.)
> But if I do understand this right - I have to make several global or
> static varible just to handle space for the enviroment.  This to me 
> seems to make things clutter up.

No, just malloc them before passing them to putenv, as you
are doing now.

It seems to me that putenv is a useful function which
operates on a fairly low level on a very simple data structure
(array of pointers).  It also works.  The user, however,
may opt to develop an interface to putenv which will optimize
for speed, memory usage, etc.

If putenv always malloc'd space, it would start eating up
some serious memory in something like the shell, where you might
change the value of an environment variable several hundred
times in a particularly large script (please don't ask for examples,
you get the general idea :-).
It could free the old space, however, there's
no way of telling if it was created by malloc in the first place,
to it would either have to maintain a list of what's freeable,
or malloc new space for the whole environment at the start of the
program.

Perhaps the real answer is a better data structure for the
environment, but I thing UNIX (all flavors) is pretty much stuck
with the current one.
-- 
Name:			Christopher J. Calabrese
Brain loaned to:	AT&T Bell Laboratories, Murray Hill, NJ
att!ulysses!cjc		cjc@ulysses.att.com
Obligatory Quote:	``Now, where DID I put that bagel?''

chris@mimsy.UUCP (Chris Torek) (02/22/89)

In article <233@tree.UUCP> asmodeus@tree.UUCP (Jonathan Ballard) writes:
>From what you people have been saying is that I can't pass in a automatic
>varible because putenv uses that space, right?

No, not right; see below.

>... Why doesn't putenv just alloc a new spot for the enviroment
>varible to go?

It expects *you* to do that, if it is necessary (often it is not).

>But if I do understand this right - I have to make several global or
>static varible just to handle space for the enviroment.

Not so.

A real treatment of the issue belongs in comp.lang.c.  The important
part, however, is the storage for the *characters* to which the thing
you hand to putenv() will point must remain undisturbed.  Thus, the
following is wrong (but may work, if you are unlucky, depending on
how much stack junk you might have:)

	main() {
		set();
		... refer to environment ...
	}

	set() {
		char env[30];

		strcpy(env, "FOO=bar");
		putenv(env);
	}

But if main() does not return to its caller (if it ends with exit(0)),
or if its caller (the C library startup code) does not look at the
environment, the following is correct:

	main() {
		char env[30];

		strcpy(env, "FOO=bar");
		putenv(env);
	}

In both examples, `env' provides space for 30 characters.  The
environment will retain a pointer to the first of those thirty
characters (the `F' in FOO=bar).  This space-for-30-chars is `alive'
(guaranteed to remain undisturbed) for the duration of main() and any
functions called by main()---in other words, for the entire program.
If `env' is declared in `set()', the space is alive only for the
duration of the call to set().  When set() returns to main(), the space
becomes dead and is free to be reused.  Whether it *is* reused is
compiler- and system-dependent (and may even vary from one run to
another).

As another example,

		putenv("FOO=bar");

would be correct whether it were in set() or in main().  This is
because a double-quoted string has static storage duration:  that is,
there is one copy of the characters making up that string, and it sits
somewhere in text or data space, never moving and (unless you do
something technically illegal) never changing.  You can make set()
work by changing it from

	set() {
		char env[30];

to

	set() {
		static char env[30];

Finally, at any time, you can always call

	p = strdup(pointer);
	if (p == NULL) ... error ...
	putenv(p);

(you may need to write strdup() first if you have an old support
library).  strdup() calls malloc() to allocate space for the string
at which `pointer' is assumed to point.  The space allocated by
malloc() will not be reused unless you allow it.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

Leisner.Henr@xerox.com (marty) (02/23/89)

According to Christopher J. Calabrese
>> If putenv always malloc'd space, it would start eating up
some serious memory in something like the shell, where you might
change the value of an environment variable several hundred
times in a particularly large script (please don't ask for examples,
you get the general idea :-)

I don't agree -- putenv could detect whether an equivalent environment
string already exists.  If putenv always does a malloc/strcpy, whenever a
string is overwritten it could free  or realloc the storage.

I had a version which works sorta this way on Ms/Dos (actually the whole
environment array was one malloced block ).

A version of putenv on a masscomp I'm looking at right now appears to work
the same way (it uses realloc on successive calls to putenv after the first
malloc [hmmm -- maybe someone else had the same idea ;-) ]).

I find having to allocate static storage for putenv to be kinda strange --
I guess it's another portability bugaboo.

marty
ARPA:	leisner.henr@xerox.com
GV:  leisner.henr
NS:  martin leisner:wbst139:xerox
UUCP:  hplabs!arisia!leisner

cjc@ulysses.homer.nj.att.com (Chris Calabrese[mav]) (02/23/89)

In article <18436@adm.BRL.MIL>, Leisner.Henr@xerox.com (marty) writes:
> According to Christopher J. Calabrese
> >> If putenv always malloc'd space, it would start eating up
> some serious memory in something like the shell, where you might
> change the value of an environment variable several hundred
> times in a particularly large script (please don't ask for examples,
> you get the general idea :-)
> 
> I don't agree -- putenv could detect whether an equivalent environment
> string already exists.  If putenv always does a malloc/strcpy, whenever a
> string is overwritten it could free  or realloc the storage.

Yes, this detection would be quite simple, but the real trick
would be to detect if the old value had it's space malloc'd for it.
I don't think most implementations malloc the initial space,
or at least not for each individual value in the array, so you
can't use free() on these.  In order to do this, there would
have to be some kind of guarentee that the space could be free'd
first, and that's highly implementation dependant.

> I had a version which works sorta this way on Ms/Dos (actually the whole
> environment array was one malloced block ).
> A version of putenv on a masscomp I'm looking at right now appears to work
> the same way (it uses realloc on successive calls to putenv after the first
> malloc [hmmm -- maybe someone else had the same idea ;-) ]).

These are reasonable ways of doing it, but it has to be done from
the ground up - you can't just tack on a new version of putenv.

BTW, what do you mean by the `first malloc'?  Do the elements of the
array get created with malloc, or is there some list of spaces
which are allowed to be free'd because they were created with putenv
(one way of avoiding the problems of
non-portability/implementation-dependence)?
-- 
Name:			Christopher J. Calabrese
Brain loaned to:	AT&T Bell Laboratories, Murray Hill, NJ
att!ulysses!cjc		cjc@ulysses.att.com
Obligatory Quote:	``Now, where DID I put that bagel?''

mash@mips.COM (John Mashey) (02/27/89)

The "environment", once upon a time was, in sequence:
	a) A small number of fixed-length strings kept in the u-area,
	in PWB/UNIX, set upon login, and essentially read-only thereafter.
	b) A magic file opened by a shell, which left a high-numbered file
	descriptor open so that descendent shells could look at it,
	assuming no intervening processes had closed it. [intermediate
	versions of the Bourne shell, before 7th edition].
	c) A variable list of name-value pairs, kept in kernel data space,
	and managed by system calls, by the "family" of processes that owned it.
	(Proposed way of doing it for V7, never implemented, because:
		1) Mismatch of the semantics of this, and what people really
		wanted to do with the environment.  In particular, what did
		a parent process see, and when, if a child process modified
		the environment? For various reasons, we believed that it was
		Evil to introduce something whose semantics let it be
		an inter-process communication method, when there were much
		better alternatives with firmer bases available.
		2) A fair amount of machinery, complexity, and code for the
		intended purpose,which was mostly to permit clean-semanticed
		inheritance of "invisible" parameters, i.e., data structures
		that could be passed around without bothering the masses of
		code out there.
	d) The current model, suggested by DMR, which treats the environment
	almost identically to the argument list, i.e.:
		a) It is allocated on the stack at exec time; it is NOT
		malloced.  Only a small amount of kernel code knows anything
		about the environment, and once it copies it to the being-
		built image, it forgets about it.  Remember that this was
		designed in the days when the most commonly-used machines
		allowed all of 64Kbytes of kernel code, not in the latter days
		of creeping featurism :-)
		b) Many programs don't need to look at the environment much;
		many need to look at it; a few need to change it.  Thus,
		the design was tuned in that direction.
		c) The semantics are simple: you start with the environment
		passed in to you as a behind-the-back environment list.
		If you don't do anything, you children inherit it.
		If you change it, your ancestors know nothing about it,
		but your children still inherit it.
		If you put a lot of junk in the environment, YOU pay for
		it in YOUR process address space consumption.
		----
		d) Personally, I thought this was a classically-elegant way
		of solving the bulk of the problem with really-minimal
		mechanism, except for those few programs that did a lot of
		environment modification; typically, those programs (like *sh),
		have their own large set of dynamic storage allocation code
		anyway, and are not going to use any general-purpose mechanism.

Anyway, if you understand the form and nature of (argv) and just think
of the environment as another of those, it's probably easier to understand.
-- 
-john mashey	DISCLAIMER: <generic disclaimer, I speak for me only, etc>
UUCP: 	{ames,decwrl,prls,pyramid}!mips!mash  OR  mash@mips.com
DDD:  	408-991-0253 or 408-720-1700, x253
USPS: 	MIPS Computer Systems, 930 E. Arques, Sunnyvale, CA 94086