Login Register






Thread Rating:
  • 0 Vote(s) - 0 Average


Tutorial inotify part 2: recursion filter_list
Author
Message
inotify part 2: recursion #1
If you haven't read the first part of the tutorial, do so here.

Watching single directories is nice, but in many cases doesn't quite cut it. For example, if someone was building a Gulp-like build system, watching new directories created inside existing ones would be a must. We can use the program we created in the last part (full code at the bottom, in the spoiler) and tack on the new functionality.

To start, we need to add three more headers: "stdlib.h", "string.h", and "dirent.h", to our program. After doing so, our preprocessor section should look something like this.
Code:
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/inotify.h>

#define MAX_EVENTS 256
#define E_SIZE (sizeof (struct inotify_event))
#define BUF_LENGTH MAX_EVENTS*(E_SIZE+PATH_MAX)

#define target_ftype(e) (e->mask&IN_ISDIR?"directory":"file")

Next, we need a function to find and add watchers to subdirectories. We can do that with the following, which replaces the add_watch_dir() function from our original example.
Code:
void recurse_watch_dirs(int fd,char *root,uint32_t mask){
    char abs_dirname[PATH_MAX]; // absolute path (from system root: "/")
    struct dirent *entry; // directory entry
    DIR *dptr; // directory handle
    int wd; // inotify watch descriptor

    // open root directory and report/exit on errors
    if(!(dptr=opendir(root))){
        fprintf(stderr,"Failed to open directory %s: %s\n",root,strerror(errno));
        exit(1);
    }

    // add directory watch to the inotify file descriptor and check for errors
    if((wd=inotify_add_watch(fd,root,mask))==-1)
        fprintf(stderr,"Failed to watch directory %s: %s\n",root,strerror(errno));
    else
        printf("Watching directory %s\n",root);

    // iterate directory entries
    while((entry=readdir(dptr))){
        // skip if entry is not a directory or matches '.'/'..'
        if(entry->d_type!=DT_DIR||!strcmp(entry->d_name,".")||!strcmp(entry->d_name,".."))
            continue;

        // concatenate root + directory name
        strcpy(abs_dirname,root);
        strcat(abs_dirname,entry->d_name);

        // call function recursively
        recurse_watch_dirs(fd,abs_dirname,mask);
    }

    // close directory
    closedir(dptr);
}

To use this function instead of the original add_watch_dir(), we need to change some things in main(). First, we have to declare a "root" string variable denoting the root watch directory and give it a size of PATH_MAX, keeping with how we managed path names thus far.
Next, after the argument length check ("if(argc<2)"), we need to add the following to copy a well-formed (ending with "/") path string to our new root string.
Code:
strcpy(root,argv[1]); // copy argument to root
// ensure root ends with "/"
if(root[strlen(root)-1]!='/') strcat(root,"/");
Finally, change the remainder of main() (the add_watch_dir(), handle_event() and cleanup calls) to the ones below.
Code:
wd=recurse_watch_dirs(fd,root,IN_CREATE|IN_MODIFY|IN_DELETE);
while(1) handle_event(fd,buf);

close(fd);
return 0;

Okay, so it works (code available here), but it reports only the file name, not the relative path.
To resolve this, we need to either use C++ for its standard hashmap implementation, or implement a simple lookup table ourselves. To do this, we first need to define a wd/path "pair" struct (and its current size).
Code:
struct lookup_pair{
    int wd;
    char *path;
};

typedef struct lookup_pair pair_t;
pair_t *wd_lookup[MAX_EVENTS];
size_t lookup_size;
Now when we call inotify_add_watch() in recurse_watch_dirs(), we can add an entry to our new lookup table.
Code:
// add directory watch to the inotify file descriptor and check for errors
if((wd=inotify_add_watch(fd,root,mask))==-1){
    fprintf(stderr,"Failed to watch directory %s: %s\n",root,strerror(errno));
}else{
    printf("Watching directory %s\n",root);
    wd_lookup[lookup_size]=malloc(sizeof (pair_t*));
    wd_lookup[lookup_size]->path=memcpy(malloc(len_root),root,len_root);
    wd_lookup[lookup_size++]->wd=wd;
}

To get a value, we need to iterate through the lookup table until we find a matching "key" (wd). It's not the most effecient, but it's not terrible for 256 items.
Code:
char *get_watch_path(struct inotify_event *event){
    char *path=malloc(sizeof (char)*PATH_MAX);
    size_t i;

    for(i=0;i<lookup_size;i++){
        if(wd_lookup[i]->wd==event->wd){
            strcpy(path,wd_lookup[i]->path);
            strcat(path,event->name);
        }
    }

    return path;
}

Finally, when reporting file events in handle_event(), we can call get_watch_path() to get the full path + file name.
Code:
void handle_event(int fd,char *buf){
    size_t idx=0,length=read(fd,buf,BUF_LENGTH); // buffer index and read length
    struct inotify_event *event; // pointer to event

    // report read() errors
    if(length<0) perror("read");

    // while the index is under the length read from fd
    while(idx<length){
        event=(struct inotify_event*) &buf[idx]; // get event at current index
        if(!event->len) continue; // skip if the event is zero-length

        if(event->mask&IN_CREATE) // check for create flag in mask
            printf("%s %s created\n",target_ftype(event),get_watch_path(event));
        else if(event->mask&IN_MODIFY) // check for modify flag
            printf("%s %s modified\n",target_ftype(event),get_watch_path(event));
        else if(event->mask&IN_DELETE) // check for delete flag
            printf("%s %s deleted\n",target_ftype(event),get_watch_path(event));

        // add event size to idx
        idx+=E_SIZE+event->len;
    }
}

And that pretty much covers the basics! There's quite a bit I intentionally didn't cover, but this should be more than enough to get anyone started. As with everything in C, the manpages will get you a long way if you bother reading them.
As for this code, all examples are available on GitHub; the last one is here.
It's often the outcasts, the iconoclasts ... those who have the least to lose because they
don't have much in the first place, who feel the new currents and ride them the farthest.

Reply





Messages In This Thread
inotify part 2: recursion - by Inori - 08-28-2017, 07:02 PM



Users browsing this thread: 1 Guest(s)