Skip to content

Instantly share code, notes, and snippets.

@jaypeche
Created December 20, 2014 10:48
Show Gist options
  • Save jaypeche/497ac0c3ce36cdb7f5b9 to your computer and use it in GitHub Desktop.
Save jaypeche/497ac0c3ce36cdb7f5b9 to your computer and use it in GitHub Desktop.
fanotify example
/*
* File: fanotify-example.c
* Date: Fri Nov 15 14:55:49 2013
* Author: Aleksander Morgado <[email protected]>
*
* A simple tester of fanotify in the Linux kernel.
*
* This program is released in the Public Domain.
*
* Compile with:
* $> gcc -o fanotify-example fanotify-example.c
*
* Run as:
* $> ./fanotify-example /path/to/monitor /another/path/to/monitor ...
*/
/* Define _GNU_SOURCE, Otherwise we don't get O_LARGEFILE */
#define _GNU_SOURCE
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/signalfd.h>
#include <fcntl.h>
#include <linux/fanotify.h>
/* Structure to keep track of monitored directories */
typedef struct {
/* Path of the directory */
char *path;
} monitored_t;
/* Size of buffer to use when reading fanotify events */
#define FANOTIFY_BUFFER_SIZE 8192
/* Enumerate list of FDs to poll */
enum {
FD_POLL_SIGNAL = 0,
FD_POLL_FANOTIFY,
FD_POLL_MAX
};
/* Setup fanotify notifications (FAN) mask. All these defined in fanotify.h. */
static uint64_t event_mask =
(FAN_ACCESS | /* File accessed */
FAN_MODIFY | /* File modified */
FAN_CLOSE_WRITE | /* Writtable file closed */
FAN_CLOSE_NOWRITE | /* Unwrittable file closed */
FAN_OPEN | /* File was opened */
FAN_ONDIR | /* We want to be reported of events in the directory */
FAN_EVENT_ON_CHILD); /* We want to be reported of events in files of the directory */
/* Array of directories being monitored */
static monitored_t *monitors;
static int n_monitors;
static char *
get_program_name_from_pid (int pid,
char *buffer,
size_t buffer_size)
{
int fd;
ssize_t len;
char *aux;
/* Try to get program name by PID */
sprintf (buffer, "/proc/%d/cmdline", pid);
if ((fd = open (buffer, O_RDONLY)) < 0)
return NULL;
/* Read file contents into buffer */
if ((len = read (fd, buffer, buffer_size - 1)) <= 0)
{
close (fd);
return NULL;
}
close (fd);
buffer[len] = '\0';
aux = strstr (buffer, "^@");
if (aux)
*aux = '\0';
return buffer;
}
static char *
get_file_path_from_fd (int fd,
char *buffer,
size_t buffer_size)
{
ssize_t len;
if (fd <= 0)
return NULL;
sprintf (buffer, "/proc/self/fd/%d", fd);
if ((len = readlink (buffer, buffer, buffer_size - 1)) < 0)
return NULL;
buffer[len] = '\0';
return buffer;
}
static void
event_process (struct fanotify_event_metadata *event)
{
char path[PATH_MAX];
printf ("Received event in path '%s'",
get_file_path_from_fd (event->fd,
path,
PATH_MAX) ?
path : "unknown");
printf (" pid=%d (%s): \n",
event->pid,
(get_program_name_from_pid (event->pid,
path,
PATH_MAX) ?
path : "unknown"));
if (event->mask & FAN_OPEN)
printf ("\tFAN_OPEN\n");
if (event->mask & FAN_ACCESS)
printf ("\tFAN_ACCESS\n");
if (event->mask & FAN_MODIFY)
printf ("\tFAN_MODIFY\n");
if (event->mask & FAN_CLOSE_WRITE)
printf ("\tFAN_CLOSE_WRITE\n");
if (event->mask & FAN_CLOSE_NOWRITE)
printf ("\tFAN_CLOSE_NOWRITE\n");
fflush (stdout);
close (event->fd);
}
static void
shutdown_fanotify (int fanotify_fd)
{
int i;
for (i = 0; i < n_monitors; ++i)
{
/* Remove the mark, using same event mask as when creating it */
fanotify_mark (fanotify_fd,
FAN_MARK_REMOVE,
event_mask,
AT_FDCWD,
monitors[i].path);
free (monitors[i].path);
}
free (monitors);
close (fanotify_fd);
}
static int
initialize_fanotify (int argc,
const char **argv)
{
int i;
int fanotify_fd;
/* Create new fanotify device */
if ((fanotify_fd = fanotify_init (FAN_CLOEXEC,
O_RDONLY | O_CLOEXEC | O_LARGEFILE)) < 0)
{
fprintf (stderr,
"Couldn't setup new fanotify device: %s\n",
strerror (errno));
return -1;
}
/* Allocate array of monitor setups */
n_monitors = argc - 1;
monitors = malloc (n_monitors * sizeof (monitored_t));
/* Loop all input directories, setting up marks */
for (i = 0; i < n_monitors; ++i)
{
monitors[i].path = strdup (argv[i + 1]);
/* Add new fanotify mark */
if (fanotify_mark (fanotify_fd,
FAN_MARK_ADD,
event_mask,
AT_FDCWD,
monitors[i].path) < 0)
{
fprintf (stderr,
"Couldn't add monitor in directory '%s': '%s'\n",
monitors[i].path,
strerror (errno));
return -1;
}
printf ("Started monitoring directory '%s'...\n",
monitors[i].path);
}
return fanotify_fd;
}
static void
shutdown_signals (int signal_fd)
{
close (signal_fd);
}
static int
initialize_signals (void)
{
int signal_fd;
sigset_t sigmask;
/* We want to handle SIGINT and SIGTERM in the signal_fd, so we block them. */
sigemptyset (&sigmask);
sigaddset (&sigmask, SIGINT);
sigaddset (&sigmask, SIGTERM);
if (sigprocmask (SIG_BLOCK, &sigmask, NULL) < 0)
{
fprintf (stderr,
"Couldn't block signals: '%s'\n",
strerror (errno));
return -1;
}
/* Get new FD to read signals from it */
if ((signal_fd = signalfd (-1, &sigmask, 0)) < 0)
{
fprintf (stderr,
"Couldn't setup signal FD: '%s'\n",
strerror (errno));
return -1;
}
return signal_fd;
}
int
main (int argc,
const char **argv)
{
int signal_fd;
int fanotify_fd;
struct pollfd fds[FD_POLL_MAX];
/* Input arguments... */
if (argc < 2)
{
fprintf (stderr, "Usage: %s directory1 [directory2 ...]\n", argv[0]);
exit (EXIT_FAILURE);
}
/* Initialize signals FD */
if ((signal_fd = initialize_signals ()) < 0)
{
fprintf (stderr, "Couldn't initialize signals\n");
exit (EXIT_FAILURE);
}
/* Initialize fanotify FD and the marks */
if ((fanotify_fd = initialize_fanotify (argc, argv)) < 0)
{
fprintf (stderr, "Couldn't initialize fanotify\n");
exit (EXIT_FAILURE);
}
/* Setup polling */
fds[FD_POLL_SIGNAL].fd = signal_fd;
fds[FD_POLL_SIGNAL].events = POLLIN;
fds[FD_POLL_FANOTIFY].fd = fanotify_fd;
fds[FD_POLL_FANOTIFY].events = POLLIN;
/* Now loop */
for (;;)
{
/* Block until there is something to be read */
if (poll (fds, FD_POLL_MAX, -1) < 0)
{
fprintf (stderr,
"Couldn't poll(): '%s'\n",
strerror (errno));
exit (EXIT_FAILURE);
}
/* Signal received? */
if (fds[FD_POLL_SIGNAL].revents & POLLIN)
{
struct signalfd_siginfo fdsi;
if (read (fds[FD_POLL_SIGNAL].fd,
&fdsi,
sizeof (fdsi)) != sizeof (fdsi))
{
fprintf (stderr,
"Couldn't read signal, wrong size read\n");
exit (EXIT_FAILURE);
}
/* Break loop if we got the expected signal */
if (fdsi.ssi_signo == SIGINT ||
fdsi.ssi_signo == SIGTERM)
{
break;
}
fprintf (stderr,
"Received unexpected signal\n");
}
/* fanotify event received? */
if (fds[FD_POLL_FANOTIFY].revents & POLLIN)
{
char buffer[FANOTIFY_BUFFER_SIZE];
ssize_t length;
/* Read from the FD. It will read all events available up to
* the given buffer size. */
if ((length = read (fds[FD_POLL_FANOTIFY].fd,
buffer,
FANOTIFY_BUFFER_SIZE)) > 0)
{
struct fanotify_event_metadata *metadata;
metadata = (struct fanotify_event_metadata *)buffer;
while (FAN_EVENT_OK (metadata, length))
{
event_process (metadata);
if (metadata->fd > 0)
close (metadata->fd);
metadata = FAN_EVENT_NEXT (metadata, length);
}
}
}
}
/* Clean exit */
shutdown_fanotify (fanotify_fd);
shutdown_signals (signal_fd);
printf ("Exiting fanotify example...\n");
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment