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.
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:
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.
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.