Poesía Binaria

Buscando un proceso en C

En ocasiones, nuestros programas requieren que un servicio o un programa esté en ejecución. Algunos servicios los podemos ubicar fácilmente, ya que /var/run ,  /dev/shm u otra ruta contienen un archivo con su PID (Identificador de proceso), otras servicios no figuran en ningún lado. También puede ser que estemos esperando que otro proceso termine y necesitamos averiguar su PID.

Para todo ello, debemos echar un ojo al contenido de /proc/, ahí encontramos, entre otras cosas, información sobre los procesos en ejecución del sistema. En principio lo que nos interesa es el nombre del ejecutable (es lo que estamos buscando), para ello, vamos a buscar dentro del directorio /proc/ todos los directorios que sean numéricos (esos que contendrán un PID), y leeremos el fichero /proc/[pid]/comm, que contiene la información del nombre del ejecutable. Eso sí, ese nombre tendrá una longitud limitada (marcada por la constante del kernel TASK_COMM_LEN, que suele valer 16 (con lo que los nombres tendrán como máximo 15 caracteres):

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
76
77
78
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <ctype.h>

void tareaProceso(char *nombre, int pid);

void buscaProceso(char *nombre);

void error(char *error);

/* Una función lo más general posible */
void getProcName(char *dest, int pid);

int main(int argc, char *argv[])
{
  buscaProceso("firefox-bin");
  return EXIT_SUCCESS;
}

void tareaProceso(char *nombre, int pid)
{
  printf("Encontrado proceso %-16s con PID: %d\n", nombre, pid);
  /* Aquí ya puedo matarlo, mandarle señales o hacer lo que quiera */
  /* con el proceso en cuestión */
}

void buscaProceso(char *nombre)
{
  DIR *proc;
  struct dirent *proceso;
  char procname[16];        /* Nombre del proceso */
  int pid;

  proc=opendir("/proc");

  if (proc==NULL)
    error("No puedo acceder al directorio /proc");

  while ((proceso=readdir(proc)) != NULL)
    {
      /* En /proc hay más cosas además de los PIDs, debemos asegurarnos de que */
      /* lo que hemos visto es un ID de proceso, lo demás no nos interesa */
      if ( (*proceso->d_name>'0') && (*proceso->d_name<='9') )
    {
      /* Convertimos el PID a número para la llamada a la función */
      pid=strtol(proceso->d_name, NULL, 10);
      getProcName(procname, pid);
      if (strncmp(nombre,procname,15)==0)
        tareaProceso(procname, pid);
    }
    }
}

void error(char *error)
{
  fprintf(stderr, "Error: %s (%d, %s)\n", error, errno, strerror(errno));
  exit(EXIT_FAILURE);
}

void getProcName(char *dest, int pid)
{
  FILE *fcomm;
  char filename[20];        /* /proc/[PID] (5 chars)/comm : total 16 chars */

  sprintf(filename,"/proc/%d/comm", pid);

  fcomm=fopen(filename, "r");
  if (!fcomm)
    error("No existe el fichero");
  /* A veces fgets() coge un salto de línea extra */
  /* fgets(dest, 16, fcomm); */
  fscanf(fcomm, "%s", dest);
  close(fcomm);
}

En este ejemplo si comentamos la línea 52 y en la línea 53 ponemos:

1
 tareaProceso(nombre, pid);

podemos obtener un listado completo de las tareas en ejecución (sólo por curiosear un poco). Aunque en este ejemplo tenemos un problema, mencionado antes, el número de caracteres del ejecutable, es decir, hay ejecutables con más de 15 caracteres, y tal vez queramos buscarlos. Con la solución actual estamos cortando los nombres (con strncmp()), es decir, sería lo mismo aplicacionconnombrelargo que aplicacionconnombrecorto, ya que sólo tenemos en cuenta los primeros 15 caracteres. Tiene que haber otra forma de sacar el nombre completo, y es a través del archivo /proc/[PID]/cmdline, es más, desde aquí podemos sacar la ruta del ejecutable y los parámetros con los que se llamó, tal y como vemos cuando ejecutamos:

$ ps x

Vamos a ver cómo podemos adaptar el código de antes para poder sacar toda esa informació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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <libgen.h>     /* basename */

enum pnametype
  {
    SHORT,
    LONG,
    WPATH
  };

/* Realiza una tarea con el proceso encontrado */
void tareaProceso(char *nombre, int pid);

/* Busca un proceso determinado */
void buscaProceso(char *nombre);

/* Sale del programa emitiendo un error */
void error(char *error);

/* Obtiene el nombre de un proceso */
void getProcName(char *dest, int pid, enum pnametype outtype);

/* Lee los contenidos de un archivo */
int getFileContents(char *dest, char *fname);

int main(int argc, char *argv[])
{
  buscaProceso("plugin-container");
  return EXIT_SUCCESS;
}

void tareaProceso(char *nombre, int pid)
{
  printf("Encontrado proceso %-16s con PID: %d\n", nombre, pid);
  /* Aquí ya puedo matarlo, mandarle señales o hacer lo que quiera */
  /* con el proceso en cuestión */
}

void buscaProceso(char *nombre)
{
  DIR *proc;
  struct dirent *proceso;
  char procname[255];       /* Como getProcName necesita una variable grande, se la damos */
  int pid;

  proc=opendir("/proc");

  if (proc==NULL)
    error("No puedo acceder al directorio /proc");

  while ((proceso=readdir(proc)) != NULL)
    {
      /* En /proc hay más cosas además de los PIDs, debemos asegurarnos de que */
      /* lo que hemos visto es un ID de proceso, lo demás no nos interesa */
      if ( (*proceso->d_name>'0') && (*proceso->d_name<='9') )
    {
      /* Convertimos el PID a número para la llamada a la función */
      pid=strtol(proceso->d_name, NULL, 10);
      getProcName(procname, pid, LONG);
      if (strcmp(nombre,procname)==0)
        tareaProceso(procname, pid);
    }
    }
}

void error(char *error)
{
  fprintf(stderr, "Error: %s (%d, %s)\n", error, errno, strerror(errno));
  exit(EXIT_FAILURE);
}

int getFileContents(char *dest, char *fname)
{
  FILE *fdata;
  int res;

  fdata=fopen(fname, "r");
  if (!fdata)
    error("No existe el fichero");
 
  res=fscanf(fdata, "%s", dest);
  if (res==EOF)         /* Si no hemos leído nada,  */
    *dest='\0';         /* nos aseguramos de vaciar la cadena */

  fclose(fdata);

  /* Devolvemos un error si, por ejemplo el fichero está vacío */
  return (res!=EOF);
}
/* dest tiene que ser grande, en un proyecto real, reservamos memoria desde */
/* getProcName, no nos arriesgamos a que falle algo de fuera */
void getProcName(char *dest, int pid, enum pnametype outtype)
{
  char filename[20];        /* /proc/[PID] (5 chars)/cmdline : total 19 chars */
  char *tmp;
  char *newfile;
  if (outtype==SHORT)
    sprintf(filename,"/proc/%d/comm", pid);
  else
    sprintf(filename,"/proc/%d/cmdline", pid);

  if (!getFileContents(dest, filename))
    {
      /* Si no hemos podido obtener nada */
      if (outtype!=SHORT)
    {
      /* probamos desde comm */
      sprintf(filename,"/proc/%d/comm", pid);
      getFileContents(dest, filename);
    }
    }

  if (outtype!=SHORT)
    {
      /* Si queremos un tipo de salida que no sea el corto, tendremos que transformar esta cadena */
      tmp=dest;
      while ((*tmp!=' ') && (*tmp!='\0')) ++tmp;
      tmp='\0';
      if (outtype==LONG)
    {
      tmp=basename(dest);
      strcpy(dest, tmp);
    }
    }
}

Ahora, obtenemos la información dependiendo del tipo especificado por outtype de tipo enum pnametype, e incluso si especificamos LONG, este tipo extraerá el nombre del archivo ejecutable despreciando la ruta del mismo. Para descargar los archivos de código, click aquí (2.3Kb)

También podría interesarte....