← Arquitectura Cliente-Servidor

Programas — Tema 1

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).

#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.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).

#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.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.

#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.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).

#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.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.

#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.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.

/* 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.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.

#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.c

Versió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

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.

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.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.

#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.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.

#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.c

execlp(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.c

execvp(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.c

Programa «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.c

Igual, 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.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.

#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.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.

#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.c

Igual 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.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.

#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.c

Programa 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.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.




#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.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.

#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.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.

#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.c

Imprime 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.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.

#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.