A basic inotify utility for Linux 2.6.13+
a.k.a. Finding out when a file is accessed
I am using a rather daunting http server called Apache and have some alias directives set up. When I try to access these paths, however, I am confronted with a rude 4xx error.
If this was a smaller, more reasonable program, strace would come to mind. But the amount of data that apache would spew I'm sure could be used for a good laugh at hackathons, even with a context sensitive grep (-A and -B, look into it).
lsof loses here too; it doesn't know about file access history.
gdb may be a winner if I want to attach to the process, break on open, and then go from there. But I'm not a masochist like that, I swear.
inotify
(It's not a calendar app from Apple)
You've probably never heard of inotify unless you follow kernel news or are a +20 computer wizard that casts prosaic spells with every meta-command sequence.
However, if you are a mere mortal and have not heard of it yet, then check this:
inotify will tell you when a file has been accessed, modified, deleted etc.
It is so close to awesome, but unfortunately, it's just a kernel routine. This means you need to write program in order to use it.
I was hoping that a command line wrapper would exist, similar to how there is a command line version of the stat system call.
Psst! I actually found an extensive tool suite while researching this article. But hopefully, my simpleton implementation will be useful to more then just me. :-p
The Plan
(Always spec before you code, for the love of bob)
My usage requirement is to be able to run inotify with a file to watch, like so:
$ inotify /file/of/deep/interest
Then, if in another terminal, I do the following:
$ cat /file/of/deep/interest
I can switch back to my inotify window and see stuff on the screen. Hopefully, I will even be notified of what type of access occured.
"Pshh! This can't be to hard", I scoff with a grin of ignorant confidence.
The Code
Linux Journal did a nice writeup a while back on inotify. Since my requirements were simple, I just used a blocked read call, as described early on in the article.
I used much of the code from the article as my base. I've commented the purpose of my code below.
You can get the source code, with makefile here.
#include <sys/inotify.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
/* size of the event structure, not counting name */
#define EVENT_SIZE (sizeof (struct inotify_event))
/* reasonable guess as to size of 1024 events */
#define BUF_LEN (1024 * (EVENT_SIZE + 16))
char buf[BUF_LEN];
// We listen for all the events and do so in a
// one shot way. After we are done we can just
// reregister.
#define ARGS IN_ONESHOT | IN_ALL_EVENTS
// We create a map with the name of the bits
// mapped to the bitfields as specified in the
// inotify man page.
//
// If you haven't seen anything like this, it will
// allow us to iterate through the array and test each
// bit.
//
// If it's successful, then we print out the desc. The
// code that does this is below.
struct {
int bit;
char *desc;
} map[] = {
{ IN_ACCESS, "IN_ACCESS" },
{ IN_ATTRIB, "IN_ATTRIB" },
{ IN_CLOSE_WRITE, "IN_CLOSE_WRITE" },
{ IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE" },
{ IN_CREATE, "IN_CREATE" },
{ IN_DELETE, "IN_DELETE" },
{ IN_DELETE_SELF, "IN_DELETE_SELF" },
{ IN_MODIFY, "IN_MODIFY" },
{ IN_MOVE_SELF, "IN_MOVE_SELF" },
{ IN_MOVED_FROM, "IN_MOVED_FROM" },
{ IN_MOVED_TO, "IN_MOVED_TO" },
{ IN_OPEN, "IN_OPEN" }
};
// Since the control_c function is called on a signal,
// the easiest way to make the file descriptors accessible
// to the control_c function is to make them global.
//
// According to the man page of inotify_rm_watch, the watch
// descriptor should be a uint32_t, so it has been made so below.
int g_fd;
uint32_t g_wd;
void control_c(int ignored) {
printf("Removing watch\n");
inotify_rm_watch(g_fd, g_wd);
_exit (0);
}
int main(int argc, char*argv[]){
int ret,
len,
ix,
i,
mapLen = sizeof(map) / (sizeof(int) + sizeof(char*));
// Get the command line argument as the file to watch
argv++;
if(*argv == 0) {
printf ("No file name specified\n");
return -1;
}
// Since we block on a read, we need some other way of cleaning up
// our watch when the user wants to exit.
//
// I usually just do control+c to exit my program, so we listen for
// that signal and we deregister the watch there.
signal(SIGINT, control_c);
g_fd = inotify_init();
if (g_fd < 0) {
perror ("inotify_init");
}
g_wd = inotify_add_watch(g_fd,
*argv,
ARGS);
if (g_wd < 0) {
perror ("inotify_add_watch");
}
while(1) {
len = read (g_fd, buf, BUF_LEN);
if (len > 0) {
i = 0;
while (i < len) {
struct inotify_event *event;
event = (struct inotify_event *) &buf[i];
// Output type of access by using the map above
for (ix = 0; ix < mapLen; ix++) {
if(event->mask & map[ix].bit) {
printf("%s ", map[ix].desc);
}
}
if (event->len) {
printf ("name=%s", event->name);
}
i += EVENT_SIZE + event->len;
}
printf("\n");
fflush(0);
// Saving files at least in vim seems to temporarily remove
// the file completely, and then write a new file with the
// same name.
//
// This means that there is some blackout period where the
// file actually does not exist. This little sleep here
// is a naive way of avoiding this problem. However, it
// appears to work well.
usleep(50000);
// Since are ARGS specifies ONE_SHOT, we need to reregister
// the watch each time. This is fine.
g_wd = inotify_add_watch(g_fd,
*argv,
ARGS);
if (g_wd < 0) {
perror ("inotify_add_watch");
}
}
}
}