Cuando un proceso se inicia, recibe un bloque de memoria llamado entorno, una serie de pares clave-valor (como PATH=/usr/bin) que definen su contexto. Para interactuar con este bloque, el estándar C nos proporciona getenv, que busca una cadena en dicho entorno y nos devuelve un puntero (const char*) directamente a su ubicación en memoria. Esta eficiencia es clave: getenv no copia la cadena, simplemente te da la dirección de donde ya reside. Sin embargo, esta misma optimización es peligrosa: si modificas el entorno con setenv [POSIX] o putenv [POSIX], el puntero que obtuviste previamente con getenv podría quedar invalidado, apuntando a memoria que ha sido reubicada o liberada.
Por otro lado, system es una abstracción de alto nivel que delega la ejecución de un comando al shell del sistema (normalmente /bin/sh). Internamente, system realiza un fork, un exec, y un waitpid. Es una herramienta extremadamente útil para automatizar tareas rápidas, pero es costosa en términos de rendimiento y, lo más importante, es una fuente crítica de inyección de comandos. Si concatenas una cadena que contiene datos provenientes de un usuario directamente en un argumento para system, un atacante podría inyectar delimitadores como ;, && o | para ejecutar código arbitrario con los privilegios de tu proceso. Para control total y seguridad, en sistemas productivos preferimos usar la familia fork/exec [POSIX], evitando invocar un shell intermedio.
Para usar system de forma segura, debes tratar el valor de retorno con cuidado. No devuelve simplemente el código de salida del comando, sino un estado codificado que requiere macros específicas para ser interpretado.
#define _POSIX_C_SOURCE=200809L
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(void) {
// 1. Verificación de disponibilidad del shell
if (system(NULL) == 0) {
fprintf(stderr, "Error: No hay un shell disponible en este sistema.\n");
return EXIT_FAILURE;
}
// 2. Uso de getenv para leer configuración
// El retorno es const char* porque no debemos modificar el entorno directamente.
const char *path_val = getenv("PATH");
if (path_val != NULL) {
printf("PATH detectado: %.50s...\n", path_val);
}
// 3. Uso de setenv [POSIX] para modificar el entorno
// setenv es preferible a putenv porque gestiona la memoria internamente.
if (setenv("APP_MODE", "production", 1) != 0) {
perror("setenv");
return EXIT_FAILURE;
}
printf("Modo actual: %s\n", getenv("APP_MODE"));
// 4. Ejecución de un comando externo vía system
// Ejecutamos un comando simple para verificar el estado.
printf("Ejecutando comando externo...\n");
int status = system("echo 'Operación completada desde el shell'");
if (status == -1) {
perror("system");
return EXIT_FAILURE;
}
// El valor de retorno de system es un entero codificado.
// WIFEXITED comprueba si el proceso terminó normalmente.
// WEXITSTATUS extrae el código de salida real.
if (WIFEXITED(status)) {
int exit_code = WEXITSTATUS(status);
printf("El comando terminó con código: %d\n", exit_code);
} else {
printf("El comando falló o fue interrumpido.\n");
}
return EXIT_SUCCESS;
}
En el código anterior, empezamos validando la existencia de un shell mediante system(NULL), una técnica común para asegurar que las llamadas posteriores no fallen silenciosamente. Al llamar a getenv("PATH"), obtenemos un puntero constante; fíjate en la cast de printf, usamos %.50s solo para evitar inundar la terminal, pero el puntero es el original del entorno.
Cuando llamamos a setenv("APP_MODE", "production", 1), el sistema reserva memoria para la nueva clave y el valor, lo cual es mucho más seguro que putenv. Tras la llamada, getenv("APP_MODE") puede leer correctamente el valor. Finalmente, al ejecutar system("echo ..."), el valor devuelto en status no es simplemente 0. Es un entero que contiene información sobre si el proceso hijo murió por una señal o si terminó con éxito. Por eso es imperativo usar las macros WIFEXITED y WEXITSTATUS para interpretar ese bit de información de manera correcta según el estándar POSIX.
El error frecuente
Un error clásico al trabajar con putenv es pasarle un puntero a una variable local (en el stack). A diferencia de setenv, putenv no duplica la cadena; simplemente inserta el puntero tal cual en la lista de variables de entorno.
void error_example(void) {
// ¡PELIGRO! La variable 'buffer' vive solo en el stack de esta función.
char buffer[32] = "config_path=/tmp";
// putenv toma el puntero a 'buffer' y lo mete al entorno global.
putenv(buffer);
// Al salir de esta función, 'buffer' es memoria inválida (stack frame destruido).
// Pero el entorno del proceso sigue apuntando a esa dirección de memoria.
}
Si intentas llamar a getenv después de que esa función haya retornado, estarás leyendo basura o provocando un segmentation fault. Además, si otra función modifica el stack, el entorno estará corrupto. AddressSanitizer detectaría esto como un uso de memoria inválida (use-after-return). Para evitarlo, usa siempre setenv en aplicaciones modernas, ya que esta función realiza una copia segura de los datos en el heap.
N° 87