Programas en C del profesor (procesos: system(), fork(), familia exec()). Archivos reales en files/prof-tema1/. Compilar: cc programaXX_nombre.c -o programaXX · ejecutar: ./programaXX. Los PID y listados de ls varían en cada corrida.
programa04_system_v1.c — Ejemplo mínimo de system("ls -l /"): el shell ejecuta el comando y return_value recibe el estado devuelto por el shell (0 si todo salió bien).programa04_system_v2.c — Igual que el anterior pero además interpreta el valor devuelto con WIFEXITED / WEXITSTATUS (tema de wait(), se ve más adelante) y repite con un comando que falla (ls -l no_existe).programa05_fork_v1.c — Primer contacto con fork(): después de la llamada hay dos procesos ejecutando el mismo código, por eso los printf 2 y 3 salen duplicados. id_hijo vale 0 en el hijo y el PID del hijo en el padre.programa05_fork_v2.c — El if (id_hijo != 0) separa el código que ejecuta el padre del que ejecuta el hijo. El orden en que aparecen los bloques PADRE/HIJO no está garantizado (condición de carrera).programa08_child.c — fork() con manejo de error: si devuelve -1 hace perror("fork") y exit. El hijo imprime in child y sus pid/ppid y termina con exit; el padre imprime in parent.programa03_codigos_sistema.c — Recorre strerror(0..150) e imprime el mensaje de cada código de error de Linux. Es la versión «en C» de la tabla de los 131 códigos.programa06_error.c — Provoca un error real: rename("antes.txt","despues.txt") cuando antes.txt no existe. Muestra errno, strerror(errno), perror() y un switch que traduce el tipo de error.programa07_error.c — Versión mínima del anterior: solo errno + perror(). Sirve para ver que perror("texto") imprime texto: <mensaje del errno actual>.programa09 — No es C, es un script de shell. Lista las macros de error (EPERM, ENOENT, …) con su número, leyéndolas de /usr/include/asm-generic/errno*.h. Solo funciona en Linux.programa10_execl.c — execl(path, arg0, …, NULL): lista de argumentos y ruta absoluta. A propósito llama a /bin/lsa (no existe) para mostrar el caso de error: execl devuelve -1. Si tuviera éxito, el proceso se reemplaza y las líneas siguientes nunca se ejecutan.programa11_execv.c — execv(path, argv[]): igual que execl pero los argumentos van en un arreglo. Aquí sí usa /bin/ls, así que reemplaza el proceso y muestra el listado; la última línea no se imprime.programa18_execlp.c — execlp(file, …): como execl pero recibe el nombre del comando (ls, no /bin/ls) y lo busca en $PATH — de ahí la «p».programa19_execvp.c — execvp(file, argv[]): versión con arreglo de argumentos de execlp. Mismo resultado.programa12_getenv_user1.c — Programa «hijo» que invocan los ejemplos de exec: imprime su PID y el valor de la variable de entorno USER con getenv.programa13_getenv_user2.c — Igual, pero también lee MI_VARIABLE. Si no está definida, getenv devuelve NULL y se imprime (null).programa14_execle1.c — execle(path, …, NULL, envp[]): lista de argumentos más un entorno propio (envp) que solo vale para el programa ejecutado. El getenv("USER") de antes del execle todavía muestra tu USER real.programa15_execle2.c — execle invocando a nuestro programa12; el envp le pasa USER=jose solo para esa ejecución. El PID no cambia: exec reemplaza el programa, no crea proceso nuevo.programa16_execle3.c — Igual que el anterior con dos variables (USER, MI_VARIABLE) e invocando programa13.programa17_execve1.c — execve(path, argv[], envp[]): equivale a execle pero todo en arreglos (argumentos y entorno). Es la llamada «base» del sistema; las demás exec* son envoltorios de ésta.programa20_myecho.c — Programa auxiliar: imprime argv[0..argc-1], uno por línea. Sirve para ver exactamente qué argumentos recibe un programa lanzado con exec.programa21_execve.c — Recibe un ejecutable en argv[1] y lo lanza con execve, pasándole newargv (con newargv[0] = argv[1]) y entorno NULL. Ejercicio: explicar qué hace newargv[0] = argv[1] y modificar el bloque final para que, si execve falla, el printf no se ejecute.programa22_fork-exec.c — El patrón completo con trazas: spawn() hace fork(); el hijo llama execvp("./programa01_print-pid") (se reemplaza), el padre regresa de spawn y sigue. El orden exacto de las líneas varía.programa23_fork-exec.c — Versión limpia del patrón (sin trazas): el hijo ejecuta ls -l /, el padre imprime Termina el proceso padre. El diagrama programa23_fork-exec.png ilustra este flujo.programa01_print-pid.c — Imprime el PID del proceso (getpid) y el PID de su padre (getppid).programa02_ids.c — Imprime los IDs reales y efectivos de usuario y grupo. En un ejecutable con el bit setuid, el UID efectivo sería el del dueño del archivo, no el tuyo.programa04_system_v1.cEjemplo mínimo de system("ls -l /"): el shell ejecuta el comando y return_value recibe el estado devuelto por el shell (0 si todo salió bien).
#include <stdio.h>
#include <stdlib.h>
int main ()
{
int return_value;
printf("Ejemplo de funcion system(\"ls -l /\") con un comando que se ejecuta correctamente\n");
return_value = system ("ls -l /");
printf("Termina proceso hijo, return_value=%d\n", return_value);
return return_value;
}
Salida típica
Ejemplo de funcion system("ls -l /") con un comando que se ejecuta correctamente
<listado del directorio raíz>
Termina proceso hijo, return_value=0
programa04_system_v2.cIgual que el anterior pero además interpreta el valor devuelto con WIFEXITED / WEXITSTATUS (tema de wait(), se ve más adelante) y repite con un comando que falla (ls -l no_existe).
#include <stdio.h>
#include <stdlib.h>
int main ()
{
int return_value;
printf("Ejemplo 2 de funcion system(\"ls -l /\") con un comando que se ejecuta correctamente\n");
return_value = system ("ls -l /");
printf("Termina proceso hijo, return_value=%d\n", return_value);
printf("Lo siguiente tiene que ver con la funcion wait(), lo veremos mas adelante\n");
printf("En el proceso inicial, WIFEXITED(return_value)=%d\n",WIFEXITED(return_value) );
printf("En el proceso inicial, WEXITSTATUS(return_value) codigo de salida del hijo=%d\n",WEXITSTATUS(return_value) );
printf("---------------------------------------\n");
printf("Ejemplo 2 de funcion system(\"ls -l no_existe\") con un comando que se ejecuta incorrectamente\n");
return_value = system ("ls -l no_existe");
printf("Termina proceso hijo, return_value=%d\n", return_value);
printf("Lo siguiente tiene que ver con la funcion wait(), lo veremos mas adelante\n");
printf("En el proceso inicial, WIFEXITED(return_value)=%d\n",WIFEXITED(return_value) );
printf("En el proceso inicial, WEXITSTATUS(return_value) codigo de salida del hijo=%d\n",WEXITSTATUS(return_value) );
return return_value;
}
Salida típica
Ejemplo 2 ... comando correcto
<listado del raíz>
Termina proceso hijo, return_value=0
WIFEXITED(return_value)=1
WEXITSTATUS(...) codigo de salida del hijo=0
---------------------------------------
Ejemplo 2 ... "ls -l no_existe"
ls: cannot access 'no_existe': No such file or directory
Termina proceso hijo, return_value=512
WIFEXITED(return_value)=1
WEXITSTATUS(...)=2
programa05_fork_v1.cPrimer contacto con fork(): después de la llamada hay dos procesos ejecutando el mismo código, por eso los printf 2 y 3 salen duplicados. id_hijo vale 0 en el hijo y el PID del hijo en el padre.
#include <stdio.h>
#include <unistd.h>
int main ()
{
pid_t id_hijo;
printf( "1: el id del proceso main es %d\n", getpid() );
id_hijo = fork ();
printf( "2: Este printf saldra duplicado\n");
printf( "3: La variable \"id_hijo\" de este proceso es %d. La pregunta es: Quien soy, Padre o Hijo?\n", id_hijo );
return 0;
}
Salida típica
1: el id del proceso main es 12345
2: Este printf saldra duplicado
3: La variable "id_hijo" de este proceso es 12346. ...
2: Este printf saldra duplicado
3: La variable "id_hijo" de este proceso es 0. ...
programa05_fork_v2.cEl if (id_hijo != 0) separa el código que ejecuta el padre del que ejecuta el hijo. El orden en que aparecen los bloques PADRE/HIJO no está garantizado (condición de carrera).
#include <stdio.h>
#include <unistd.h>
int main ()
{
pid_t id_hijo;
printf( "1: el id del proceso main es %d\n", getpid() );
id_hijo = fork ();
printf( "2: Este printf saldra duplicado\n");
printf( "3: Este printf tambien sale por duplicado. La variable id_hijo es %d\n", id_hijo );
if( id_hijo != 0 )
{
printf( "\nPADRE:proceso main o padre con id %d\n", getpid());
printf( "PADRE:variable id_hijo es %d\n", id_hijo );
printf( "PADRE:id del padre del padre %d\n",getppid());
}
else
{
printf( "\nHIJO:proceso hijo con id %d\n", getpid() );
printf( "HIJO:variable id_hijo es %d\n", id_hijo );
printf( "HIJO:id del padre es %d\n", getppid() );
}
return 0;
}
Salida típica
1: el id del proceso main es 12345
2: Este printf saldra duplicado
3: ... La variable id_hijo es 12346
PADRE:proceso main o padre con id 12345
PADRE:variable id_hijo es 12346
PADRE:id del padre del padre 6789
2: Este printf saldra duplicado
3: ... La variable id_hijo es 0
HIJO:proceso hijo con id 12346
HIJO:variable id_hijo es 0
HIJO:id del padre es 12345
programa08_child.cfork() con manejo de error: si devuelve -1 hace perror("fork") y exit. El hijo imprime in child y sus pid/ppid y termina con exit; el padre imprime in parent.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
pid_t child;
if((child = fork()) == -1)
{
perror("fork");
exit(EXIT_FAILURE);
}
else if(child == 0)
{
puts("in child");
printf("\tchild pid = %d\n", getpid());
printf("\tchild ppid = %d\n", getppid());
exit(EXIT_SUCCESS);
}
else
{
puts("in parent");
printf("\tparent pid = %d\n", getpid());
printf("\tparent ppid = %d\n", getppid());
}
exit(EXIT_SUCCESS);
}
Salida típica
in parent
parent pid = 12345
parent ppid = 6789
in child
child pid = 12346
child ppid = 12345
programa03_codigos_sistema.cRecorre strerror(0..150) e imprime el mensaje de cada código de error de Linux. Es la versión «en C» de la tabla de los 131 códigos.
/* Manejo de codigos de sistema */
#include <stdio.h>
#include <string.h>
int main()
{
int i;
for(i=0; i <= 150; i++)
{
printf( "%d: %s\n", i , strerror( i ) );
}
return 0;
}
Salida típica
0: Success
1: Operation not permitted
2: No such file or directory
3: No such process
4: Interrupted system call
...
11: Resource temporarily unavailable
12: Cannot allocate memory
13: Permission denied
...
(el mensaje de los códigos sin definir aparece como "Unknown error N")
programa06_error.cProvoca un error real: rename("antes.txt","despues.txt") cuando antes.txt no existe. Muestra errno, strerror(errno), perror() y un switch que traduce el tipo de error.
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
int result;
result = rename("antes.txt","despues.txt");
if( result != 0 )
{
printf("Error al renombrar el archivo\n");
printf("errno:%d\n", errno);
printf( "strerror(errno):%s\n", strerror( errno ) );
perror( "el mensaje de error es" );
switch(errno)
{
case EPERM:
printf("Operation not permitted\n");
break;
case ENOENT:
printf("File not found\n");
break;
case EACCES:
printf("Permission denied\n");
break;
case ENAMETOOLONG:
printf("Filename is too long\n");
break;
default:
printf("Otro error\n");
}
return 1;
}
printf("Archivo renombrado\n");
return 0;
}
Salida típica
Error al renombrar el archivo
errno:2
strerror(errno):No such file or directory
el mensaje de error es: No such file or directory
File not found
programa07_error.cVersión mínima del anterior: solo errno + perror(). Sirve para ver que perror("texto") imprime texto: <mensaje del errno actual>.
#include <stdio.h>
#include <errno.h>
// Esta version es parecida a programa06_error
// la diferencia es que esta version
// llama a la funcion perror( string )
// observen la linea de perror( "El error es" )
// y que imprime en pantalla
int main()
{
int result;
result = rename("antes.txt","despues.txt");
if( result != 0 )
{
printf("errno:%d\n", errno);
perror( "El error es" );
return 1;
}
printf("Archivo renombrado");
return 0;
}
Salida típica
errno:2
El error es: No such file or directory
programa09No es C, es un script de shell. Lista las macros de error (EPERM, ENOENT, …) con su número, leyéndolas de /usr/include/asm-generic/errno*.h. Solo funciona en Linux.
grep -hPo '#define\s+(E[A-Z0-9_]+)\s+(\d+)' /usr/include/asm-generic/errno-base.h /usr/include/asm-generic/errno.h | awk '{printf "%-20s %s\n", $2, $3}' | sort -n -k2
Salida típica
EPERM 1
ENOENT 2
ESRCH 3
EINTR 4
EIO 5
...
programa10_execl.cexecl(path, arg0, …, NULL): lista de argumentos y ruta absoluta. A propósito llama a /bin/lsa (no existe) para mostrar el caso de error: execl devuelve -1. Si tuviera éxito, el proceso se reemplaza y las líneas siguientes nunca se ejecutan.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// Este ejemplo muestra la funcion execl con el comando ls -l /
int main(void)
{
printf("En el proceso inicial, antes de llamar a execl\n");
// como es execl entonces es una lista
// la sintaxis es:
// int execl (char *path, char *arg0,... char *argn, (char *)0);
// path es la ruta incluyendo el nombre del comando/ejecutable
// En C el nombre del comando/ejecutable es el argumento 0
// por eso se repite
// con NULL se indica que se llego al final de la lista de argumentos
// Si execl falla devuelve -1 y entra al if por lo que termina
// con exit(EXIT_FAILURE)
if( execl("/bin/lsa", "/bin/lsa", "-l", "/", NULL) == -1)
{
printf("execl genero error y devolvio -1\n");
perror("execl");
exit(EXIT_FAILURE);
}
// Por lo tanto, sea exito o error, las sig 2 sentencias
// JAMAS se ejecutan
printf("Si execl es exito o error, esto no se imprime\n");
exit(EXIT_SUCCESS);
}
Salida típica
En el proceso inicial, antes de llamar a execl
execl genero error y devolvio -1
execl: No such file or directory
programa11_execv.cexecv(path, argv[]): igual que execl pero los argumentos van en un arreglo. Aquí sí usa /bin/ls, así que reemplaza el proceso y muestra el listado; la última línea no se imprime.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// Este ejemplo muestra la funcion execv con el comando ls -l /
int main(void)
{
printf("En el proceso inicial, antes de llamar a execv\n");
// como es execv entonces es un vector/arreglo
// el cual debe declararse previo a la llamada a execv
// la sintaxis es:
// int execv (char *path, char *argv[ ]);
// path es la ruta incluyendo el nombre del comando/ejecutable
// En C el nombre del comando/ejecutable es el argumento 0
// por eso se repite
// con NULL se indica que se llego al final del vector de argumentos
// Si execv falla devuelve -1 y entra al if por lo que termina
// con exit(EXIT_FAILURE)
char *args[]={"/bin/ls","-l","/",NULL};
if( execv("/bin/ls", args) == -1)
{
printf("execv genero error y devolvio -1\n");
perror("execv");
exit(EXIT_FAILURE);
}
// Por lo tanto, sea exito o error, las sig 2 sentencias
// JAMAS se ejecutan
printf("Si execv es exito o error, esto no se imprime\n");
exit(EXIT_SUCCESS);
}
Salida típica
En el proceso inicial, antes de llamar a execv
<listado del directorio raíz>
programa18_execlp.cexeclp(file, …): como execl pero recibe el nombre del comando (ls, no /bin/ls) y lo busca en $PATH — de ahí la «p».
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// Este ejemplo muestra la funcion execlp con el comando ls -l /
int main(void)
{
// como es execlp entonces es una lista
// la sintaxis es:
// int execlp (char *file, char *arg0,... char *argn, (char *)0);
// file es el nombre del comando/ejecutable sin ruta absoluta
// porque toma el directorio de la variable PATH, de ahi la "p" de execlp
// En C el nombre del comando/ejecutable es el argumento 0
// por eso se repite
// con NULL se indica que se llego al final de la lista de argumentos
// OBSERVEN QUE execlp() NO NECESITA LA RUTA ABSOLUTA DEL COMANDO O EJECUTABLE
// SOLO PONEMOS "ls" NO ES NECESARIO PONER "/bin/ls"
if( execlp("ls", "ls", "-l", "/", NULL) == -1)
{
printf("execlp genero error y devolvio -1\n");
perror("execlp");
exit(EXIT_FAILURE);
}
printf("Si execlp es exitoso, esto no se imprime\n");
exit(EXIT_SUCCESS);
}
Salida típica
<listado del directorio raíz>
programa19_execvp.cexecvp(file, argv[]): versión con arreglo de argumentos de execlp. Mismo resultado.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// Este ejemplo muestra la funcion execvp con el comando ls -l /
int main(void)
{
// como es execvp entonces es un vector/arreglo
// el cual debe declararse previo a la llamada a execvp
// la sintaxis es:
// int execvp (char *file, char *argv[ ]);
// file es el nombre del comando/ejecutable, sin ruta
// ya que la ruta la toma de la variable PATH, de ahi la "p" en el nombre
// En C el nombre del comando/ejecutable es el argumento 0
// por eso se repite
// con NULL se indica que se llego al final del vector de argumentos
// OBSERVEN QUE execvp() NO NECESITA LA RUTA ABSOLUTA DEL COMANDO O EJECUTABLE
// SOLO PONEMOS "ls" NO ES NECESARIO PONER "/bin/ls"
char *args[]={"ls","-l","/",NULL};
if( execvp("ls", args) == -1)
{
printf("execvp genero error y devolvio -1\n");
perror("execvp");
exit(EXIT_FAILURE);
}
printf("Si execvp es exitoso, esto no se imprime\n");
exit(EXIT_SUCCESS);
}
Salida típica
<listado del directorio raíz>
programa12_getenv_user1.cPrograma «hijo» que invocan los ejemplos de exec: imprime su PID y el valor de la variable de entorno USER con getenv.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf( "Entrando al programa llamado por la familia exec, mi pid es %d\n", getpid() );
printf( "el valor de la variable de entorno USER es: %s\n", getenv("USER") );
return 0;
}
Salida típica
Entrando al programa llamado por la familia exec, mi pid es 12345
el valor de la variable de entorno USER es: jose
programa13_getenv_user2.cIgual, pero también lee MI_VARIABLE. Si no está definida, getenv devuelve NULL y se imprime (null).
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf( "Entrando al programa llamado por la familia exec, mi pid es %d\n", getpid() );
printf( "el valor de la variable de entorno USER es: %s\n", getenv("USER") );
printf( "el valor de la variable de entorno MI_VARIABLE es: %s\n", getenv("MI_VARIABLE") );
return 0;
}
Salida típica
Entrando al programa llamado por la familia exec, mi pid es 12345
el valor de la variable de entorno USER es: jose
el valor de la variable de entorno MI_VARIABLE es: acs
programa14_execle1.cexecle(path, …, NULL, envp[]): lista de argumentos más un entorno propio (envp) que solo vale para el programa ejecutado. El getenv("USER") de antes del execle todavía muestra tu USER real.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// Este ejemplo muestra la funcion execle con el comando ls -l /
int main(void)
{
printf("En el proceso inicial, antes de llamar a execle\n");
// como es execle entonces es una lista
// y ademas puede leer variables de entorno que vienen en un arreglo
// y que solo son validas para la ejecucion del archivo dentro de la funcion execle
// la sintaxis es:
// int execle(char *path, char *arg0,... char*argn, (char *)0, char *envp[ ]);
// path es la ruta incluyendo el nombre del comando/ejecutable
// En C el nombre del comando/ejecutable es el argumento 0
// por eso se repite
// con NULL se indica que se llego al final de la lista de argumentos
char *variables_entorno[] = { "PATH=/bin:/usr/bin", "USER=jose", NULL };
printf( "el valor de la variable de entorno USER es: %s\n", getenv("USER") );
if( execle("/bin/ls", "/bin/ls", "-l", "/", NULL, variables_entorno) == -1)
{
printf("execle genero error y devolvio -1\n");
perror("execle");
exit(EXIT_FAILURE);
}
// Por lo tanto, sea exito o error, las sig 2 sentencias
// JAMAS se ejecutan
printf("Si execle es exito o error, esto no se imprime\n");
exit(EXIT_SUCCESS);
}
Salida típica
En el proceso inicial, antes de llamar a execle
el valor de la variable de entorno USER es: <tu-usuario>
<listado del directorio raíz, con PATH y USER personalizados solo para ese ls>
programa15_execle2.cexecle invocando a nuestro programa12; el envp le pasa USER=jose solo para esa ejecución. El PID no cambia: exec reemplaza el programa, no crea proceso nuevo.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// Este ejemplo muestra la funcion execle
// invocando a un programa nuestro getenv_user
// para leer la variable de entorno USER
int main(void)
{
printf("En el proceso inicial pid=%d, antes de llamar a execle\n", getpid() );
// como es execle entonces es una lista
// y ademas puede leer variables de entorno que vienen en un arreglo
// y que solo son validas para esta ejecucion
// la sintaxis es:
// int execle(char *path, char *arg0,... char*argn, (char *)0, char *envp[ ]);
// path es la ruta incluyendo el nombre del comando/ejecutable
// En C el nombre del comando/ejecutable es el argumento 0
// por eso se repite
// con NULL se indica que se llego al final de la lista de argumentos
char *variables_entorno[] = { "PATH=/bin:/usr/bin", "USER=jose", NULL };
printf( "el valor de la variable de entorno USER es: %s\n", getenv("USER") );
printf( "ahora ejecutando ./programa12_getenv_user1, el valor de la variable de entorno USER va a cambiar solo para esta ejecucion\n" );
if( execle("./programa12_getenv_user1", "./programa12_getenv_user1", NULL, variables_entorno) == -1)
{
printf("execle genero error y devolvio -1\n");
perror("execle");
exit(EXIT_FAILURE);
}
// Por lo tanto, sea exito o error, las sig 2 sentencias
// JAMAS se ejecutan
printf("Si execle es exito o error, esto no se imprime\n");
exit(EXIT_SUCCESS);
}
Salida típica
En el proceso inicial pid=12345, antes de llamar a execle
el valor de la variable de entorno USER es: <tu-usuario>
ahora ejecutando ./programa12_getenv_user1 ...
Entrando al programa llamado por la familia exec, mi pid es 12345
el valor de la variable de entorno USER es: jose
programa16_execle3.cIgual que el anterior con dos variables (USER, MI_VARIABLE) e invocando programa13.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// Este ejemplo muestra la funcion execle
// invocando a un programa nuestro getenv_user
// para leer la variable de entorno USER
int main(void)
{
printf("En el proceso inicial pid=%d, antes de llamar a execle\n", getpid() );
// como es execle entonces es una lista
// y ademas puede leer variables de entorno que vienen en un arreglo
// y que solo son validas para esta ejecucion
// la sintaxis es:
// int execle(char *path, char *arg0,... char*argn, (char *)0, char *envp[ ]);
// path es la ruta incluyendo el nombre del comando/ejecutable
// En C el nombre del comando/ejecutable es el argumento 0
// por eso se repite
// con NULL se indica que se llego al final de la lista de argumentos
char *variables_entorno[] = { "PATH=/bin:/usr/bin", "USER=jose", "MI_VARIABLE=acs", NULL };
printf( "el valor de la variable de entorno USER es: %s\n", getenv("USER") );
printf( "el valor de la variable de entorno MI_VARIABLE es: %s\n", getenv("MI_VARIABLE") );
printf( "ahora ejecutando ./programa13_getenv_user2, los valores de las variables de entorno USER y MI_VARIABLE van a cambiar solo para esta ejecucion\n" );
if( execle("./programa13_getenv_user2", "./programa13_getenv_user2", NULL, variables_entorno) == -1)
{
printf("execle genero error y devolvio -1\n");
perror("execle");
exit(EXIT_FAILURE);
}
// Por lo tanto, sea exito o error, las sig 2 sentencias
// JAMAS se ejecutan
printf("Si execle es exito o error, esto no se imprime\n");
exit(EXIT_SUCCESS);
}
Salida típica
En el proceso inicial pid=12345, antes de llamar a execle
el valor de la variable de entorno USER es: <tu-usuario>
el valor de la variable de entorno MI_VARIABLE es: (null)
ahora ejecutando ./programa13_getenv_user2 ...
Entrando al programa llamado por la familia exec, mi pid es 12345
el valor de la variable de entorno USER es: jose
el valor de la variable de entorno MI_VARIABLE es: acs
programa17_execve1.cexecve(path, argv[], envp[]): equivale a execle pero todo en arreglos (argumentos y entorno). Es la llamada «base» del sistema; las demás exec* son envoltorios de ésta.
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
// Este ejemplo muestra la funcion execve
// invocando a un programa nuestro getenv_user
// para leer la variable de entorno USER
int main(void)
{
printf("En el proceso inicial pid=%d, antes de llamar a execve\n", getpid() );
// como es execve entonces es un vector/arreglo
// y ademas puede leer variables de entorno que vienen en un arreglo
// y que solo son validas para la ejecucion del ejecutable dentro de la funcion execve
// la sintaxis es:
// int execve(char *path, char *argv[ ], char *envp[ ]);
// path es la ruta incluyendo el nombre del comando/ejecutable
// En C el nombre del comando/ejecutable es el argumento 0
// por eso se repite
// con NULL se indica que se llego al final del vector/arreglo
char * args[]={"./programa13_getenv_user2", NULL};
char *variables_entorno[] = { "PATH=/bin:/usr/bin", "USER=jose", "MI_VARIABLE=acs", NULL };
printf( "el valor de la variable de entorno USER es: %s\n", getenv("USER") );
printf( "el valor de la variable de entorno MI_VARIABLE es: %s\n", getenv("MI_VARIABLE") );
printf( "ahora ejecutando ./programa13_getenv_user2, los valores de las variables de entorno USER y MI_VARIABLE van a cambiar solo para esta ejecucion\n" );
if( execve("./programa13_getenv_user2", args, variables_entorno) == -1)
{
printf("execve genero error y devolvio -1\n");
perror("execve");
exit(EXIT_FAILURE);
}
// Por lo tanto, sea exito o error, las sig 2 sentencias
// JAMAS se ejecutan
printf("Si execve es exito o error, esto no se imprime\n");
exit(EXIT_SUCCESS);
}
Salida típica
En el proceso inicial pid=12345, antes de llamar a execve
el valor de la variable de entorno USER es: <tu-usuario>
el valor de la variable de entorno MI_VARIABLE es: (null)
ahora ejecutando ./programa13_getenv_user2 ...
Entrando al programa llamado por la familia exec, mi pid es 12345
el valor de la variable de entorno USER es: jose
el valor de la variable de entorno MI_VARIABLE es: acs
programa20_myecho.cPrograma auxiliar: imprime argv[0..argc-1], uno por línea. Sirve para ver exactamente qué argumentos recibe un programa lanzado con exec.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int j;
for( j=0; j < argc; j++ )
{
printf("argv[%d]: %s\n", j, argv[j]);
}
exit( EXIT_SUCCESS );
}
Salida típica
$ ./programa20_myecho hola mundo
argv[0]: ./programa20_myecho
argv[1]: hola
argv[2]: mundo
programa21_execve.cRecibe un ejecutable en argv[1] y lo lanza con execve, pasándole newargv (con newargv[0] = argv[1]) y entorno NULL. Ejercicio: explicar qué hace newargv[0] = argv[1] y modificar el bloque final para que, si execve falla, el printf no se ejecute.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
char *newargv[] = { NULL, "hola", "mundo", "buen", "dia", NULL };
if( argc != 2 )
{
fprintf( stderr, "La sintaxis es: %s <archivo-a-ejecutar>\n", argv[0] );
exit( EXIT_FAILURE );
}
// ESTA SENTENCIA ES MUY IMPORTANTE!
// QUE HACE ESTA SENTENCIA?
newargv[0] = argv[1];
execve( argv[1], newargv, NULL );
// AUNQUE LA SIG SENTENCIA DICE QUE NO SE EJECUTA,
// ASI COMO ESTA EL CODIGO, SI PODRIA EJECUTARSE, SI EL execve FALLA
// MODIFICA ESTE BLOQUE PARA QUE AUNQUE FALLE LA LLAMADA A execve
// LA SIGUIENTE SENTENCIA NO SE EJECUTE
printf("Esta sentencia no se ejecuta\n");
perror("execve");
exit( EXIT_FAILURE );
}
Salida típica
$ ./programa21_execve ./programa20_myecho
argv[0]: ./programa20_myecho
argv[1]: hola
argv[2]: mundo
argv[3]: buen
argv[4]: dia
programa22_fork-exec.cEl patrón completo con trazas: spawn() hace fork(); el hijo llama execvp("./programa01_print-pid") (se reemplaza), el padre regresa de spawn y sigue. El orden exacto de las líneas varía.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int spawn (char* program, char** arg_list)
{
pid_t child_pid;
printf ("Voy a llamar a fork() para crear un hijo\n");
child_pid = fork ();
printf ("Este printf sale duplicado, fork() me devolvio: %d\n", child_pid);
if (child_pid != 0)
return child_pid;
else
{
printf( "Soy el proceso hijo, PID=%d y PPID=%d\n", getpid(), getppid() );
// RECUERDEN: Si execvp() se ejecuta exitosamente
// el proceso "invocador" termina en ese momento,
// pero termina con exito
printf ("Hasta aqui llego! Voy a llamar a exec(...) para ejecutar programa01_print-pid\n");
execvp (program, arg_list);
printf ("ocurrio un error al ejecutar execvp(...)\n");
return 1; // termina con error
}
}
int main ()
{
char* arg_list[] = { "./programa01_print-pid", NULL };
printf( "Soy el proceso padre, PID=%d y PPID=%d\n", getpid(), getppid() );
printf( "Voy a la funcion spawn(...)\n" );
spawn ("./programa01_print-pid", arg_list);
printf ("Termina el proceso padre\n");
return 0; // Termina con exito
}
Salida típica
Soy el proceso padre, PID=12345 y PPID=6789
Voy a la funcion spawn(...)
Voy a llamar a fork() para crear un hijo
Este printf sale duplicado, fork() me devolvio: 12346
Termina el proceso padre
Este printf sale duplicado, fork() me devolvio: 0
Soy el proceso hijo, PID=12346 y PPID=12345
Hasta aqui llego! Voy a llamar a exec(...) para ejecutar programa01_print-pid
El process id es 12346
El parent process id es 12345
programa23_fork-exec.cVersión limpia del patrón (sin trazas): el hijo ejecuta ls -l /, el padre imprime Termina el proceso padre. El diagrama programa23_fork-exec.png ilustra este flujo.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int spawn (char* program, char** arg_list)
{
pid_t child_pid;
child_pid = fork ();
if (child_pid != 0)
return child_pid;
else
{
// RECUERDEN: Si execvp() se ejecuta exitosamente
// el proceso "parent" termina en ese momento,
// pero termina con exito
execvp (program, arg_list);
printf ("ocurrio un error al ejecutar execvp(...)\n");
// alternativa al printf:
// fprintf (stderr, "ocurrio un error al ejecutar execvp(...)\n");
return 1; // termina con error
}
}
int main ()
{
char* arg_list[] = {
"ls",
"-l",
"/",
NULL
};
spawn ("ls", arg_list);
printf ("Termina el proceso padre\n");
return 0; // Termina con exito
}
Salida típica
Termina el proceso padre
<listado del directorio raíz>
(o el listado primero y luego "Termina el proceso padre": el orden no está garantizado)
programa01_print-pid.cImprime el PID del proceso (getpid) y el PID de su padre (getppid).
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("El process id es %d\n", getpid());
printf("El parent process id es %d\n", getppid());
return 0;
}
Salida típica
El process id es 12345
El parent process id es 6789
programa02_ids.cImprime los IDs reales y efectivos de usuario y grupo. En un ejecutable con el bit setuid, el UID efectivo sería el del dueño del archivo, no el tuyo.
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("Real user ID: %d\n", getuid());
printf("Effective user ID: %d\n", geteuid());
printf("Real group ID: %d\n", getgid());
printf("Effective group ID: %d\n", getegid());
return 0;
}
Salida típica
Real user ID: 1000
Effective user ID: 1000
Real group ID: 1000
Effective group ID: 1000
Diagramas de las tres llamadas (system / fork / exec) en el Tema 1.