¿Cómo monitorear un proceso externo para eventos por su PID en C?
linux process (4)
Ejecute el binario de destino utilizando una biblioteca de precarga que atrapa fork()
. Siempre que todos los procesos secundarios también utilicen la biblioteca de precarga, verá todos los procesos secundarios locales, sin importar cómo se ejecuten.
Aquí hay una implementación de ejemplo.
Primero, el archivo de cabecera forkmonitor.h
Define los mensajes pasados desde la biblioteca de precarga al proceso de monitoreo:
#ifndef FORKMONITOR_H
#define FORKMONITOR_H
#define FORKMONITOR_ENVNAME "FORKMONITOR_SOCKET"
#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 108
#endif
#define TYPE_EXEC 1 /* When a binary is executed */
#define TYPE_DONE 2 /* exit() or return from main() */
#define TYPE_FORK 3
#define TYPE_VFORK 4
#define TYPE_EXIT 5 /* _exit() or _Exit() */
#define TYPE_ABORT 6 /* abort() */
struct message {
pid_t pid; /* Process ID */
pid_t ppid; /* Parent process ID */
pid_t sid; /* Session ID */
pid_t pgid; /* Process group ID */
uid_t uid; /* Real user ID */
gid_t gid; /* Real group ID */
uid_t euid; /* Effective user ID */
gid_t egid; /* Effective group ID */
unsigned short len; /* Length of data[] */
unsigned char type; /* One of the TYPE_ constants */
char data[0]; /* Optional payload, possibly longer */
};
#endif /* FORKMONITOR_H */
La variable de entorno FORKMONITOR_SOCKET
(nombrada por la macro FORKMONITOR_ENVNAME
arriba) especifica la adición del socket del datagrama del dominio Unix al proceso de monitoreo. Si no se define o está vacío, no se envían mensajes de supervisión.
Aquí está la propia biblioteca, libforkmonitor.c
. Tenga en cuenta que simplifiqué el código un poco, dejando de lado la inicialización multihilo (ya que es raro que una biblioteca llame a cualquiera de las funciones interceptadas, y aún más raro que lo haga desde varios subprocesos). Sería mejor usar los componentes atómicos (__sync_bool_compare_and_swap ()) para actualizar el puntero de la función y el captador atómico (__sync_fetch_and_or (, 0)) para recuperar el puntero de la función, para evitar cualquier problema con las bibliotecas wikkyky. (Esto es bastante seguro para los programas multiproceso, ya que los punteros solo se modificarán antes de que se ejecute main()
).
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <dlfcn.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "forkmonitor.h"
static pid_t (*actual_fork)(void) = NULL;
static pid_t (*actual_vfork)(void) = NULL;
static void (*actual_abort)(void) = NULL;
static void (*actual__exit)(int) = NULL;
static void (*actual__Exit)(int) = NULL;
static int commfd = -1;
#define MINIMUM_COMMFD 31
static void notify(const int type, struct message *const msg, const size_t extra)
{
const int saved_errno = errno;
msg->pid = getpid();
msg->ppid = getppid();
msg->sid = getsid(0);
msg->pgid = getpgrp();
msg->uid = getuid();
msg->gid = getgid();
msg->euid = geteuid();
msg->egid = getegid();
msg->len = extra;
msg->type = type;
/* Since we don''t have any method of dealing with send() errors
* or partial send()s, we just fire one off and hope for the best. */
send(commfd, msg, sizeof (struct message) + extra, MSG_EOR | MSG_NOSIGNAL);
errno = saved_errno;
}
void libforkmonitor_init(void) __attribute__((constructor));
void libforkmonitor_init(void)
{
const int saved_errno = errno;
int result;
/* Save the actual fork() call pointer. */
if (!actual_fork)
*(void **)&actual_fork = dlsym(RTLD_NEXT, "fork");
/* Save the actual vfork() call pointer. */
if (!actual_vfork)
*(void **)&actual_vfork = dlsym(RTLD_NEXT, "vfork");
/* Save the actual abort() call pointer. */
if (!actual_abort)
*(void **)&actual_abort = dlsym(RTLD_NEXT, "abort");
/* Save the actual _exit() call pointer. */
if (!actual__exit)
*(void **)&actual__exit = dlsym(RTLD_NEXT, "_exit");
if (!actual__exit)
*(void **)&actual__exit = dlsym(RTLD_NEXT, "_Exit");
/* Save the actual abort() call pointer. */
if (!actual__Exit)
*(void **)&actual__Exit = dlsym(RTLD_NEXT, "_Exit");
if (!actual__Exit)
*(void **)&actual__Exit = dlsym(RTLD_NEXT, "_exit");
/* Open an Unix domain datagram socket to the observer. */
if (commfd == -1) {
const char *address;
/* Connect to where? */
address = getenv(FORKMONITOR_ENVNAME);
if (address && *address) {
struct sockaddr_un addr;
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, address, sizeof addr.sun_path - 1);
/* Create and bind the socket. */
commfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (commfd != -1) {
if (connect(commfd, (const struct sockaddr *)&addr, sizeof (addr)) == -1) {
/* Failed. Close the socket. */
do {
result = close(commfd);
} while (result == -1 && errno == EINTR);
commfd = -1;
}
}
/* Move commfd to a high descriptor, to avoid complications. */
if (commfd != -1 && commfd < MINIMUM_COMMFD) {
const int newfd = MINIMUM_COMMFD;
do {
result = dup2(commfd, newfd);
} while (result == -1 && errno == EINTR);
if (!result) {
do {
result = close(commfd);
} while (result == -1 && errno == EINTR);
commfd = newfd;
}
}
}
}
/* Send an init message, listing the executable path. */
if (commfd != -1) {
size_t len = 128;
struct message *msg = NULL;
while (1) {
ssize_t n;
free(msg);
msg = malloc(sizeof (struct message) + len);
if (!msg) {
len = 0;
break;
}
n = readlink("/proc/self/exe", msg->data, len);
if (n > (ssize_t)0 && (size_t)n < len) {
msg->data[n] = ''/0'';
len = n + 1;
break;
}
len = (3 * len) / 2;
if (len >= 65536U) {
free(msg);
msg = NULL;
len = 0;
break;
}
}
if (len > 0) {
/* INIT message with executable name */
notify(TYPE_EXEC, msg, len);
free(msg);
} else {
/* INIT message without executable name */
struct message msg2;
notify(TYPE_EXEC, &msg2, sizeof msg2);
}
}
/* Restore errno. */
errno = saved_errno;
}
void libforkmonitor_done(void) __attribute__((destructor));
void libforkmonitor_done(void)
{
const int saved_errno = errno;
int result;
/* Send an exit message, no data. */
if (commfd != -1) {
struct message msg;
notify(TYPE_DONE, &msg, sizeof msg);
}
/* If commfd is open, close it. */
if (commfd != -1) {
do {
result = close(commfd);
} while (result == -1 && errno == EINTR);
}
/* Restore errno. */
errno = saved_errno;
}
/*
* Hooked C library functions.
*/
pid_t fork(void)
{
pid_t result;
if (!actual_fork) {
const int saved_errno = errno;
*(void **)&actual_fork = dlsym(RTLD_NEXT, "fork");
if (!actual_fork) {
errno = EAGAIN;
return (pid_t)-1;
}
errno = saved_errno;
}
result = actual_fork();
if (!result && commfd != -1) {
struct message msg;
notify(TYPE_FORK, &msg, sizeof msg);
}
return result;
}
pid_t vfork(void)
{
pid_t result;
if (!actual_vfork) {
const int saved_errno = errno;
*(void **)&actual_vfork = dlsym(RTLD_NEXT, "vfork");
if (!actual_vfork) {
errno = EAGAIN;
return (pid_t)-1;
}
errno = saved_errno;
}
result = actual_vfork();
if (!result && commfd != -1) {
struct message msg;
notify(TYPE_VFORK, &msg, sizeof msg);
}
return result;
}
void _exit(const int code)
{
if (!actual__exit) {
const int saved_errno = errno;
*(void **)&actual__exit = dlsym(RTLD_NEXT, "_exit");
if (!actual__exit)
*(void **)&actual__exit = dlsym(RTLD_NEXT, "_Exit");
errno = saved_errno;
}
if (commfd != -1) {
struct {
struct message msg;
int extra;
} data;
memcpy(&data.msg.data[0], &code, sizeof code);
notify(TYPE_EXIT, &(data.msg), sizeof (struct message) + sizeof (int));
}
if (actual__exit)
actual__exit(code);
exit(code);
}
void _Exit(const int code)
{
if (!actual__Exit) {
const int saved_errno = errno;
*(void **)&actual__Exit = dlsym(RTLD_NEXT, "_Exit");
if (!actual__Exit)
*(void **)&actual__Exit = dlsym(RTLD_NEXT, "_exit");
errno = saved_errno;
}
if (commfd != -1) {
struct {
struct message msg;
int extra;
} data;
memcpy(&data.msg.data[0], &code, sizeof code);
notify(TYPE_EXIT, &(data.msg), sizeof (struct message) + sizeof (int));
}
if (actual__Exit)
actual__Exit(code);
exit(code);
}
void abort(void)
{
if (!actual_abort) {
const int saved_errno = errno;
*(void **)&actual_abort = dlsym(RTLD_NEXT, "abort");
errno = saved_errno;
}
if (commfd != -1) {
struct message msg;
notify(TYPE_ABORT, &msg, sizeof msg);
}
actual_abort();
exit(127);
}
La función libforkmonitor_init()
es llamada automáticamente por el vinculador de tiempo de ejecución antes de llamar al proceso main()
, y se llama a libforkmonitor_done()
cuando el proceso regresa de main()
o llama a exit()
.
El libforkmonitor_init()
abre un socket de datagrama de dominio Unix al proceso de monitoreo, y envía sus credenciales y la ruta al ejecutable actual. Cada proceso secundario (siempre y cuando la biblioteca de precarga aún esté cargada) se ejecuta después de que se cargan, por lo que no hay necesidad de capturar las posix_spawn*()
exec*()
o posix_spawn*()
o ''popen () `etc.
Las funciones de la biblioteca C fork()
y vfork()
son interceptadas. Estas intercepciones son necesarias para detectar los casos en que el programa original se bifurca para crear procesos esclavos sin ejecutar ningún otro binario. (Al menos, la biblioteca GNU C usa fork()
internamente, por lo que estos también detectarán popen()
, posix_spawn()
, etc.)
Además, las funciones de la biblioteca C _exit()
, _Exit()
y abort()
se interceptan. _exit()
esto porque a algunos binarios, especialmente Dash, les gusta usar _exit()
, y pensé que sería bueno capturar todas las formas de salidas normales. (Sin embargo, no se detecta la muerte debida a señales; y si un binario ejecuta otro binario, solo recibirá el nuevo mensaje EXEC. Tenga en cuenta el proceso y las ID del proceso principal).
Aquí hay un programa de monitoreo simple, forkmonitor.c
:
#define _POSIX_C_SOURCE 200809L
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "forkmonitor.h"
static volatile sig_atomic_t done = 0;
static void done_handler(const int signum)
{
if (!done)
done = signum;
}
static int catch_done(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = done_handler;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL) == -1)
return errno;
return 0;
}
static const char *username(const uid_t uid)
{
static char buffer[128];
struct passwd *pw;
pw = getpwuid(uid);
if (!pw)
return NULL;
strncpy(buffer, pw->pw_name, sizeof buffer - 1);
buffer[sizeof buffer - 1] = ''/0'';
return (const char *)buffer;
}
static const char *groupname(const gid_t gid)
{
static char buffer[128];
struct group *gr;
gr = getgrgid(gid);
if (!gr)
return NULL;
strncpy(buffer, gr->gr_name, sizeof buffer - 1);
buffer[sizeof buffer - 1] = ''/0'';
return (const char *)buffer;
}
int main(int argc, char *argv[])
{
const size_t msglen = 65536;
struct message *msg;
int socketfd, result;
const char *user, *group;
if (catch_done(SIGINT) || catch_done(SIGQUIT) || catch_done(SIGHUP) ||
catch_done(SIGTERM) || catch_done(SIGPIPE)) {
fprintf(stderr, "Cannot set signal handlers: %s./n", strerror(errno));
return 1;
}
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "/n");
fprintf(stderr, "Usage: %s [ -h | --help ]/n", argv[0]);
fprintf(stderr, " %s MONITOR-SOCKET-PATH/n", argv[0]);
fprintf(stderr, "/n");
fprintf(stderr, "This program outputs events reported by libforkmonitor/n");
fprintf(stderr, "to Unix domain datagram sockets at MONITOR-SOCKET-PATH./n");
fprintf(stderr, "/n");
return 0;
}
msg = malloc(msglen);
if (!msg) {
fprintf(stderr, "Out of memory./n");
return 1;
}
socketfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (socketfd == -1) {
fprintf(stderr, "Cannot create an Unix domain datagram socket: %s./n", strerror(errno));
return 1;
}
{
struct sockaddr_un addr;
size_t len;
if (argv[1])
len = strlen(argv[1]);
else
len = 0;
if (len < 1 || len >= UNIX_PATH_MAX) {
fprintf(stderr, "%s: Path is too long (max. %d characters)/n", argv[1], UNIX_PATH_MAX - 1);
return 1;
}
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
memcpy(addr.sun_path, argv[1], len + 1); /* Include ''/0'' at end */
if (bind(socketfd, (struct sockaddr *)&addr, sizeof (addr)) == -1) {
fprintf(stderr, "Cannot bind to %s: %s./n", argv[1], strerror(errno));
return 1;
}
}
printf("Waiting for connections./n");
printf("/n");
/* Infinite loop. */
while (!done) {
ssize_t n;
n = recv(socketfd, msg, msglen, 0);
if (n == -1) {
const char *const errmsg = strerror(errno);
fprintf(stderr, "%s./n", errmsg);
fflush(stderr);
break;
}
if (msglen < sizeof (struct message)) {
fprintf(stderr, "Received a partial message; discarded./n");
fflush(stderr);
continue;
}
switch (msg->type) {
case TYPE_EXEC:
printf("Received an EXEC message:/n");
break;
case TYPE_DONE:
printf("Received a DONE message:/n");
break;
case TYPE_FORK:
printf("Received a FORK message:/n");
break;
case TYPE_VFORK:
printf("Received a VFORK message:/n");
break;
case TYPE_EXIT:
printf("Received an EXIT message:/n");
break;
case TYPE_ABORT:
printf("Received an ABORT message:/n");
break;
default:
printf("Received an UNKNOWN message:/n");
break;
}
if (msg->type == TYPE_EXEC && (size_t)n > sizeof (struct message)) {
if (*((char *)msg + n - 1) == ''/0'')
printf("/tExecutable: ''%s''/n", (char *)msg + sizeof (struct message));
}
printf("/tProcess ID: %d/n", (int)msg->pid);
printf("/tParent process ID: %d/n", (int)msg->ppid);
printf("/tSession ID: %d/n", (int)msg->sid);
printf("/tProcess group ID: %d/n", (int)msg->pgid);
user = username(msg->uid);
if (user)
printf("/tReal user: ''%s'' (%d)/n", user, (int)msg->uid);
else
printf("/tReal user: %d/n", (int)msg->uid);
group = groupname(msg->gid);
if (group)
printf("/tReal group: ''%s'' (%d)/n", group, (int)msg->gid);
else
printf("/tReal group: %d/n", (int)msg->gid);
user = username(msg->euid);
if (user)
printf("/tEffective user: ''%s'' (%d)/n", user, (int)msg->euid);
else
printf("/tEffective user: %d/n", (int)msg->euid);
group = groupname(msg->egid);
if (group)
printf("/tEffective group: ''%s'' (%d)/n", group, (int)msg->egid);
else
printf("/tEffective group: %d/n", (int)msg->egid);
printf("/n");
fflush(stdout);
}
do {
result = close(socketfd);
} while (result == -1 && errno == EINTR);
unlink(argv[1]);
return 0;
}
Toma un solo parámetro de línea de comando, la dirección de socket de dominio Unix. Debe ser una ruta absoluta del sistema de archivos.
Puede detener el programa de monitoreo a través de las señales INT
( Ctrl + C ), HUP
, QUIT
y TERM
.
Compilar la biblioteca usando
gcc -W -Wall -O3 -fpic -fPIC -c libforkmonitor.c
gcc -shared -Wl,-soname,libforkmonitor.so libforkmonitor.o -ldl -o libforkmonitor.so
y el programa monitor usando
gcc -W -Wall -O3 forkmonitor.c -o forkmonitor
En una ventana de terminal, inicie el forkmonitor primero:
./forkmonitor "$PWD/commsocket"
En otra ventana de terminal, en el mismo directorio, ejecute el comando supervisado, precargando automáticamente la biblioteca libforkmonitor.so
y especificando el socket para el monitor:
env "LD_PRELOAD=$PWD/libforkmonitor.so" "FORKMONITOR_SOCKET=$PWD/commsocket" command args...
Tenga en cuenta que debido a que utiliza las variables de entorno LD_PRELOAD
y FORKMONITOR_SOCKET
, los procesos secundarios se ignoran si su padre modifica el entorno (eliminando las dos variables de entorno), y al ejecutar setgid
binarios setuid
o setgid
. Esta limitación se puede evitar eliminando las variables de entorno y codificándolas.
El enlazador en tiempo de ejecución no cargará previamente las bibliotecas para setgid
binarios setuid
o setgid
, a menos que la biblioteca esté en uno de los directorios de la biblioteca estándar, y también setgid
marcado como setgid
.
Agregar el nombre de la biblioteca a /etc/ld.so.preload
cargará la biblioteca para todos los archivos binarios, pero probablemente debería agregar un mecanismo a libforkmonitor_init()
que limita el monitoreo a los archivos binarios deseados y / o a un usuario real específico (como usuario efectivo) cambios al ejecutar un binario setuid).
Por ejemplo, cuando corro
env "LD_PRELOAD=$PWD/libforkmonitor.so" "FORKMONITOR_SOCKET=$PWD/commsocket" sh -c ''date ; ls -laF''
La salida de monitoreo es (anonimizada):
Received an EXEC message:
Executable: ''bin/dash''
Process ID: 11403
Parent process ID: 9265
Session ID: 9265
Process group ID: 11403
Real user: ''username'' (1000)
Real group: ''username'' (1000)
Effective user: ''username'' (1000)
Effective group: ''username'' (1000)
Received a FORK message:
Process ID: 11404
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: ''username'' (1000)
Real group: ''username'' (1000)
Effective user: ''username'' (1000)
Effective group: ''username'' (1000)
Received an EXEC message:
Executable: ''bin/date''
Process ID: 11404
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: ''username'' (1000)
Real group: ''username'' (1000)
Effective user: ''username'' (1000)
Effective group: ''username'' (1000)
Received a DONE message:
Process ID: 11404
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: ''username'' (1000)
Real group: ''username'' (1000)
Effective user: ''username'' (1000)
Effective group: ''username'' (1000)
Received a FORK message:
Process ID: 11405
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: ''username'' (1000)
Real group: ''username'' (1000)
Effective user: ''username'' (1000)
Effective group: ''username'' (1000)
Received an EXEC message:
Executable: ''bin/ls''
Process ID: 11405
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: ''username'' (1000)
Real group: ''username'' (1000)
Effective user: ''username'' (1000)
Effective group: ''username'' (1000)
Received a DONE message:
Process ID: 11405
Parent process ID: 11403
Session ID: 9265
Process group ID: 11403
Real user: ''username'' (1000)
Real group: ''username'' (1000)
Effective user: ''username'' (1000)
Effective group: ''username'' (1000)
Received an EXIT message:
Process ID: 11403
Parent process ID: 9265
Session ID: 9265
Process group ID: 11403
Real user: ''username'' (1000)
Real group: ''username'' (1000)
Effective user: ''username'' (1000)
Effective group: ''username'' (1000)
Esta es una solución de control de árbol de procesos muy ligera. Aparte del proceso de inicio, salida y llamada a una de las funciones interceptadas ( fork()
, vfork()
, _exit()
, _Exit()
, abort()
), la ejecución del programa no se verá afectada en absoluto. Debido a que la biblioteca es tan liviana, incluso aquellos afectados solo se verán afectados por una cantidad muy, muy pequeña; Probablemente no sea suficiente para medir de forma fiable.
Obviamente, es posible interceptar otras funciones y / o usar comunicación bidireccional, "pausando" la ejecución de la función interceptada hasta que la aplicación de monitoreo responda.
Hay algunos errores en general, especialmente relacionados con los procesos setuid / setgid y los procesos que generan un nuevo entorno (omitiendo las variables de entorno LD_PRELOAD
y FORKMONITOR_SOCKET
), pero se pueden FORKMONITOR_SOCKET
si hay privilegios de superusuario disponibles.
Espero que encuentre este informativo. Preguntas?
¿Hay alguna biblioteca que tenga alguna función que le permita monitorear un proceso externo para eventos por su pid_t
? Quiero decir, monitorear si un proceso externo ha salido, o si ha creado uno o más procesos secundarios (con fork
), o si se ha convertido en otra imagen ejecutable (a través de una llamada de la familia de la función exec
o posix_spawn
) o si una señal de Unix era entregado a ella
EDITAR
Necesito algo que no interfiera con la ejecución del programa que se está monitoreando. Entonces, se supone que no debo usar ptrace
, ya que detiene el proceso que se está monitoreando cuando emite alguna señal y es necesario reanudar el proceso cuando esto sucede.
Hay pocas herramientas disponibles que pueden recopilar información sobre un proceso mientras se está ejecutando.
Te sugiero que uses perf y systemTap.
https://perf.wiki.kernel.org/index.php/Main_Page
http://sourceware.org/systemtap/SystemTap_Beginners_Guide/index.html
Si puede ejecutar como root, entonces puede usar los eventos proc de la interfaz netlink:
http://bewareofgeek.livejournal.com/2945.html
Simplemente lo compilé limpiamente en fedora 17 x86_64 y me da esto:
[root@hip1 yotest]# ./proc
set mcast listen ok
fork: parent tid=2358 pid=2358 -> child tid=21007 pid=21007
exec: tid=21007 pid=21007
fork: parent tid=21007 pid=21007 -> child tid=21008 pid=21008
fork: parent tid=21007 pid=21007 -> child tid=21009 pid=21009
fork: parent tid=21007 pid=21007 -> child tid=21010 pid=21010
fork: parent tid=21007 pid=21007 -> child tid=21011 pid=21011
exec: tid=21010 pid=21010
exec: tid=21008 pid=21008
exec: tid=21011 pid=21011
exec: tid=21009 pid=21009
exit: tid=21008 pid=21008 exit_code=0
fork: parent tid=21010 pid=21010 -> child tid=21012 pid=21012
exit: tid=21009 pid=21009 exit_code=0
exec: tid=21012 pid=21012
exit: tid=21012 pid=21012 exit_code=0
exit: tid=21010 pid=21010 exit_code=0
exit: tid=21011 pid=21011 exit_code=0
exit: tid=21007 pid=21007 exit_code=0
Deberá filtrar los pids específicos que sean de su interés, pero puede hacerlo fácilmente en la instrucción de cambio en la línea 107.
Para fines de preservación:
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/connector.h>
#include <linux/cn_proc.h>
#include <signal.h>
#include <errno.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/*
* connect to netlink
* returns netlink socket, or -1 on error
*/
static int nl_connect()
{
int rc;
int nl_sock;
struct sockaddr_nl sa_nl;
nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
if (nl_sock == -1) {
perror("socket");
return -1;
}
sa_nl.nl_family = AF_NETLINK;
sa_nl.nl_groups = CN_IDX_PROC;
sa_nl.nl_pid = getpid();
rc = bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl));
if (rc == -1) {
perror("bind");
close(nl_sock);
return -1;
}
return nl_sock;
}
/*
* subscribe on proc events (process notifications)
*/
static int set_proc_ev_listen(int nl_sock, bool enable)
{
int rc;
struct __attribute__ ((aligned(NLMSG_ALIGNTO))) {
struct nlmsghdr nl_hdr;
struct __attribute__ ((__packed__)) {
struct cn_msg cn_msg;
enum proc_cn_mcast_op cn_mcast;
};
} nlcn_msg;
memset(&nlcn_msg, 0, sizeof(nlcn_msg));
nlcn_msg.nl_hdr.nlmsg_len = sizeof(nlcn_msg);
nlcn_msg.nl_hdr.nlmsg_pid = getpid();
nlcn_msg.nl_hdr.nlmsg_type = NLMSG_DONE;
nlcn_msg.cn_msg.id.idx = CN_IDX_PROC;
nlcn_msg.cn_msg.id.val = CN_VAL_PROC;
nlcn_msg.cn_msg.len = sizeof(enum proc_cn_mcast_op);
nlcn_msg.cn_mcast = enable ? PROC_CN_MCAST_LISTEN : PROC_CN_MCAST_IGNORE;
rc = send(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
if (rc == -1) {
perror("netlink send");
return -1;
}
return 0;
}
/*
* handle a single process event
*/
static volatile bool need_exit = false;
static int handle_proc_ev(int nl_sock)
{
int rc;
struct __attribute__ ((aligned(NLMSG_ALIGNTO))) {
struct nlmsghdr nl_hdr;
struct __attribute__ ((__packed__)) {
struct cn_msg cn_msg;
struct proc_event proc_ev;
};
} nlcn_msg;
while (!need_exit) {
rc = recv(nl_sock, &nlcn_msg, sizeof(nlcn_msg), 0);
if (rc == 0) {
/* shutdown? */
return 0;
} else if (rc == -1) {
if (errno == EINTR) continue;
perror("netlink recv");
return -1;
}
switch (nlcn_msg.proc_ev.what) {
case PROC_EVENT_NONE:
printf("set mcast listen ok/n");
break;
case PROC_EVENT_FORK:
printf("fork: parent tid=%d pid=%d -> child tid=%d pid=%d/n",
nlcn_msg.proc_ev.event_data.fork.parent_pid,
nlcn_msg.proc_ev.event_data.fork.parent_tgid,
nlcn_msg.proc_ev.event_data.fork.child_pid,
nlcn_msg.proc_ev.event_data.fork.child_tgid);
break;
case PROC_EVENT_EXEC:
printf("exec: tid=%d pid=%d/n",
nlcn_msg.proc_ev.event_data.exec.process_pid,
nlcn_msg.proc_ev.event_data.exec.process_tgid);
break;
case PROC_EVENT_UID:
printf("uid change: tid=%d pid=%d from %d to %d/n",
nlcn_msg.proc_ev.event_data.id.process_pid,
nlcn_msg.proc_ev.event_data.id.process_tgid,
nlcn_msg.proc_ev.event_data.id.r.ruid,
nlcn_msg.proc_ev.event_data.id.e.euid);
break;
case PROC_EVENT_GID:
printf("gid change: tid=%d pid=%d from %d to %d/n",
nlcn_msg.proc_ev.event_data.id.process_pid,
nlcn_msg.proc_ev.event_data.id.process_tgid,
nlcn_msg.proc_ev.event_data.id.r.rgid,
nlcn_msg.proc_ev.event_data.id.e.egid);
break;
case PROC_EVENT_EXIT:
printf("exit: tid=%d pid=%d exit_code=%d/n",
nlcn_msg.proc_ev.event_data.exit.process_pid,
nlcn_msg.proc_ev.event_data.exit.process_tgid,
nlcn_msg.proc_ev.event_data.exit.exit_code);
break;
default:
printf("unhandled proc event/n");
break;
}
}
return 0;
}
static void on_sigint(int unused)
{
need_exit = true;
}
int main(int argc, const char *argv[])
{
int nl_sock;
int rc = EXIT_SUCCESS;
signal(SIGINT, &on_sigint);
siginterrupt(SIGINT, true);
nl_sock = nl_connect();
if (nl_sock == -1)
exit(EXIT_FAILURE);
rc = set_proc_ev_listen(nl_sock, true);
if (rc == -1) {
rc = EXIT_FAILURE;
goto out;
}
rc = handle_proc_ev(nl_sock);
if (rc == -1) {
rc = EXIT_FAILURE;
goto out;
}
set_proc_ev_listen(nl_sock, false);
out:
close(nl_sock);
exit(rc);
}
(gcc -o proc proc.c)
Y algo de información en netlink:
Extracto: http://www.linuxjournal.com/article/7356
Netlink es asíncrono porque, como con cualquier otra API de socket, proporciona una cola de socket para suavizar la ráfaga de mensajes. La llamada del sistema para enviar un mensaje de enlace de red pone en cola el mensaje en la cola de enlace de red del receptor y luego invoca el controlador de recepción del receptor. El receptor, dentro del contexto del manejador de recepción, puede decidir si procesa el mensaje inmediatamente o si deja el mensaje en la cola y lo procesa más tarde en un contexto diferente. A diferencia de netlink, las llamadas al sistema requieren procesamiento síncrono. Por lo tanto, si utilizamos una llamada del sistema para pasar un mensaje del espacio de usuario al kernel, la granularidad de la programación del kernel puede verse afectada si el tiempo para procesar ese mensaje es largo.
¡También hay un anuncio interesante para nltrace hecho recientemente que también puede encontrar interesante! http://lists.infradead.org/pipermail/libnl/2013-April/000993.html
Utilice el comando del sistema "pidof" de la biblioteca procps. Muy simple y facil de usar. Si devuelve algo, entonces el proceso se está ejecutando o viceversa.