RPM Package Manager, CVS Repository http://rpm5.org/cvs/ ____________________________________________________________________________
Server: rpm5.org Name: Jeff Johnson Root: /v/rpm/cvs Email: j...@rpm5.org Module: rpm Date: 23-May-2017 06:55:11 Branch: rpm-5_4 Handle: 2017052304551100 Modified files: (Branch: rpm-5_4) rpm/rpmio fanotify.c Log: - Take 2 ... Summary: Revision Changes Path 1.1.2.3 +316 -230 rpm/rpmio/fanotify.c ____________________________________________________________________________ patch -p0 <<'@@ .' Index: rpm/rpmio/fanotify.c ============================================================================ $ cvs diff -u -r1.1.2.2 -r1.1.2.3 fanotify.c --- rpm/rpmio/fanotify.c 23 May 2017 04:53:56 -0000 1.1.2.2 +++ rpm/rpmio/fanotify.c 23 May 2017 04:55:11 -0000 1.1.2.3 @@ -1,265 +1,351 @@ -/* Copyright 2015 Mark Haines +/* + * File: fanotify-example.c + * Date: Fri Nov 15 14:55:49 2013 + * Author: Aleksander Morgado <aleksan...@lanedo.com> * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * A simple tester of fanotify in the Linux kernel. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is released in the Public Domain. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Compile with: + * $> gcc -o fanotify-example fanotify-example.c + * + * Run as: + * $> ./fanotify-example /path/to/monitor /another/path/to/monitor ... */ -#include "system.h" +/* Define _GNU_SOURCE, Otherwise we don't get O_LARGEFILE */ +#define _GNU_SOURCE -#if defined(HAVE_SYS_FANOTIFY_H) -#include <sys/fanotify.h> -#endif - -#include "debug.h" - -const char USAGE[] = - "Usage: fanotify [FLAGS PATH]... [FLAGS]\n" - "Watch a directory or filesytem using fanotify\n\n" - "If no path is supplied then watch the current directory\n\n" - "Examples:\n" - " fanotify CLOSE_WRITE\n" - " Watch the current directory\n" - " fanotify CLOSE_WRITE MOUNT\n" - " Watch the current filesytem\n" - " fanotify CLOSE_WRITE \"\" /home\n" - " Watch the /home directory\n"; - -struct flag { - uint64_t mask; - const char *name; +#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 }; -static const struct flag MASKS[8] = { -#define _ENTRY(_v) { FAN_##_v, #_v, } - _ENTRY(OPEN), - _ENTRY(ACCESS), - _ENTRY(MODIFY), - _ENTRY(CLOSE), - _ENTRY(CLOSE_WRITE), - _ENTRY(CLOSE_NOWRITE), - _ENTRY(ONDIR), - _ENTRY(EVENT_ON_CHILD), -#undef _ENTRY -}; -static const unsigned int MASK_COUNT = sizeof(MASKS)/sizeof(MASKS[0]); +/* 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); -static const struct flag FLAGS[] = { -#define _ENTRY(_v) { FAN_MARK_##_v, #_v, } - _ENTRY(DONT_FOLLOW), - _ENTRY(ONLYDIR), - _ENTRY(MOUNT), - _ENTRY(IGNORED_MASK), -#undef _ENTRY -}; -static const unsigned int FLAG_COUNT = sizeof(FLAGS)/sizeof(FLAGS[0]); + 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]; -uint64_t parse_flags(const struct flag *flags, unsigned flag_count, - const char *pos) + 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) { - uint64_t mask = 0; - unsigned j; - char const *next = pos; - - while (*next) { - /* Scan through the ',' separated list of flags */ - next = strchr(pos, ','); - if (!next) - next = pos + strlen(pos); - - for (j = 0; j < flag_count; ++j) { - const struct flag *fp = flags + j; - if (((int) strlen(fp->name) == (next - pos)) - && (strncmp(fp->name, pos, (next - pos)) == 0)) { - /* If the flag has a name we know then add it to the mask */ - mask |= fp->mask; - break; - } - } - if (j == flag_count) { - /* If we don't know what the flag is then exit */ - fprintf(stderr, "Unknown value: \"%*s\"\nPossible values are:\n", - (int) (next - pos), pos); - for (j = 0; j < flag_count; ++j) - fprintf(stderr, " %s\n", flags[j].name); - exit(1); - } - pos = next + 1; + 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); } - return mask; + free (monitors); + close (fanotify_fd); } -char *print_flags(char *te, char *tend, - const struct flag *flags, unsigned flag_count, - uint64_t mask) +static int +initialize_fanotify (int argc, + const char **argv) { - /* If the mask is empty then we don't print anything. */ - if (!mask) - goto exit; - - for (unsigned j = 0; j < flag_count; ++j) { - const struct flag *fp = flags + j; - if ((fp->mask & mask) != fp->mask) - continue; - /* Print the name of the bits */ - size_t length = strlen(fp->name); - if ((tend - te) < (int) length) - length = (tend - te); - memcpy(te, fp->name, length); - te += length; - if (te < tend) - *te++ = '|'; - /* Remove the bits from the mask. */ - mask &= ~fp->mask; + 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; } - if (mask) { - /* The mask contained some bits we don't know about. Print it as hex */ - te += snprintf(te, (tend - te), "0x%llx", (long long) mask); - } else { - /* We have written a trailing '|' character since the mask is set and - * we known what all the bits mean. So we can safely move te one - * character back to remove the trailing '|' */ - --te; + /* 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); } -exit: - return te; + return fanotify_fd; } -int main(int argc, char const *argv[]) +static void +shutdown_signals (int signal_fd) { - int ec = 1; /* assume failure */ - int rc; + close (signal_fd); +} - if (argc == 1) { - fprintf(stdout, "%s", USAGE); - goto exit; +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; } - /* - * Create a fanotify_fd. We only need to be notified about events, and - * we only want to read the files. - */ - int fanfd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY); - if (fanfd < 0) { - /* - * The most likely reason to fail here is that we don't have - * the CAP_SYS_ADMIN cabability needed by fanotify_init - */ - if (errno == EPERM) - fprintf(stderr, "fanotify needs to be run as root\n"); - else - perror("fanotify_init"); - goto exit; + /* 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; } - /* - * In theory fanotify_mark should be able to take AT_FDCWD for the dirfd. - * However it seems to complain if we pass AT_FDCWD to it. So instead we - * open the current working directory and pass the resulting fd. - */ - int cwdfd = openat(AT_FDCWD, ".", O_RDONLY | O_DIRECTORY); - if (cwdfd < 0) { - perror("openat"); - goto exit; + 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); } - for (int i = 1; i < argc; ++i) { - /* Parse the mask bits from the first argument */ - uint64_t mask = parse_flags(MASKS, MASK_COUNT, argv[i]); - unsigned int flags = FAN_MARK_ADD; - char const *path = "."; - /* Then parse the flags bits from the second argument */ - if (++i < argc) - flags |= parse_flags(FLAGS, FLAG_COUNT, argv[i]); - /* Then optionally set path using the third argument */ - if (++i < argc) - path = argv[i]; - rc = fanotify_mark(fanfd, flags, mask, cwdfd, path); - if (rc < 0) { - perror("fanotify_mark"); - goto exit; - } + /* Initialize signals FD */ + if ((signal_fd = initialize_signals ()) < 0) + { + fprintf (stderr, "Couldn't initialize signals\n"); + exit (EXIT_FAILURE); } - close(cwdfd); + /* Initialize fanotify FD and the marks */ + if ((fanotify_fd = initialize_fanotify (argc, argv)) < 0) + { + fprintf (stderr, "Couldn't initialize fanotify\n"); + exit (EXIT_FAILURE); + } - for (;;) { - char s[BUFSIZ]; - size_t ns = sizeof(s); - char *se = s; - - ssize_t nr = read(fanfd, s, ns); - if (nr < 0) { - perror("read"); - exit(1); - } - char *send = s + nr; - - while (se < send) { - struct fanotify_event_metadata * event - = (struct fanotify_event_metadata *) se; - char t[BUFSIZ]; - size_t nt = sizeof(t); - char *te = t; - /* Leave space at the end of the output buffer for a '\n' */ - char *tend = t + nt - 1; - - /* Check that we have enough input read an event structure. */ - if ((send - se) < (int) sizeof(*event)) { - perror("Invalid fanotify_event_meta"); - goto exit; - } - - /* Check that we have all of the event structure and that it's - * a version that we understand */ - if ((send - s) < event->event_len - || event->vers != FANOTIFY_METADATA_VERSION) - { - perror("Invalid fanotify_event_meta"); - goto exit; - } - - /* Print the event mask. Each bit will be separated by '|' - * characters. */ - te = print_flags(te, tend, MASKS, MASK_COUNT, event->mask); - - /* Print the pid of the process that this is event is from */ - te += snprintf(te, (tend - te), " %d ", event->pid); - - /* We aren't told the path of the event directly. But we can read - * the /proc/self/fd/%d symlink to see what path the file - * descriptor was opened with */ - char fn[32]; - snprintf(fn, sizeof(fn), "/proc/self/fd/%d", event->fd); - nr = readlink(fn, te, (tend - te)); - if (nr < 0) { - perror("readlink"); - goto exit; - } - te += nr; - *te = '\0'; - - fprintf(stdout, "%s\n", t); - - /* Close the event's file descriptor. */ - close(event->fd); - - /* Advance to the next event in the input buffer */ - se += event->event_len; - } + /* 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); + } + } + } } - ec = 0; -exit: - return ec; + /* Clean exit */ + shutdown_fanotify (fanotify_fd); + shutdown_signals (signal_fd); + + printf ("Exiting fanotify example...\n"); + + return EXIT_SUCCESS; } @@ . ______________________________________________________________________ RPM Package Manager http://rpm5.org CVS Sources Repository rpm-cvs@rpm5.org