6.7 Environment List

6.7 Environment List
Linux System Programming — Chapter 6
5
Key Functions
3
Code Examples
6
Interview Questions

What You Will Learn

Every process has an environment list — a set of NAME=value string pairs inherited from its parent. Environment variables are a simple, widely-used mechanism for passing configuration from shell to program, and from parent process to child process.

What is the Environment List?

The environment is a NULL-terminated array of strings of the form NAME=value. It is accessible in C through the global variable extern char **environ.

environ Data Structure in Memory
environ
(char** global)
ptr[0] →
ptr[1] →
ptr[2] →
ptr[3] →
NULL
String storage (above stack)
LOGNAME=ravi\0
SHELL=/bin/bash\0
HOME=/home/ravi\0
PATH=/usr/bin:/bin\0
Inheritance: When a child process is created with fork(), it gets a copy of the parent’s environment. Changes in the child do not affect the parent and vice versa — it is a one-way, one-time transfer.

Environment Functions — Quick Reference
Function Purpose Returns
getenv(“NAME”) Read value of environment variable char* to value, or NULL if not set
setenv(“N”,”V”,1) Add or update a variable (copies strings) 0 on success, -1 on error
putenv(“N=V”) Add or update (uses the string directly — no copy) 0 on success, nonzero on error
unsetenv(“NAME”) Remove a variable from the environment 0 on success, -1 on error
clearenv() Erase entire environment (sets environ = NULL) 0 on success, nonzero on error
setenv vs putenv: setenv() copies the name and value strings internally — safe to pass stack variables. putenv() stores the pointer you give it — passing a local char[] buffer is dangerous because the buffer disappears when the function returns.

Code Examples

Example 1: Read and Print Environment Variables
#include <stdio.h>
#include <stdlib.h>

extern char **environ;   /* global — points to env list */

int main(void)
{
    /* Method 1: getenv — read a specific variable */
    char *home = getenv("HOME");
    char *path = getenv("PATH");
    char *missing = getenv("DOES_NOT_EXIST");

    printf("HOME   = %s\n", home  ? home  : "(not set)");
    printf("PATH   = %s\n", path  ? path  : "(not set)");
    printf("MISSING = %s\n", missing ? missing : "(not set)");

    /* Method 2: iterate environ — print all variables */
    printf("\n--- Full environment ---\n");
    for (char **ep = environ; *ep != NULL; ep++) {
        printf("  %s\n", *ep);
    }
    return 0;
}

/* Partial output:
   HOME   = /home/ravi
   PATH   = /usr/local/bin:/usr/bin:/bin
   MISSING = (not set)

   --- Full environment ---
     LOGNAME=ravi
     SHELL=/bin/bash
     HOME=/home/ravi
     PATH=/usr/local/bin:/usr/bin:/bin
     TERM=xterm-256color
     ...  */
Example 2: Set, Update, and Unset Environment Variables
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    /* Set a new variable (overwrite=0 means don't replace if exists) */
    setenv("APP_MODE", "development", 0);
    printf("APP_MODE = %s\n", getenv("APP_MODE"));

    /* Set again with overwrite=1 — this time it replaces */
    setenv("APP_MODE", "production", 1);
    printf("APP_MODE = %s (updated)\n", getenv("APP_MODE"));

    /* putenv — pointer is stored directly, no copy */
    /* string must remain valid for the lifetime of the env entry */
    static char db_url[] = "DB_URL=postgres://localhost/mydb";
    putenv(db_url);
    printf("DB_URL   = %s\n", getenv("DB_URL"));

    /* Remove a variable */
    unsetenv("DB_URL");
    printf("DB_URL after unset = %s\n",
           getenv("DB_URL") ? getenv("DB_URL") : "NULL (removed)");

    return 0;
}

/* Output:
   APP_MODE = development
   APP_MODE = production (updated)
   DB_URL   = postgres://localhost/mydb
   DB_URL after unset = NULL (removed) */
Example 3: Child Inherits Parent’s Environment
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    /* Parent sets a custom environment variable */
    setenv("MY_CONFIG", "value_from_parent", 1);
    printf("[Parent] Set MY_CONFIG = %s\n", getenv("MY_CONFIG"));

    pid_t pid = fork();

    if (pid == 0) {
        /* Child inherits the environment AT TIME OF FORK */
        printf("[Child]  MY_CONFIG = %s (inherited)\n",
               getenv("MY_CONFIG"));

        /* Child changes it — parent is NOT affected */
        setenv("MY_CONFIG", "value_from_child", 1);
        printf("[Child]  MY_CONFIG changed to: %s\n",
               getenv("MY_CONFIG"));
        exit(0);
    }

    wait(NULL);   /* Wait for child */

    /* Parent's copy is unchanged */
    printf("[Parent] MY_CONFIG still = %s (child change didn't affect us)\n",
           getenv("MY_CONFIG"));

    return 0;
}

/* Output:
   [Parent] Set MY_CONFIG = value_from_parent
   [Child]  MY_CONFIG = value_from_parent  (inherited copy)
   [Child]  MY_CONFIG changed to: value_from_child
   [Parent] MY_CONFIG still = value_from_parent */

Interview Preparation Questions

Q1. How does a child process receive environment variables from its parent?

When fork() is called, the child gets a copy of the parent’s entire environment list. This is a one-time, one-way transfer at the moment of fork. After that, changes in either process’s environment are independent and invisible to the other.

Q2. What is the difference between setenv() and putenv()?

setenv() copies the name and value strings into newly allocated memory — safe to pass stack variables. putenv() stores the exact pointer you provide directly in the environment — you must ensure the string stays valid (use heap or static storage). Never pass a local char[] to putenv().

Q3. What does getenv() return if the variable is not set?

NULL. Always check the return value of getenv() before using it to avoid a null pointer dereference: `char *val = getenv(“X”); if (val != NULL) { … }`.

Q4. Should you modify the string returned by getenv()?

No. SUSv3 says you must not modify it — in most implementations it points directly into the environment storage. If you need a modified copy, use strdup() to duplicate it first, then modify the copy.

Q5. How do you clear the entire environment in C? What is the risk?

Use clearenv() or assign NULL to environ directly. Risk: clearenv() does not free memory previously allocated by setenv() — repeated clearenv()+setenv() cycles in a loop will leak memory. In practice it’s usually called once at startup.

Q6. Where in virtual memory is the environment list stored?

The initial environment strings and the argv strings share a single contiguous region just above the initial stack in the process’s virtual address space. They are placed there by the kernel via execve() before the process starts executing.

Leave a Reply

Your email address will not be published. Required fields are marked *