Poesía Binaria

Buscar un archivo en el PATH en C

En principio si vamos a ejecutar un programa con popen() o exec*() no habría problema. Siempre que exec sea de la familia de los exec*p(), es decir execvp(), execlp(), execvpe(), ya que estos buscan en la variable de entorno PATH el ejecutable, aunque los demás exec no lo hacen y requieren el fichero y path completo.

Podemos utilizarla, por ejemplo para ahorrarle a exec el trabajo de tener que buscar en el PATH si vamos a hacer muchas llamadas a un ejecutable o para mostrar al usuario la ruta y el nombre de archivo de un programa que va a ejecutar. También, un poco más abajo encontramos un bonus, que nos permite buscar archivos tanto en el PATH del sistema como en uno que nosotros espeifiquemos, y que nos será útil por ejemplo para buscar ejecutables en /usr/sbin, /usr/local/sbin y otros en sistemas en los que para nuestro usuario, ese ejecutable no se encuentra (hay más *nix que colores…)

En esta primera versión, somos nosotros los que debemos reservar memoria en una cadena de caracteres para almacenar el resultado, y el éxito o no de la ejecución vendrá dado por el return de la función:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

short file_exists(char *filename)
{
  int fd=open(filename, O_RDONLY);
  if (fd==-1)
    {
      if (errno==2)             /* If errno==2 it means file not found */
        return 0;               /* otherwise there is another error at */
      else                      /* reading file, for example path not  */
        return -1;              /* found, no memory, etc */
    }
  close(fd);                    /* If we close the file, it exists */
  return 1;
}

int findInPath(char *result, char *executable)
{
  char *path = getenv("PATH");
  char *saveptr;
  char *tmpstr = malloc(strlen(path)+strlen(executable)+2);
  char *directory = strtok_r(path, ":", &saveptr);
  char *slash = "/";
  short found = 0;
  while ( (directory != NULL) && (!found) )
    {
      sprintf (tmpstr, "%s%s%s", directory, (directory[strlen(directory)-1]=='/')?"":slash, executable);
      if (file_exists(tmpstr))
    found = 1;
      directory = strtok_r(NULL, ":", &saveptr);
    }

  if (found)
    strcpy(result, tmpstr);

  free(tmpstr);

  return found;
}

int main ()
{
  char res[200];
  if (findInPath(res, "ls"))
  {
     printf ("Found at: %s\n", res);
  }
  else
    printf ("Not found!\n");

  return 0;
}

En esta segunda versión, directamente devolvemos un char* como resultado de la función, por lo que si éste vale NULL significará que el archivo no se ha encontrado en el PATH. Eso sí, cuando no utilicemos más el valor, es nuestra responsabilidad liberar la memoria.
También me gustaría indicar que se podría haber hecho una reserva de memoria más eficiente, ahora mismo reservamos un tamaño igual al tamaño del PATH + longitud del nombre de archivo + 2 (una / y el terminador), esto sería en el peor de los casos, en el que en el PATH sólo haya un directorio, aunque generalmente no será así, aunque preferí esto a estar reservando y liberando memoria cada vez que lea un directorio, y no creo que el PATH sea tan excesivamente largo como para que haya un gran desperdicio de memoria.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

short file_exists(char *filename)
{
  int fd=open(filename, O_RDONLY);
  if (fd==-1)
    {
      if (errno==2)             /* If errno==2 it means file not found */
        return 0;               /* otherwise there is another error at */
      else                      /* reading file, for example path not  */
        return -1;              /* found, no memory, etc */
    }
  close(fd);                    /* If we close the file, it exists */
  return 1;
}

char* findInPath2(char *executable)
{
  char *path = getenv("PATH");
  char *saveptr;
  char *tmpstr = malloc(strlen(path)+strlen(executable)+2);
  char *directory = strtok_r(path, ":", &saveptr);
  char *slash = "/";
  short found = 0;
  while ( (directory != NULL) && (!found) )
    {
      sprintf (tmpstr, "%s%s%s", directory, (directory[strlen(directory)-1]=='/')?"":slash, executable);
      if (file_exists(tmpstr))
    found = 1;
      directory = strtok_r(NULL, ":", &saveptr);
    }
  if (found)
    return tmpstr;

  return NULL;
}

int main ()
{
  char *res = findInPath2("kwrite");
  if (res != NULL)
    {
      printf ("Found at: %s\n", res);
      free(res);
    }
  else
    printf ("Not found!\n");

  return 0;
}

Como toque final, vamos a presentar un tercer ejemplo donde además de buscar en el PATH buscamos en rutas puestas a mano:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

short file_exists(char *filename)
{
  int fd=open(filename, O_RDONLY);
  if (fd==-1)
    {
      if (errno==2)             /* If errno==2 it means file not found */
        return 0;               /* otherwise there is another error at */
      else                      /* reading file, for example path not  */
        return -1;              /* found, no memory, etc */
    }
  close(fd);                    /* If we close the file, it exists */
  return 1;
}

char* findInPath3(char *executable, char *additional_path)
{
  char *path = getenv("PATH");
  char *saveptr;
  char *tmpstr = malloc(strlen(path)+strlen(executable)+2);
  char *directory = strtok_r(path, ":", &saveptr);
  char *_additional_path;
  char *slash = "/";
  short found = 0;
  while ( (directory != NULL) && (!found) )
    {
      sprintf (tmpstr, "%s%s%s", directory, (directory[strlen(directory)-1]=='/')?"":slash, executable);
      if (file_exists(tmpstr))
    found = 1;
      directory = strtok_r(NULL, ":", &saveptr);
    }
  if (found)
    return tmpstr;

  if (additional_path == NULL)
    return NULL;

  _additional_path = malloc(strlen(additional_path)+1);
  strcpy(_additional_path, additional_path);
  directory = strtok_r(_additional_path, ":", &saveptr);
  while ( (directory != NULL) && (!found) )
    {
      sprintf (tmpstr, "%s%s%s", directory, (directory[strlen(directory)-1]=='/')?"":slash, executable);
      if (file_exists(tmpstr))
    found = 1;
      directory = strtok_r(NULL, ":", &saveptr);
    }
  free(_additional_path);

  if (found)
    return tmpstr;

  return NULL;
}

int main (int argc, char *argv[])
{
  char *res = findInPath3("puertas.jpg", (char*) "/home/usuario:/home/usuario/Descargas/Imágenes" );
  if (res != NULL)
    {
      printf ("Found at: %s\n", res);
      free(res);
    }
  else
    printf ("Not found!\n");
 
  return 0;
}

En este ejemplo es curioso cómo para strtok_r() es necesario hacer una copia de la cadena additional_path, más que nada porque ésta es modificada en cada iteración y si presentamos una cadena literal en realidad estamos pansando un valor constante y por tanto no podrá ser modificado y devolverá una violación de segmento. De todas formas, con strtok() y derivados es importante siempre trabajar con una copia (no lo hacemos con la variable path, porque no tiene uso más allá del bucle con strtok(), podemos probar poner su valor en pantalla al final del bucle para ver a lo que me refiero.

Como último apunte decir que he utilizado strtok_r porque éste es reentrante y es más seguro utilizar éste en aplicaciones multi-hilo. Si queremos utilizar el strtok() de toda la vida, perfecto, pero strtok_r() es más seguro, ya que la información necesaria para las iteraciones las almacena en una variable que nosotros controlamos. Por ejemplo strok() es un desastre también en funciones recursivas.

Foto: VectorOpenStock (CC-by)

Actualizado: 18/01/2015 : Arreglado el código del tercer ejemplo (no se veía bien en la web)

También podría interesarte....