tcl-inotify

NAME
SYNOPSIS
DESCRIPTION
EXAMPLE
OUTPUT
EXAMPLE
AUTHOR
COPYRIGHT

NAME

tcl−inotify − File−system notification service for Tcl

SYNOPSIS

package require inotify

inotify create watch handler

inotify info

watch add pathname flags

watch del pathname

watch queue

watch read

watch info

rename watch {}

DESCRIPTION

This is a Tcl extension that provides an interface to the inotify file system notification service of the Linux kernel.

Inotify is a Linux kernel subsystem that provides file system event notifications. It was written by John McCutchan with help from Robert Love and later Amy Griffis to replace dnotify. It was included in the mainline kernel from release 2.6.13, and could be compiled into 2.6.12 and possibly earlier releases by use of a patch. Its function is essentially an extension to filesystems to notice changes to the filesystem, and report those changes to applications.

inotify create watch handler

Creates an inotify instance in the form of a new Tcl command, named watch. Whenever there are events for this instance, handler procedure is executed with a single argument, the instanceId. On success the command returns instanceId, otherwise an error is generated that needs to be catched.

inotify info

It returns a list of length n * 3, where n is the number of active instances. Every triplet is comprised by: instanceId handler watch. The command can generate errors that need to be catched.

watch add pathname flags

Adds a watch to the inotify instance that corresponds to watch command. The new watch monitors the pathname object (which can be a file or a directory) for the events specified by flags. On success the command returns watchId, otherwise an error is generated that needs to be catched.

watch del pathname

Removes the watch associated with pathname.The command can generate errors that need to be catched.

watch queue

Returns the bytes in the kernel buffer that are occupied by inotify events which haven’t been processed yet (pending).

watch read

Returns a list of dictionaries, one for each pending event for the inotify instance that corresponds to the watch command. The format of the dictionary is:
watchid idVal flags flagsVal cookie cookieVal filename nameVal
Up to 32 pending events can be returned with a single invocation of ’read’ subcommand. Always use the ’queue’ subcommand to find out if there are more events to be retrieved.

watch info

It returns a list of length n * 3, where n is the number of active instances. Every triplet is comprised by: pathname watchId flags. The command can generate errors that need to be catched.

rename watch {}

Removes all the instance that corresponds to the watch command, and all associated watches.

PARAMETERS

instanceId is a number that uniquely identifies a set of watches that are handled by the same handler procedure.

watchId is a number that uniquely identifies a watch.

handler is a procedure (with optional namespace), that is executed every time there are events in the kernel event queue for the corresponding instanceId. The events can be read in batch by the handler procedure by invoking watch read. The instanceId is provided as an argument to the handler.

cookie is used to associate two different events, one IN_MOVED_FROM and one IN_MOVED_TO that involve the same file/directory.

flags is a string composed of single character event identifiers. The following events are supported:

    Id Name              Description
     n IN_CREATE         File was created.
     r IN_ACCESS         File was read from.
     w IN_MODIFY         File was written to.
     a IN_ATTRIB         File’s metadata (inode or xattr) was changed.
     C IN_CLOSE_WRITE    File was closed (and was open for writing).
     c IN_CLOSE_NOWRITE  File was closed (and was not open for writing).
     o IN_OPEN           File was opened.
     m IN_MOVED_FROM     File was moved away from watch.
     M IN_MOVED_TO       File was moved to watch.
     d IN_DELETE         File was deleted.
     s IN_DELETE_SELF    The watch itself was deleted.

In addition to the above, for event registration only, the following are also supported: (only for the watch add command)

    Id Name              Description
     1 IN_ONESHOT        The watch will be automatically removed during
                         generation of the first event.
     _ IN_CLOSE          IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
     > IN_MOVE           IN_MOVED_FROM | IN_MOVED_TO
     * IN_ALL_EVENTS     Bitwise OR of all events.

In a similar manner, the following are specific for event reporting: (only for the watch read command)

    Id Name              Description
     u IN_UNMOUNT        The backing filesystem was unmounted.
     f IN_Q_OVERFLOW     The inotify queue overflowed.
     i IN_IGNORED        The watch was automatically removed, because the
                         file was deleted or its filesystem was unmounted.
     D IN_ISDIR          The event occurred against a directory.

EXAMPLE

# Monitor a directory for all events, and cause some to trigger
package require inotify

set num 0
proc handler { fd } {
    variable term
    variable num
    puts "____________________________________________"
    puts "Invocation:$num"
    puts "Instance: $fd"
    puts "Remaining:[watch queue]"
    puts "Inotify Info:[inotify info]"
    puts "Watch Info:[watch info]"
    set events [watch read]
    puts "Events (raw):$events0

   # In single−threaded scripts, reading this buffer always gives the data that caused to invoke the handler
    foreach {watchId flags cookie filepath} $events {
      puts "Watch:$watchId"
      puts "Flags:$flags"
      puts "Cookie:$cookie"
      puts "Object:$filepath0
    }
    incr num
    if {$num >= 5} {
        set term 1
    }
}

set fd [inotify create "watch" "::handler"]
puts Created

set wd [watch add [pwd] {*}]
puts Added

watch remove [pwd]
puts Removed

set wd [watch add [pwd] {*}]
puts Added

after 1000 [list exec echo a > test.txt]
after 2000 [list exec mv test.txt delme.txt]
after 3000 [list exec rm delme.txt]
after 4000 [list exec ls]
after 5000 [list set term 1]

set term 0
puts "waiting.."
vwait term

puts "0xiting.."
rename watch {}
rename inotify {}

OUTPUT

Created
Added
Removed
Added
waiting..
____________________________________________
Invocation:     0
Instance:       5
Remaining:      0
Inotify Info:   5 ::handler watch
Watch Info:     /tmp/inotify 2 *
Events (raw):   1 i 0 {}

       Watch:  1
        Flags:  i
        Cookie: 0
        Object:

____________________________________________
Invocation:     1
Instance:       5
Remaining:      0
Inotify Info:   5 ::handler watch
Watch Info:     /tmp/inotify 2 *
Events (raw):   2 n 0 test.txt 2 o 0 test.txt 2 w 0 test.txt 2 C 0 test.txt

       Watch:  2
        Flags:  n
        Cookie: 0
        Object: test.txt

       Watch:  2
        Flags:  o
        Cookie: 0
        Object: test.txt

       Watch:  2
        Flags:  w
        Cookie: 0
        Object: test.txt

       Watch:  2
        Flags:  C
        Cookie: 0
        Object: test.txt

____________________________________________
Invocation:     2
Instance:       5
Remaining:      0
Inotify Info:   5 ::handler watch
Watch Info:     /tmp/inotify 2 *
Events (raw):   2 m 585 test.txt 2 M 585 delme.txt

       Watch:  2
        Flags:  m
        Cookie: 585
        Object: test.txt

       Watch:  2
        Flags:  M
        Cookie: 585
        Object: delme.txt

____________________________________________
Invocation:     3
Instance:       5
Remaining:      0
Inotify Info:   5 ::handler watch
Watch Info:     /tmp/inotify 2 *
Events (raw):   2 d 0 delme.txt

       Watch:  2
        Flags:  d
        Cookie: 0
        Object: delme.txt

____________________________________________
Invocation:     4
Instance:       5
Remaining:      0
Inotify Info:   5 ::handler watch
Watch Info:     /tmp/inotify 2 *
Events (raw):   2 oD 0 {} 2 cD 0 {}

       Watch:  2
        Flags:  oD
        Cookie: 0
        Object:

       Watch:  2
        Flags:  cD
        Cookie: 0
        Object:

exiting..

EXAMPLE

# An efficient tailf implementation using inotify
package require inotify
package provide tailf

namespace eval tailf {
namespace export add rem

array set File2wd {}
array set Wd2info {}

inotify create ::tailf::watch ::tailf::handler

proc getpos {filepath} {
    set fd [open $filepath r]
    seek $fd 0 end
    set pos [tell $fd]
    close $fd
    return $pos
}

proc handler {args} {
    variable File2wd
    variable Wd2info

   while {[watch queue]} {
        set events [watch read]

       foreach {wd flags cookie filepath} $events {
            if {! [info exists Wd2info($wd)]} {
                return
            }

           set diff {}
            set callback [lindex $Wd2info($wd) 1]
            set filepath [lindex $Wd2info($wd) 2]
            set old [lindex $Wd2info($wd) 0]
            set new [getpos $filepath]
            set Wd2info($wd) [lreplace $Wd2info($wd) 0 0 $new]

           if {$new < $old} {
                set old 0
            }

           if {$old != $new} {
                set fd [open $filepath r]
                seek $fd $old
                while {[gets $fd line] >= 0} {
                    append diff "$line" "0
                }
                close $fd
            }

           eval [list $callback $filepath $diff]
        }
    }
    return
}

proc add {filepath callback} {
    variable File2wd
    variable Wd2info

   if {[file exists $filepath] && [file isfile $filepath]} {
        set pos [getpos $filepath]
        set wd [watch add $filepath {w}]
        set File2wd($filepath) $wd
        set Wd2info($wd) [list $pos $callback $filepath]
    } else {
        error "is either not a file, or does not exist"
    }
}

proc rem {filepath} {
    variable File2wd
    variable Wd2info

   if {! [info exists ::tailf::File($filepath)]} {
        error "$filepath is not monitored, or watch has been removed"
    }

   set wd $File2wd($filepath)
    unset Wd2info($wd)
    unset File2wd($filepath)
    watch del $filepath
}

} ;# namespace end

proc puts_hdlr {filepath diff} {
    puts −nonewline $diff
}

::tailf::add "delme.txt" puts_hdlr

set a {}
vwait a

AUTHOR

Alexandros Stergiakis <sterg@kth.se>

COPYRIGHT

Copyright (C) 2008 Alexandros Stergiakis

This program is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.