[comp.unix.microport] 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