FreePascal Information Logo Friend of FreePascal Compiler Title
Articles with Feedback, FPC News Library, PDF Collection, Mail Lists, Books, Newsgroups, IRC Open online discussion areas Research and Tutorials Tools, Compilers and Utilities Blurbs about us, advertising, etc.
Welcome to the FoFPC Research Notes: Kernel inotify

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.
 Links and Products we find useful



ButtonGenerator.com
Valid XHTML 1.0 Transitional Internet Map
Programmer's Heaven
grat-i-fi-ca-tion - noun
the state of being gratified; great satisfaction.


"Friends of Free Pascal is, in my view, one of the best things to happen to FPC since the compiler first appeared"

Gary Funk
BoardWatch Magazine
Locations of visitors to this page world map hits counter
Copyright 2009 by 3F, LLC. All rights reserved. Worldwide.
Your request was processed by server #3 in 0.649590 secs.

sponsor
This sponsor helps us with our documentation