Search code examples
cpointers

What solves a 'inotify_add_watch' error 'no such file or directory' on recursively adding sub directories?


This example should add an 'inotify_add_watch' functionality for a newly created subsequent directory within a given path (for example: /tmp), but does not add that watch function for the newly created directory (for example: /tmp/subdir1/subdir2).

Only for the root path directory (/tmp) level, given by argv[1], it is functional (for example: /tmp/subdir1). The returned watch descriptor (variable 'watch_fd') is increased.
For a directory created in a more distant tree position (/tmp/subdir1/subdir2) from root directory (/tmp), there's that 'no such file or directory' error. The returned watch descriptor 'watch_fd' then is -1.

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>

#define EVENT_SIZE (sizeof(struct inotify_event))
#define EVENT_BUF_LEN (1024 * (EVENT_SIZE + 16))

// Function to add inotify watch recursively up to a specified depth
void add_watch_recursive(int inotify_fd, const char *base_path, int depth, int current_depth) {
    if (current_depth > depth) {
        return;
    }

    printf("base_path %s \t", base_path);
    int watch_fd = inotify_add_watch(inotify_fd, base_path, IN_CREATE | IN_DELETE);
    printf("watch_fd %d \n", watch_fd);
    if (watch_fd < 0) {
        perror("inotify_add_watch");
        printf("\t\t\t 'inotify_add_watch: No such file or directory' watch_fd %d\n", watch_fd);
        return;
    }


    DIR *dir = opendir(base_path);
    if (!dir) {
        perror("opendir");
        return;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        char path[PATH_MAX];
        snprintf(path, PATH_MAX, "%s/%s", base_path, entry->d_name);

        struct stat statbuf;
        if (stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
            add_watch_recursive(inotify_fd, path, depth, current_depth + 1);
        }
    }

    closedir(dir);
}

int main(int argc, char *argv[]) {
    if (argc < 3) {
        fprintf(stderr, "Usage: %s <path> <depth>\n", argv[0]);
        return EXIT_FAILURE;
    }

    const char *path = argv[1];
    int depth = atoi(argv[2]);

    if (depth < 0) {
        fprintf(stderr, "Error: Depth must be a non-negative integer\n");
        return EXIT_FAILURE;
    }

    // Print the path to verify it
    printf("Attempting to open directory: %s\n", path);

    // Check if the directory exists
    struct stat statbuf;
    if (stat(path, &statbuf) != 0) {
        perror("stat");
        return EXIT_FAILURE;
    }

    // Check if it is a directory
    if (!S_ISDIR(statbuf.st_mode)) {
        fprintf(stderr, "Error: %s is not a directory\n", path);
        return EXIT_FAILURE;
    }

    // Initialize inotify
    int inotify_fd = inotify_init();
    if (inotify_fd < 0) {
        perror("inotify_init");
        return EXIT_FAILURE;
    }

    // Add watch on the specified directory and its subdirectories up to the specified depth
    add_watch_recursive(inotify_fd, path, depth, 0);

    printf("Watching directory: %s and its subdirectories up to depth: %d\n", path, depth);

    // Buffer to store inotify events
    char buffer[EVENT_BUF_LEN];

    // Event loop
    while (1) {
        int length = read(inotify_fd, buffer, EVENT_BUF_LEN);
        if (length < 0) {
            perror("read");
            break;
        }

        int i = 0;
        while (i < length) {
            struct inotify_event *event = (struct inotify_event *) &buffer[i];
            if (event->len) {
                char event_path[PATH_MAX];
                snprintf(event_path, PATH_MAX, "%s/%s", path, event->name);

                if (event->mask & IN_CREATE) {
                    if (event->mask & IN_ISDIR) {
                        printf("Directory created: %s\n", event_path);
                        // Add watch to the new directory if within the specified depth
                        add_watch_recursive(inotify_fd, event_path, depth, 1);
                    } else {
                        printf("File created: %s\n", event_path);
                    }
                } else if (event->mask & IN_DELETE) {
                    if (event->mask & IN_ISDIR) {
                        printf("Directory deleted: %s\n", event_path);
                    } else {
                        printf("File deleted: %s\n", event_path);
                    }
                }
            }
            i += EVENT_SIZE + event->len;
        }
    }

    // Clean up
    close(inotify_fd);

    return EXIT_SUCCESS;
}

This example code for a C program for Linux system is compiled with
gcc inotifyaddwatch_subdirs.c -o inotifyaddwatch_subdirs and
started with ./inotifyaddwatch_subdirs /tmp 5 .

Only with restarting the program, a subsequent directory is added from startup, but not within the running program.

If it is a pointer error for a sub directory's singular path reference within the line
int watch_fd = inotify_add_watch(inotify_fd, base_path, IN_CREATE | IN_DELETE);
for the variable 'base_path', then it would need an additional exception handling.

The man pages for 'inotify_add_watch' explains for this error return value
"ENOENT A directory component in pathname does not exist or is a dangling symbolic link.",
but i did not succeed in adding that sub directory path (proved to exist and being recognized within that program) to that pathname pointer array.


Solution

  • Look at the output from your code.

    If I start a watch on dir1:

    ./inotifyaddwatch_subdirs dir1 5
    

    The initial output is:

    Attempting to open directory: dir1
    base_path dir1  watch_fd 1
    Watching directory: dir1 and its subdirectories up to depth: 3
    

    Then I create dir1/dir2:

    Directory created: dir1/dir2
    base_path dir1/dir2     watch_fd 2
    

    And finally I create dir1/dir2/dir3:

    Directory created: dir1/dir3
    base_path dir1/dir3     watch_fd -1
    inotify_add_watch: No such file or directory
                             'inotify_add_watch: No such file or directory' watch_fd -1
    

    Look at the value of base_path in the above output; instead of adding dir1/dir2/dir3, you're trying to add dir1/dir3, which of course does not exist. That's because in main, when you handle the notification:

    char event_path[PATH_MAX];
    snprintf(event_path, PATH_MAX, "%s/%s", path, event->name);
    

    You are appending event->name to the value of path, which is your top level directory. This will always return an invalid path for anything not contained in the top-level directory.

    You need to maintain some data structure that maps the watch descriptors returned by inotify_add_watch to directory paths, and then use this to look up the path in which a file or directory has been completed so that you can create the appropriate path name.


    Here's a version of the code that uses the LIST implementation from sys/queue.h to track watched directories:

    #include <dirent.h>
    #include <limits.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/inotify.h>
    #include <sys/queue.h>
    #include <sys/stat.h>
    #include <unistd.h>
    
    #define EVENT_SIZE (sizeof(struct inotify_event))
    #define EVENT_BUF_LEN (1024 * (EVENT_SIZE + NAME_MAX + 1))
    
    typedef struct _watched_dir {
      int wd;
      int depth;
      char path[PATH_MAX];
    
      LIST_ENTRY(_watched_dir) links;
    } watched_dir;
    
    LIST_HEAD(watched_dir_list, _watched_dir) watched_dirs;
    
    void dump_watch_list() {
      printf("WATCH LIST\n");
      watched_dir *d;
      LIST_FOREACH(d, &watched_dirs, links) {
        printf("wd %d depth %d name %s\n", d->wd, d->depth, d->path);
      }
    }
    
    watched_dir *find_dir_from_wd(int wd) {
      watched_dir *d;
      LIST_FOREACH(d, &watched_dirs, links) {
        if (d->wd == wd)
          return d;
      }
    
      return NULL;
    }
    
    watched_dir *new_watched_dir(int wd, int depth, const char *path) {
      watched_dir *d = (watched_dir *)malloc(sizeof(watched_dir));
      d->wd = wd;
      d->depth = depth;
      strncpy(d->path, path, PATH_MAX);
      return d;
    }
    
    // Function to add inotify watch recursively up to a specified depth
    void add_watch_recursive(int inotify_fd, const char *base_path, int depth,
                             int current_depth) {
      printf("add_watch_recursive %d %s\n", current_depth, base_path);
      if (current_depth > depth) {
        return;
      }
    
      printf("base_path %s \t", base_path);
      int watch_fd =
          inotify_add_watch(inotify_fd, base_path, IN_CREATE | IN_DELETE);
      printf("watch_fd %d \n", watch_fd);
      if (watch_fd < 0) {
        perror("inotify_add_watch");
        return;
      }
    
      watched_dir *wd = new_watched_dir(watch_fd, current_depth, base_path);
    
      LIST_INSERT_HEAD(&watched_dirs, wd, links);
    
      DIR *dir = opendir(base_path);
      if (!dir) {
        perror("opendir");
        return;
      }
    
      struct dirent *entry;
      while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
          continue;
        }
    
        char path[PATH_MAX];
        snprintf(path, PATH_MAX, "%s/%s", base_path, entry->d_name);
    
        struct stat statbuf;
        if (stat(path, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
          add_watch_recursive(inotify_fd, path, depth, current_depth + 1);
        }
      }
    
      closedir(dir);
    }
    
    int main(int argc, char *argv[]) {
      if (argc < 3) {
        fprintf(stderr, "Usage: %s <path> <depth>\n", argv[0]);
        return EXIT_FAILURE;
      }
    
      LIST_INIT(&watched_dirs);
    
      const char *path = argv[1];
      int depth = atoi(argv[2]);
    
      if (depth < 0) {
        fprintf(stderr, "Error: Depth must be a non-negative integer\n");
        return EXIT_FAILURE;
      }
    
      // Print the path to verify it
      printf("Attempting to open directory: %s\n", path);
    
      // Check if the directory exists
      struct stat statbuf;
      if (stat(path, &statbuf) != 0) {
        perror("stat");
        return EXIT_FAILURE;
      }
    
      // Check if it is a directory
      if (!S_ISDIR(statbuf.st_mode)) {
        fprintf(stderr, "Error: %s is not a directory\n", path);
        return EXIT_FAILURE;
      }
    
      // Initialize inotify
      int inotify_fd = inotify_init();
      if (inotify_fd < 0) {
        perror("inotify_init");
        return EXIT_FAILURE;
      }
    
      // Add watch on the specified directory and its subdirectories up to the
      // specified depth
      add_watch_recursive(inotify_fd, path, depth, 0);
    
      printf("Watching directory: %s and its subdirectories up to depth: %d\n",
             path, depth);
    
      // Buffer to store inotify events
      char buffer[EVENT_BUF_LEN];
    
      // Event loop
      while (1) {
        int length = read(inotify_fd, buffer, EVENT_BUF_LEN);
    
        if (length < 0) {
          perror("read");
          exit(EXIT_FAILURE);
        }
    
        int i = 0;
        while (i < length) {
          struct inotify_event *event = (struct inotify_event *)&buffer[i];
          watched_dir *d = find_dir_from_wd(event->wd);
    
          if (d == NULL) {
            fprintf(stderr, "unknown watch descriptor %d\n", event->wd);
            exit(EXIT_FAILURE);
          }
    
          if (event->len) {
            char event_path[PATH_MAX];
            snprintf(event_path, PATH_MAX, "%s/%s", d->path, event->name);
    
            if (event->mask & IN_CREATE) {
              if (event->mask & IN_ISDIR) {
                printf("Directory created: %s\n", event_path);
                // Add watch to the new directory if within the specified depth
                add_watch_recursive(inotify_fd, event_path, depth, d->depth + 1);
                dump_watch_list();
              } else {
                printf("File created: %s\n", event_path);
              }
            } else if (event->mask & IN_DELETE) {
              if (event->mask & IN_ISDIR) {
                printf("Directory deleted: %s\n", event_path);
              } else {
                printf("File deleted: %s\n", event_path);
              }
            }
          }
          i += EVENT_SIZE + event->len;
        }
      }
    
      // Clean up
      close(inotify_fd);
    
      return EXIT_SUCCESS;
    }
    

    Left as an exercise for the reader:

    1. What happens when you remove a watched directory? You need to remove the inotify watch (inotify_rm_watch) and remove the watched directory from watched_dirs (LIST_REMOVE). You will need to handle the IN_DELETE_SELF event.

    2. What happens when you remove the top-level watchpoint? Your code should exit when there is nothing left to watch. LIST_EMPTY might be useful.