Kernel inotify
by: G.E. Ozz Nixon Jr.
Published: June 2008
©opyright 2009 by Friends of FPC
Migrating from strictly a Windows developer to cross-platform -- I was constantly
struggling to find similar features in the Linux and Mac OS X kernels. File system
monitoring is something we do not think about, however, you will find yourself
needing to track a folder, or file to know it has been changed. Take this web site
for example, when I am on the server documenting my research findings, I need the
cluster of web servers to know a file is changing. Some may say, put it into a
centralized database server, however, that becomes a SPOF (Single Point of Failure).
Then of course the next counter would be to cluster the databases with replication.
And even though I would have been an advocate for that statement - I have to say,
ever nano-second it important to our cluster. It just makes more sense for the file
system to replicate so the web server software can do it's caching - which of course
uses inotify also to know the in-memory cache is dirty.
Inotify has many advantages over dnotify, the module that it replaced. With the older
module, a program had to use one file descriptor for each directory that it was
monitoring. This can become a bottleneck since the limit of file descriptors per process
could be reached. The use of file descriptors along with dnotify also proved to be a
problem when using removable media. Devices could not be unmounted since file descriptors
kept the resource busy.
Another drawback of dnotify is the level of granularity, since programmers can only
monitor changes at the directory level. To access detailed information about the
environmental changes that occur when a notification message is sent, a stat structure
must be used; this is considered a necessary evil in that a cache of stat structures has
to be maintained, for every new stat structure generated a comparison is run against the
cached one.
Inotify uses an API that uses fewer file descriptors, allowing programmers to use the
established select and poll interface, rather than the signal notification system used by
dnotify. This also makes integration with existing select-based or poll-based libraries
(like GLib) easier.
The inotify.pp source
unit inotify;
interface
uses
SysCall, UnixType;
{$IFDEF FPC}
{$PACKRECORDS C}
{$ENDIF}
type
uint32_t = cuint32;
{Structure describing an inotify event.}
inotify_event = record
wd: longint; // Watch descriptor.
mask: uint32_t; // Watch mask.
cookie: uint32_t; // Cookie to synchronize two events.
len: uint32_t; // Length (including NULs) of name.
name: char; // Name.
end;
{Pointer to structure describing an inotify event.}
pinotify_event = ^inotify_event;
const
{Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.}
IN_ACCESS = $00000001; {File was accessed}
IN_MODIFY = $00000002; {File was modified}
IN_ATTRIB = $00000004; {Metadata changed}
IN_CLOSE_WRITE = $00000008; {Writtable file was closed}
IN_CLOSE_NOWRITE = $00000010; {Unwrittable file closed}
IN_CLOSE = IN_CLOSE_WRITE or IN_CLOSE_NOWRITE; {Close}
IN_OPEN = $00000020; {File was opened}
IN_MOVED_FROM = $00000040; {File was moved from X}
IN_MOVED_TO = $00000080; {File was moved to Y}
IN_MOVE = IN_MOVED_FROM or IN_MOVED_TO; {Moves}
IN_CREATE = $00000100; {Subfile was created}
IN_DELETE = $00000200; {Subfile was deleted}
IN_DELETE_SELF = $00000400; {Self was deleted}
IN_MOVE_SELF = $00000800; {Self was moved}
{ Events sent by the kernel}
IN_UNMOUNT = $00002000; {Backing fs was unmounted}
IN_Q_OVERFLOW = $00004000; {Event queued overflowed}
IN_IGNORED = $00008000; {File was ignored}
{ Special flags. }
IN_ONLYDIR = $01000000; {Only watch the path if it is a directory}
IN_DONT_FOLLOW = $02000000; {Do not follow a sym link}
IN_MASK_ADD = $20000000; {Add to the mask of an already existing watch}
IN_ISDIR = $40000000; {Event occurred against dir}
IN_ONESHOT = $80000000; {Only send event once}
{All events which a program can wait on}
IN_ALL_EVENTS =
((((((((((IN_ACCESS or IN_MODIFY) or IN_ATTRIB) or IN_CLOSE_WRITE) or
IN_CLOSE_NOWRITE) or IN_OPEN) or IN_MOVED_FROM) or IN_MOVED_TO) or IN_CREATE) or
IN_DELETE) or IN_DELETE_SELF) or IN_MOVE_SELF;
{Create and initialize inotify instance}
function inotify_init: LongInt;
{Add watch of object NAME to inotify instance FD. Notify about events specified by MASK}
function inotify_add_watch(__fd: LongInt; __name: PChar; __mask: uint32_t): LongInt;
{Remove the watch specified by WD from the inotify instance FD}
function inotify_rm_watch(__fd: LongInt; __wd: uint32_t): LongInt;
var
IsGoodKernelVersion: Boolean = False;
implementation
uses
SysUtils, BaseUnix, StrUtils;
function CheckKernelVersion: Boolean;
var
KernelName: TUtsName;
sRelease: AnsiString;
I, iVersion,
iRelease, iPatch: Integer;
begin
fpUname(KernelName);
sRelease:= KernelName.Release;
iVersion:= StrToIntDef(Copy2SymbDel(sRelease, '.'), 0);
iRelease:= StrToIntDef(Copy2SymbDel(sRelease, '.'), 0);
for I:= 1 to Length(sRelease) do
if not (sRelease[I] in ['0'..'9']) then Break;
iPatch:= StrToIntDef(LeftStr(sRelease, I-1), 0);
CheckKernelVersion:= (iVersion >= 2) and (iRelease >= 6) and (iPatch >= 13);
end;
function inotify_init: LongInt;
begin
inotify_init := -1;
if IsGoodKernelVersion then
inotify_init := do_syscall(syscall_nr_inotify_init);
end;
function inotify_add_watch(__fd: LongInt; __name: PChar; __mask: uint32_t): LongInt;
begin
inotify_add_watch := -1;
if IsGoodKernelVersion then
inotify_add_watch := do_syscall(syscall_nr_inotify_add_watch,
TSysParam(__fd), TSysParam(__name), TSysParam(__mask));
end;
function inotify_rm_watch(__fd: LongInt; __wd: uint32_t): LongInt;
begin
inotify_rm_watch := -1;
if IsGoodKernelVersion then
inotify_rm_watch := do_syscall(syscall_nr_inotify_rm_watch,
TSysParam(__fd), TSysParam(__wd));
end;
initialization
IsGoodKernelVersion:= CheckKernelVersion;
end.
The interface is very simple and reports in near real-time. Inotify is initialized via
inotify_init() system call, which instantiates an inotify instance within the kernel. On
failure inotify_init() returns negative one and sets errno as appropriate. The most
common errno values are EMFILE and ENFILE, which signify that the per user and the system
wide open file limit was reached. These settings are controlled by 3 files in /proc/sys/fs/inotify/
max_queued_events, max_user_instances, and max_user_watches.
my test application
{MAC and FREEBSD use KPoll}
{.$DEFINE USE_SELECT}
uses
termio,
baseUnix,
inotify;
const
file_to_watch = '/var/log/syslog';
notify_filers = IN_ALL_EVENTS;//IN_ACCESS or IN_MODIFY;
var
instance_handle:longint;
notify_handle:longint;
message_size:longint;
buffer:pchar;
tmp:longint;
evnt:pinotify_event;
filename:string;
{$IFDEF USE_SELECT}
_time:timeval;
rfds:tfdset;
ret:longint;
{$ENDIF}
function decodeMask(Mask:Longint):string;
var
Rslt:String;
begin
Rslt:='';
if (Mask and IN_ACCESS)=IN_ACCESS then
Rslt:=Rslt+' File was accessed.';
if (Mask and IN_MODIFY)=IN_MODIFY then
Rslt:=Rslt+' File was modified.';
if (Mask and IN_ATTRIB)=IN_ATTRIB then
Rslt:=Rslt+' Attribute was changed.';
if (Mask and IN_CLOSE_WRITE)=IN_CLOSE_WRITE then
Rslt:=Rslt+' Writtable file was closed.';
if (Mask and IN_CLOSE_NOWRITE)=IN_CLOSE_NOWRITE then
Rslt:=Rslt+' Unwrittable file was closed.';
if (Mask and IN_CLOSE)=IN_CLOSE then
Rslt:=Rslt+' File was closed.';
if (Mask and IN_OPEN)=IN_OPEN then
Rslt:=Rslt+' File was opened.';
if (Mask and IN_MOVED_FROM)=IN_MOVED_FROM then
Rslt:=Rslt+' File was moved from ';
if (Mask and IN_MOVED_TO)=IN_MOVED_TO then
Rslt:=Rslt+' File was moved to ';
if (Mask and IN_MOVE)=IN_MOVE then
Rslt:=Rslt+' File is moving';
if (Mask and IN_CREATE)=IN_CREATE then
Rslt:=Rslt+' Subfile was created';
if (Mask and IN_DELETE)=IN_DELETE then
Rslt:=Rslt+' Subfile was deleted';
if (Mask and IN_DELETE_SELF)=IN_DELETE_SELF then
Rslt:=Rslt+' Self was deleted';
if (Mask and IN_MOVE_SELF)=IN_MOVE_SELF then
Rslt:=Rslt+' Self was moved';
if (Mask and IN_UNMOUNT)=IN_UNMOUNT then
Rslt:=Rslt+' Filesystem was unmounted';
if (Mask and IN_Q_OVERFLOW)=IN_Q_OVERFLOW then
Rslt:=Rslt+' Event queued overflowed';
if (Mask and IN_IGNORED)=IN_IGNORED then
Rslt:=Rslt+' File was ignored';
if (Mask and IN_ONLYDIR)=IN_ONLYDIR then
Rslt:=Rslt+' Only watch the path if it is a directory';
if (Mask and IN_DONT_FOLLOW)=IN_DONT_FOLLOW then
Rslt:=Rslt+' Do not follow a sym link';
if (Mask and IN_MASK_ADD)=IN_MASK_ADD then
Rslt:=Rslt+' Add to the mast of an already existing watch';
if (Mask and IN_ISDIR)=IN_ISDIR then
Rslt:=Rslt+' Event occurred against dir';
if (Mask and IN_ONESHOT)=IN_ONESHOT then
Rslt:=Rslt+' Only send event once';
decodeMask:=Rslt;
end;
begin
If (IsGoodKernelVersion) then begin
instance_handle := inotify_init;
If (instance_handle<>-1) then begin
System.Writeln('Instance #',instance_handle);
message_size := 0;
if (paramcount=0) then filename:=file_to_watch
else filename:=paramstr(1);
notify_handle := inotify_add_watch(instance_handle,
pchar(file_to_watch), notify_filers);
System.Writeln('Monitoring ',filename,
' for changes on ',notify_handle);
while true do begin
{$IFDEF USE_SELECT}
_time.tv_sec:=360; // 6 minutes //
_time.tv_usec:=0;
fpFD_Zero(rfds);
fpFD_Set(instance_handle, rfds);
ret := fpSelect(instance_handle+1, @rfds, nil, nil, longint(@_time));
if (ret<0) then begin
System.Writeln('Select Failed');
Halt;
end
else if (ret=0) then begin
System.Writeln('Select timed out');
Halt;
end;
{$ENDIF}
// we have data //
while (message_size=0) do
if fpioctl(instance_handle, fionread, @message_size) = -1 then begin
System.Writeln('fIOCtrl failed! Aborting!');
Halt;
end;
System.Writeln('Change detected, message size is ',
message_size,' dumping report:');
GetMem(Buffer, message_size);
fpRead(instance_handle, buffer, message_size);
tmp:=0;
while tmp < message_size do begin
evnt:=pinotify_event(buffer+tmp);
System.Writeln('Wd:',evnt^.Wd,' Mask:',decodeMask(evnt^.mask),
' Cookie:',evnt^.cookie,' Len:',evnt^.Len,
' Name: ',pchar(@evnt^.Name));
Inc(tmp, evnt^.len+16);
end;
FreeMem(Buffer, message_size);
message_size:=0;
end;
inotify_rm_watch(instance_handle, notify_handle);
fpClose(instance_handle);
end
else System.Writeln('Failed to initialize the inotify API');
end
else begin
System.Writeln('Your Kernel version does not contain support for inotify.');
System.Writeln('Support was introduced in August 2005, merged in 2.6.13 kernels.');
end;
end.
sample output
Instance #3
Monitoring /var/log/syslog for changes on 1
Change detected, message size is 48 dumping report:
Wd:1 Mask: File was opened. Cookie:0 Len:0 Name:
Wd:1 Mask: File was accessed. Cookie:0 Len:0 Name:
Wd:1 Mask: Unwrittable file was closed. Cookie:0 Len:0 Name:
Change detected, message size is 16 dumping report:
Wd:1 Mask: File was modified. Cookie:0 Len:0 Name: rver
Change detected, message size is 16 dumping report:
Wd:1 Mask: File was modified. Cookie:0 Len:0 Name: rver
G.E. Ozz Nixon Jr.