CMPSC 311, Introduction to Systems Programming

Environment Variables, Part 2



Reading
References
Related topics
Topics that sound like they ought to be related, but they aren't


Program Interfaces



Portability Summary
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
Implementation of getenv()
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
Use the returned string before calling getenv() again.
Do not modify the returned string.

Exercise.  Is it necessary to copy the returned string?
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