Search code examples
linuxfanotify

fanotify: is it possible to monitor whole filesystem and write few logs/config in monitored filesystem by same process?


My system gets hanged, if I try to log something in file by same process.

Actually I wanted to monitor entire filesystem ("/") with fanotify and also want to log errors in case any in "/tmp", but it results in system hang.

Please find below code:

    #include <errno.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <poll.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/fanotify.h>
    #include <unistd.h>
    #include <string.h>
    static void
    handle_events(int fd)
    {
        const struct fanotify_event_metadata *metadata;
        struct fanotify_event_metadata buf[200];
        ssize_t len;
        char path[PATH_MAX];
        ssize_t path_len;
        char procfd_path[PATH_MAX];
        struct fanotify_response response;

        //Loop while events can be read from fanotify file descriptor 

        for (;;) 
        {
            //Read some events

            len = read(fd, (void *) &buf, sizeof(buf));
            if (len == -1 && errno != EAGAIN) 
            {
                system("echo 'Read error' >> /tmp/fanotify.txt");
                exit(EXIT_FAILURE);
            }

            //Check if end of available data reached

            if (len <= 0)
                break;

            //Point to the first event in the buffer
            metadata = buf;

            //Loop over all events in the buffer

            while (FAN_EVENT_OK(metadata, len)) 
            {

                //Check that run-time and compile-time structures match

                if (metadata->vers != FANOTIFY_METADATA_VERSION) 
                {
                    system("echo 'Mismatch of fanotify metadata version' >> /tmp/fanotify.txt");
                    exit(EXIT_FAILURE);
                }

                /* metadata->fd contains either FAN_NOFD, indicating a
                queue overflow, or a file descriptor (a nonnegative
                integer). Here, we simply ignore queue overflow. */

                if (metadata->fd >= 0) 
                {
                    //Handle open permission event

                    if (metadata->mask & FAN_OPEN_PERM) 
                    {
                        //Allow file to be opened

                        response.fd = metadata->fd;
                        response.response = FAN_ALLOW;
                        write(fd, &response,sizeof(struct fanotify_response));
                        system("echo 'FAN_OPEN_PERM:' >> /tmp/fanotify.txt");
                     }

                    //Handle closing of writable file event

                    if (metadata->mask & FAN_CLOSE_WRITE)
                    {
                        system("echo 'FAN_CLOSE_WRITE:' >> /tmp/fanotify.txt");
                    }


                    //Retrieve and print pathname of the accessed file 

                    snprintf(procfd_path, sizeof(procfd_path),
                        "/proc/self/fd/%d", metadata->fd);
                    path_len = readlink(procfd_path, path,
                            sizeof(path) - 1);
                    if (path_len == -1) 
                    {
                        system("echo 'readlink error' >> /tmp/fanotify.txt");
                        exit(EXIT_FAILURE);
                    }

                    path[path_len] = '\0';

                    close(metadata->fd);

                    char szLog[256] = {'\0'};
                    snprintf(szLog, sizeof(szLog), "echo 'File %s' >> /tmp/fanotify.txt", path);
                    system(szLog);  

                    //Close the file descriptor of the event

                }

                //Advance to next event
                metadata = FAN_EVENT_NEXT(metadata, len);
            }
        }
    }

Here is main function from where I am calling handle_events

    int
    main(int argc, char *argv[])
    {
        char buf;
        int fd, poll_num;
        nfds_t nfds;
        struct pollfd fds[2];
        char szMountCommand[1024] = {'\0'};
        uint64_t uiMask = FAN_OPEN_PERM | FAN_CLOSE_WRITE | FAN_EVENT_ON_CHILD;

        //Check mount point is supplied

        if (argc != 2) {
            fprintf(stderr, "Usage: %s MOUNT\n", argv[0]);
            exit(EXIT_FAILURE);
        }

        system("echo 'Press enter key to terminate' >> /tmp/fanotify.txt");

        //Create the file descriptor for accessing the fanotify API

        fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK,
                O_RDONLY | O_LARGEFILE);
        if (fd == -1) {
            system("echo 'fanotify_init failed.' >> /tmp/fanotify.txt");
            exit(EXIT_FAILURE);
        }

        /* Mark the mount for:
           - permission events before opening files
           - notification events after closing a write-enabled
           file descriptor */

        snprintf(szMountCommand, sizeof(szMountCommand), "mount --bind %s %s", argv[1], argv[1]);
            system(szMountCommand); 

        if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT, uiMask, 0, argv[1]) == -1)
        {
            system("echo 'fanotify_mark failed.' >> /tmp/fanotify.txt");
            exit(EXIT_FAILURE);
        }

        system("echo 'Monitoring:' >> /tmp/fanotify.txt");

        //Prepare for polling

        nfds = 2;

        //Console input

        fds[0].fd = STDIN_FILENO;
        fds[0].events = POLLIN;

        //Fanotify input

        fds[1].fd = fd;
        fds[1].events = POLLIN;

        //This is the loop to wait for incoming events

        system("echo 'Listening for events:' >> /tmp/fanotify.txt");

        while (1) {
            poll_num = poll(fds, nfds, -1);
            if (poll_num == -1) {
                if (errno == EINTR)     //Interrupted by a signal
                    continue;           // Restart poll()

                system("echo 'poll failed.' >> /tmp/fanotify.txt");
                exit(EXIT_FAILURE);
            }

            if (poll_num > 0) {
                if (fds[0].revents & POLLIN) {

                    //Console input is available: empty stdin and quit

                    while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n')
                        continue;
                    break;
                }

                if (fds[1].revents & POLLIN) {

                    //Fanotify events are available

                    handle_events(fd);
                }
            }
        }

        system("echo 'Listening for events stopped.' >> /tmp/fanotify.txt");
        exit(EXIT_SUCCESS);
    }


Solution

  • That's an infinite loop!

    Consider you get a notification (due to some external change) and want to write that to the same filesystem. So, it would generate another notification (due to the logging). you want to write the new notification. That leads to another notification. So that is an endless loop.

    You shuold use another mounted filesystem for logging or monitor only a specific path.