Wake me up before you clo-close!
Workload: 70 lines of code
Important System-Calls: inotify_init(2), inotify_add_watch(2), read(2)
As we already discussed, the ELF's workbench area is really dynamic and a lot of synchronization is going on. But not everything is that entangled and interactive. For some tasks, ELFs just want to wait until some other ELF has put something into the warehouses and become active afterwards. In previous years, ELFs just visited the warehouse from time to time (for example daily) to see if the desired object (e.g., a plank of hard wood) was put into the warehouse. As you imagine, this is time consuming and it also induces latency. If the hard wood arrived shortly after you visited the warehouse, you would only notice on the next day. Wouldn't it be great if the warehouse would inform us directly about the arrival of the hard wood?
inotify - Get informed about changes
As yesterday's task was surely very challenging, we will have a much easier day today. Also, because its my birthday today :-). Today, we will look at the famous inotify(7) interface. With this interface, applications can watch for changes within the file system, so you can write programs that monitor a directory and perform an action if the directory or its contents were changed (by someone).
And actually, this very kernel interface changed my life for the
better: In former times, the tail(1) program was poll based
and checked its monitored files every second with the -f
(for
follow) flag. However, this lead to a 1-second delay until lines that
my other programs wrote to the log file appeared on the terminal. With
inotify, tail -f
nowadays gets informed on the file change and
immediately can present me with the line on my terminal. This saves me
a lot of adrenaline and frustration that I've had with computers. What a
gift for my birthday!
OK, so what is special about inotify? Today, I will not talk too much
about inotify itself as it is wonderfully described in inotify(7). But I will talk about the usage of file descriptors to
get a hold of in-kernel things (we use the word object
to sound
fancy). When we create a new inotify object in the kernel with
inotify_init(2), we get a file descriptor as a return
value. And we are supposed to use read(2) to retrieve
inotify events. Isn't that wild. The inotify object is surely no file
that has a beginning or an end. Nevertheless, it is a stream of events
that we want to retrieve. Additionally, the read system call blocks if
there is no event available for consumption. Therefore, using the
read()
operation on this event stream is only weird on the first
glance but actually fits if you think about it.
But, there is something weird about these inotify-files
: We cannot read byte-wise from this event stream. So a
read(inotify_fd, buffer, /*size = */ 1)
will always return -1
. The kernel will only deliver complete events
to user-space, which makes the in-kernel implementation much easier as
the kernel does not has to track half-read events. Therefore, we also
have to use a buffer that is large enough to consume at least one
event.
Tasks
Write a program that watches the current working directory ("."
) and
prints all IN_OPEN | IN_ACCESS | IN_CLOSE
events. Print each event
as an individual line.
Start your program in your 05-inotify
directory and compare its
output to the output of the strace cat Makefile
call, which traces
all system calls of the cat
program while it reads the file
Makefile
from disk. You should be able to correlate the
open()/read()/close()
system calls to inotify events that you print
on the terminal.
My outputs look like this:
$ ./inotify ./Makefile [open] ./Makefile [access] ./Makefile [close_nowrite,close]
And in another terminal, while ./inotify
is running and listening to events.
$ strace cat Makefile openat(AT_FDCWD, "Makefile", O_RDONLY) = 3 ... read(3, "inotify: inotify.c\n\tgcc inotify."..., 131072) = 151 .... close(3) = 0