ActiveState Code

Recipe 576375: Low level inotify wrapper


This module is meant to be as simple and straightforward as it gets.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
'''
    Low level inotify wrapper
'''

from os import read, close
from struct import unpack
from fcntl import ioctl
from termios import FIONREAD
from time import sleep
from ctypes import cdll, c_int, POINTER
from errno import errorcode

libc = cdll.LoadLibrary('libc.so.6')
libc.__errno_location.restype = POINTER(c_int)
def geterr(): return errorcode[libc.__errno_location().contents.value]

class Inotify(object):
    def __init__(self):
        self.fd = libc.inotify_init()
        if self.fd == -1:
            print 'inotify INIT err :', geterr()
            raise OSError()
    def read(self):
        size_int = c_int()
        while ioctl(self.fd, FIONREAD, size_int)==-1: sleep(1)
        size = size_int.value
        data = read(self.fd, size)
        deb = 0
        while deb < size:
            fin = deb+16
            wd, mask, cookie, name_len = unpack('iIII', data[deb:fin])
            deb, fin = fin, fin+name_len
            name = unpack('%ds' % name_len, data[deb:fin])
            name = name[0].rstrip('\0')
            deb = fin
            yield wd, mask, cookie, name
    def add_watch(self, path, mask):
        wd = libc.inotify_add_watch(self.fd, path, mask)
        if wd == -1: print 'inotify ADD err :', geterr()
        return wd
    def rm_watch(self, wd):
        ret = libc.inotify_rm_watch(self.fd, wd)
        if ret == -1: print 'inotify RM err :', geterr()
    def close(self):
        close(self.fd)

FLAGS = {
    'ACCESS'      : 0x00000001, # IN_ACCESS
    'MODIFY'      : 0x00000002, # IN_MODIFY
    'ATTRIB'      : 0x00000004, # IN_ATTRIB
    'WRITE'       : 0x00000008, # IN_CLOSE_WRITE
    'CLOSE'       : 0x00000010, # IN_CLOSE_NOWRITE
    'OPEN'        : 0x00000020, # IN_OPEN
    'MOVED_FROM'  : 0x00000040, # IN_MOVED_FROM
    'MOVED_TO'    : 0x00000080, # IN_MOVED_TO
    'CREATE'      : 0x00000100, # IN_CREATE
    'DELETE'      : 0x00000200, # IN_DELETE
    'DELETE_SELF' : 0x00000400, # IN_DELETE_SELF
    'MOVE_SELF'   : 0x00000800, # IN_MOVE_SELF
    'UNMOUNT'     : 0x00002000, # IN_UNMOUNT
    'Q_OVERFLOW'  : 0x00004000, # IN_Q_OVERFLOW
    'IGNORED'     : 0x00008000, # IN_IGNORED
    'ONLYDIR'     : 0x01000000, # IN_ONLYDIR
    'DONT_FOLLOW' : 0x02000000, # IN_DONT_FOLLOW
    'MASK_ADD'    : 0x20000000, # IN_MASK_ADD
    'ISDIR'       : 0x40000000, # IN_ISDIR
    'ONESHOT'     : 0x80000000, # IN_ONESHOT
}

def mask_str(mask):
    return ' | '.join(name for name, val in FLAGS.items() if val & mask)

Discussion

This should work out of the box on any modern distro.

This module is not intended to be used directly, it should be used to build higher level modules, like the one you can see here

Comments

  1. 1. At 3:41 a.m. on 27 apr 2009, Sok Ann Yap said:

    For some reason, this has stopped working for me since kernel 2.6.28 (Gentoo machines), with the following exception:

    Exception in thread Thread-2:                                                                                                                     
    Traceback (most recent call last):                                                                                                                
      File "/usr/lib64/python2.5/threading.py", line 486, in __bootstrap_inner                                                                        
        self.run()                                                                                                                                    
      File "/usr/lib64/python2.5/threading.py", line 446, in run                                                                                      
        self.__target(*self.__args, **self.__kwargs)                                                                                                  
      File "/path/to/watch.py", line 67, in _push                                              
        for wd, mask, cookie, name in self.inotify.read():                                                                                            
      File "/path/to/inotify.py", line 28, in read                                             
        data = read(self.fd, size)                                                                                                                    
    OSError: [Errno 22] Invalid argument
    

    I need to apply the following patch to get it to work again:

    --- inotify.py.orig
    +++ inotify.py
    @@ -7,7 +7,7 @@
     from fcntl import ioctl
     from termios import FIONREAD
     from time import sleep
    -from ctypes import cdll, c_int, POINTER
    +from ctypes import cdll, create_string_buffer, c_int, POINTER
     from errno import errorcode
    
     libc = cdll.LoadLibrary('libc.so.6')
    @@ -24,7 +24,9 @@
             size_int = c_int()
             while ioctl(self.fd, FIONREAD, size_int)==-1: sleep(1)
             size = size_int.value
    -        data = read(self.fd, size)
    +        buf = create_string_buffer(size)
    +        libc.read(self.fd, buf, size)
    +        data = buf.raw
             deb = 0
             while deb < size:
                 fin = deb+16
    

Sign in to comment