Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

linux C++ notify file type creation

Tags:

c++

c

linux

inotify

I need to monitor the creation of a specific file type (with a known extension) in a specific folder in linux. I understand that inotify will only watch existing files. Do I understand correctly?

Is there an alternative to inotify (or similar package) that would allow me to monitor file creation by file type?

EDIT: what I need is to monitor file creation by mask. I need to monitor a path for *.json, while disregarding other file types.

like image 609
mousomer Avatar asked Dec 01 '25 02:12

mousomer


1 Answers

This sounds like a good use case of inotify. The man page has a pretty good example that is easily transferred to your problem.

Here is a little program that can be used like

$ myprog /tmp '*.o' '*.a'

to watch the directory /tmp for the creation of *.o and *.a files. Note that the patterns are quoted to prevent expansion by the shell. The program runs forever until interrupted by SIGINT (press Ctrl + C).

I am using fnmatch to match the names of the created files against the patterns and install a signal handler for SIGINT that sets a global flag.

#include <assert.h>
#include <errno.h>
#include <fnmatch.h>
#include <limits.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>

I define a small preprocessor macro to align the buffer using GCC's __attribute__ extension. More on this later when we actually use this macro.

#ifdef __GNUC__
#  define ALIGNAS(TYPE) __attribute__ ((aligned(__alignof__(TYPE))))
#else
#  define ALIGNAS(TYPE) /* empty */
#endif

This is a global flag that we'll set in the signal handler to indicate that the program should quit gracefully.

static volatile int interrupted = 0;

And this is the signal handler itself.

static void
interruption_handler(const int s)
{
  if (s == SIGINT)
    interrupted = 1;
}

This function is the working-horse of the program.

static int
monitor_directory(const char *const directory,
                  const char *const *const patterns,
                  const size_t pattern_count)
{
  int notifyfd = -1;
  int watchfd = -1;
  int ret = 0;
  const char * errmsg = "unknown error";

First, we initialize inotify. inotify_init will return a file descriptor that we can read() notifications from. I use blocking I/O so the read() will block until an event occurs.

  notifyfd = inotify_init();
  if (notifyfd < 0)
    {
      errmsg = "inotify_init";
      goto catch;
    }

Now we register files to watch. In our case, we want to watch a single directory (directory) for the creation of new files (IN_CREATE). The returned file descriptor could be used to tell (if an event occurs) to what watched file it belongs. But since we are watching only a single file (that happens to be a directory) anyway, we don't really need this info.

  watchfd = inotify_add_watch(notifyfd, directory, IN_CREATE);
  if (watchfd < 0)
    {
      errmsg = "inotify_add_watch";
      goto catch;
    }

Now everything is set up properly and we can start read()ing from the notification file descriptor.

  while (1)
    {

It is not known in advance how much a call to read from the inotify descriptor will read so we have to read into a char buffer. We wish to align it properly, tough. See the man page for more comments on this.

      char buffer[sizeof(struct inotify_event) + NAME_MAX + 1] ALIGNAS(struct inotify_event);
      const struct inotify_event * event_ptr;

read() from the file descriptor. If we get interrupted, the read() will unblock and return –1 as it will if an error occurs.

      ssize_t count = read(notifyfd, buffer, sizeof(buffer));
      if (count < 0)
        {
          if (interrupted)
            goto finally;
          errmsg = "read";
          goto catch;
        }

We have a new event, handle it.

      event_ptr = (const struct inotify_event *) buffer;
      assert(event_ptr->wd == watchfd);
      assert(event_ptr->mask & IN_CREATE);
      if (event_ptr->len)
        {
          size_t i;

Try matching the file name against each of our patterns.

          for (i = 0; i < pattern_count; ++i)
            {
              switch (fnmatch(patterns[i], event_ptr->name, FNM_PATHNAME))
                {
                case 0:
                  /* Your application logic here... */
                  if (printf("%s\n", event_ptr->name) < 0)
                    {
                      errmsg = "printf";
                      goto catch;
                    }
                  break;
                case FNM_NOMATCH:
                  break;
                default:
                  errmsg = "fnmatch";
                  goto catch;
                }
            }
        }
    }

finally, we have to do some cleanup. close()ing the file descriptors created by inotify will cause it to release any associated resources.

 finally:
  if (watchfd >= 0)
    {
      int status = close(watchfd);
      watchfd = -1;
      if (status < 0)
        {
          errmsg = "close(watchfd)";
          goto catch;
        }
    }
  if (notifyfd >= 0)
    {
      int status = close(notifyfd);
      notifyfd = -1;
      if (status < 0)
        {
          errmsg = "close(notifyfd)";
          goto catch;
        }
    }
  return ret;
 catch:
  if (errmsg && errno)
    perror(errmsg);
  ret = -1;
  goto finally;
}

And this is how we hook everything together and run the program.

int
main(const int argc, const char *const *const argv)
{
  if (argc < 3)
    {
      fprintf(stderr, "usage: %s DIRECTORY PATTERN...\n", argv[0]);
      return EXIT_FAILURE;
    }
  {
    struct sigaction sa;
    sa.sa_handler = interruption_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);
  }
  if (monitor_directory(argv[1], argv + 2, argc - 2) < 0)
    return EXIT_FAILURE;
  return EXIT_SUCCESS;
}
like image 195
5gon12eder Avatar answered Dec 02 '25 15:12

5gon12eder