CMPSC
311,
Introduction to Systems Programming
Environment
Variables,
Part
2
Reading
- CP:AMA, pp. 688-689, 765-766,
getenv()
- CS:APP, Sec. 8.4.5, Loading and Running Programs
- C:ARM, Sec. 16.6,
getenv; Sec. 9.9, The Main
Program (second
part); Sec. 16.7.1, exec.
- APUE, Sec. 7.5, Environment List; Sec. 7.9, Environment
Variables
(other sections will be mentioned as we go)
References
- C Standard, Sec. 7.20.4.5, The
getenv function
- Posix Standard, Base Definitions, Ch. 8, Environment Variables
- Posix Standard, Rationale for Base Definitions, Sec. A.8,
Environment Variables
- Posix Standard, System Interfaces,
getenv(), putenv(),
setenv(), unsetenv(), exec()
- Solaris,
environ(5), getenv(3C), putenv(3C),
setenv(3C), unsetenv(3C), exec(2)
- Linux,
environ(5), getenv(3), putenv(3),
setenv(3), execve(2), exec(2)
- Mac OS X,
environ(7), getenv(3), execve(2),
exec(3)
Related topics
Topics that sound like they ought
to
be related, but they aren't
- floating-point environment,
fegetenv(), fesetenv()
Program Interfaces
extern char **environ;
environ is a pointer to a NULL-terminated array
of
pointers to null-terminated character
strings. Each string has the form "name=value"
to
indicate the name of an environment variable and its current
value. If you need to find the value of a specific
environment
variable, use getenv()
to do the search.
- The initial values of
environ, the array of
pointers it points to, and the strings the array elements
point to, are
all set by the system-supplied startup function initiated by
the
command
shell. Typically the array and strings start on the
runtime stack
of the process, but the details are
implementation-dependent. If putenv()
or setenv() is called, the array of pointers may
need to
be copied to a larger area, which would require changing the
value
of environ.
environ is the only object specified in the
Posix
standard whose definition is not in an include file.
envp
- The syntax was described previously, as a non-standard third
parameter to
main(). In most
implementations, the
initial
value of envp is the initial value of environ,
but environ reflects changes to the environment
made by
calls to putenv() or setenv(),
while envp
does
not. If you need to retain a reference to the initial
environment, envp would be useful, but this is
not a
common requirement.
#include <stdlib.h>
char *getenv(const char *name);
- This function will find the value of an environment
variable,
if the variable is defined in the current environment.
It returns
NULL (if not found) or a pointer to
the value string.
- The pointer returned by
getenv() could be
aimed
at a static
buffer associated with the getenv() function,
and a
subsequent call to getenv() might overwrite the
buffer.
int putenv(char *str);
- The string must have the form
"name=value".
The
function returns 0 if successful, or nonzero on error (and
then errno
is set to ENOMEM).
- if (name is in the environment list)
then replace (copy pointer only)
else add (copy pointer only)
- Because only the pointer to the string will be copied, and
not
the string itself, the string should not be stored on the
stack;
examples will follow.
- Note that adding to the environment list may require copying
it
to a larger area, and changing
environ.
Only the
environment list needs to be copied, not the environment
strings.
int setenv(const char *name, const char *value, int
rewrite);
- The function returns 0 if successful, or -1 on error (and
then
errno
is set to EINVAL or ENOMEM).
- if (name is in the environment list)
then
if (rewrite is 0)
then do not replace (this is not
an error)
else replace (allocate new memory for "name=value"
and copy the pointer to it)
else add (as above)
int unsetenv(const char *name);
- The function returns 0 if successful, or -1 on error (and
then
errno
is set to EINVAL).
- if (name is in the environment list)
then remove
else do nothing (this is not an error)
- Exercise. Implement "remove".
- Note that each of
putenv(), setenv()
and unsetenv() changes the environment in the
process in which it was called, but that change is not
propagated back
to the command shell that started the process.
- See APUE p. 194-195 for some design and implementation
details. This is a good example of memory-conscious
design.
- Can you differentiate between an environment variable that
came
from
(1) initialization (the environment string is on the stack),
(2) a call to putenv() (the environment string
was
provided by the caller of putenv()), or
(3) a call to setenv() (storage for the
environment
string was obtained from malloc() and can be
returned
with free())?
Portability Summary
- C standard library
- Posix, required in most recent version
- Posix, XSI option
- GNU extensions
- Previous practice, not recommended for new programs
Example.
testenv.c
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
/* Solaris printf("%s", NULL) generates a segmentation fault */
static inline char * safe(char *str)
{
return (str == NULL) ? "(null)" : str;
}
static inline void print_getenv(char *name)
{
char *val = safe(getenv(name)); // ignore errors
printf("%p %s %s %p\n", environ, name, val, val);
}
int main(void)
{
print_getenv("SHELL");
print_getenv("FOO");
print_getenv("SHELL");
putenv("FOO=BAR");
print_getenv("FOO");
setenv("THON", "DANCE", 1);
print_getenv("THON");
setenv("THON", "COLLAPSE", 0);
print_getenv("THON");
unsetenv("FOO");
print_getenv("FOO");
print_getenv("SHELL");
return 0;
}
Test the program, on Linux
% gcc testenv.c
% gcc -std=c99 testenv.c
testenv.c: In function ‘main’:
testenv.c:24: warning: implicit declaration of function ‘putenv’
testenv.c:26: warning: implicit declaration of function ‘setenv’
testenv.c:30: warning: implicit declaration of function
‘unsetenv’
% gcc -std=c99 -D_POSIX_C_SOURCE=200112L testenv.c
testenv.c: In function ‘main’:
testenv.c:24: warning: implicit declaration of function ‘putenv’
% gcc -std=c99 -D_POSIX_C_SOURCE=200809L testenv.c
testenv.c: In function ‘main’:
testenv.c:24: warning: implicit declaration of function ‘putenv’
% gcc -std=c99 -D_XOPEN_SOURCE=600 testenv.c
% gcc -std=c99 -D_XOPEN_SOURCE=700 testenv.c
% gcc -std=c99 -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600
testenv.c
% gcc -std=c99 -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700
testenv.c
% gcc testenv.c
% a.out
0x7fff5ec36688 SHELL /bin/tcsh 0x7fff5ec37485
0x7fff5ec36688 FOO (null) 0x400868
0x7fff5ec36688 SHELL /bin/tcsh 0x7fff5ec37485
0x101c010 FOO BAR 0x40088a
0x101c010 THON DANCE 0x101c195
0x101c010 THON DANCE 0x101c195
0x101c010 FOO (null) 0x400868
0x101c010 SHELL /bin/tcsh 0x7fff5ec37485
%
Test the program, on Solaris
% cc testenv.c
% c99 testenv.c
"testenv.c",
line
26: warning: implicit function declaration: putenv
"testenv.c", line 28: warning: implicit function
declaration: setenv
"testenv.c", line 32: warning: implicit function
declaration: unsetenv
% c99 -D_POSIX_C_SOURCE=200112L
testenv.c
% c99 -D_XOPEN_SOURCE=600 testenv.c
%
% cc testenv.c
% a.out
ffbffaec SHELL /bin/tcsh ffbffcfa
ffbffaec FOO (null) 20f88
ffbffaec SHELL /bin/tcsh ffbffcfa
2117c FOO BAR 20fa8
21178 THON DANCE 21201
21178 THON DANCE 21201
2117c FOO (null) 20f88
2117c SHELL /bin/tcsh ffbffcfa
%
Implementation of
Environment
Variables
- memory locations, strings and pointers
- starting a new process with
fork()
- loading a new program with
exec()
- memory leaks
Implementation
of
getenv()
- reentrant version (APUE Sec. 10.6, Sec. 12.5, Fig. 12.12)
- thread-safe version (APUE Sec. 12.5, Fig. 12.13, Exercise 12.4
and its solution)
- asynchronous-signal-safe version (APUE Exercise 12.3 and its
solution)
Exercise. APUE Fig.
12.11
defines an array of size ARG_MAX. Is this array
too
large? How would you obtain the value of ARG_MAX
for a particular system? [call sysconf()
or
run getconf]
Using getenv()
#include <stdlib.h>
// name is a string literal or a proper
C-string
char array
char *value = getenv(name);
if (value == NULL)
{ // name not found in the environment
}
else if (strcmp(value, "VALUE") == 0)
{ // this specific case
}
else
{ // every other case
}
Exercise.
What's
wrong here?
char *value = getenv(name);
switch (value) {
case NULL:
// name not found in the
environment
break;
case "VALUE":
// this specific case
break;
default:
// every other case
};
Using getenv()
The return value might be aimed at
- a read-only section of memory
- a single buffer whose contents are modified on each call
getenv() returns the same value on each call
- a dynamically-allocated buffer that might be reallocated on
the
next call
- a tightly packed set of character strings with no room for
expansion
Use the returned string before calling getenv() again.
Do not modify the returned string.
Exercise. Is it
necessary
to copy the returned string?
- If so, then use strdup(), which returns a value from
malloc().
Exercise.
What's
wrong
here?
char *value = getenv(name);
value = "new_value";
Exercise.
What's
wrong here?
char *value = getenv(name);
strcpy(value, "new_value");
Exercise. What's wrong
here?
char *value1 = getenv(name1);
char *value2 = getenv(name2);
if (strcmp(value1, value2) == 0)
{ // name1 and name2 have the same value
}
else
{ // name1 and name2 have different
values
}
Using putenv(), setenv()
#include <stdlib.h>
// str points to a string that looks like
"name=value"
if (putenv(str) != 0)
{ // failed, print a message
}
if
(setenv(name, value, 1) != 0)
{ // failed, print a message
}
Exercise. What's wrong
here? "I tried to change the environment variable FOO
by calling putenv() in my program, but when I tried to
verify the change by running printenv, FOO
had its old value. setenv() didn't work either."
Exercise. Is this
claim
correct? putenv("name=value") is equivalent to setenv("name",
"value", 1).
Exercise. What's wrong
here?
void my_setenv(char *name,
char
*value)
{
char str[strlen(name) + strlen(value) + 2];
char *p = str;
while (*name++ != NULL) *p = *name;
*p = '=';
while (*value++ != NULL) *p = *value;
*p = '\0';
putenv(str);
}
Exercise. What's wrong
here?
void my_setenv(char *name,
char
*value)
{
char str[strlen(name) + strlen(value) + 2];
int i;
for (i = 0; name[i] != '\0'; i++) str[i]
=
name[i];
str[i] = '=';
for (i = 0; value[i] !=
'\0';
i++) str[i] = value[i];
str[i] = '\0';
putenv(str);
}
Exercise. What's wrong
here? Same function but with
static char
str[strlen(name) + strlen(value) + 2];
Exercise. What's wrong
here? Same function but with
char *str =
malloc(strlen(name) +
strlen(value) + 2);
Example. testputenv.c
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
/* print_getenv() as before */
void hack(void)
{
char str[] = "A=B";
putenv(str);
print_getenv("A");
}
int main(void)
{
print_getenv("SHELL");
print_getenv("A");
hack();
print_getenv("A");
print_getenv("SHELL");
return 0;
}
Test the program, on Solaris
% a.out
ffbffaec SHELL /bin/tcsh ffbffcfa
ffbffaec A (null) 20f30
21104 A B ffbffa16
21104 A ú ffbffa16
21104 SHELL /bin/tcsh ffbffcfa
Exercise. Fix the testputenv.c
program by adding one keyword.
Using
environ
Exercise. Suppose (for
some bizarre reason) you want to retain a reference to previous
states
of the environment. How should you implement this?
Exercise. What's wrong
here? [A similar function is available on Linux]
void clearenv(void)
{
environ = NULL;
}
Exercise. What's wrong
here?
void clearenv(void)
{
environ[0] = NULL;
}
Demonstration Program.
Here are two versions of a simple demonstration program, environ.1.c and environ.2.c, with
a makefile.
The
program
prints
a few addresses associated with argc, argv,
envp and environ, as seen by main(),
before
and
after
calls to putenv(), setenv()
and unsetenv(). The second version is also a
good
exercise in "pointer swilling".
Debugging Exercise.
We wanted to demonstrate the dangers of bypassing putenv()
and setenv() to change the value of an environment
variable. The general idea is this:
putenv("A=B");
char *a = getenv("A");
strcpy(a, "C");
There are two obvious things that can go wrong: the new value is too
long and overflows the string space of the old value, or, the new
value
is too short and some extra bytes remain from the old value.
We
didn't expect a Segmentation Fault.
Here are two versions of the test program, environ.3.c
and environ.4.c;
use
the
same makefile as above.
Exercise. Design,
implement and test a set of functions to manipulate an environment
array. This would be useful in a command shell, where
you
need to construct an environment for a child process, and then use fork()
and execve() to create and start the child.
Explain why.
The C Standard says, "The implementation shall behave as if no
library function calls the getenv function."
The Posix Standard says, under getenv(), "The
application
shall ensure that it does not modify the string pointed to by the getenv()
function. The string pointed to may be
overwritten by a subsequent call to getenv(), setenv(),
unsetenv(), or putenv() but shall not be
overwritten by a call to any other function in this volume of
POSIX.1-2008. If the application modifies environ
or the pointers to which it points, the behavior of getenv()
is undefined."
Last revised, 15 Feb. 2013