summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/webserver/inotify.py
blob: a2b5193daab80f293a71e2a6b1a0cb5b3036475e (plain) (blame)
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import struct
import ctypes
import os, os.path
import errno
import sys
import tornado.ioloop
import tornado.platform.posix

# The below class is from pyinotify, released under the MIT license
# Copyright (c) 2010 Sebastien Martini <seb@dbzteam.org>
class _CtypesLibcINotifyWrapper():
    def __init__(self):
        self._libc = None
        self._get_errno_func = None

    def init(self):
        assert ctypes
        libc_name = None
        try:
            libc_name = ctypes.util.find_library('c')
        except (OSError, IOError):
            pass  # Will attempt to load it with None anyway.

        if sys.version_info >= (2, 6):
            self._libc = ctypes.CDLL(libc_name, use_errno=True)
            self._get_errno_func = ctypes.get_errno
        else:
            self._libc = ctypes.CDLL(libc_name)
            try:
                location = self._libc.__errno_location
                location.restype = ctypes.POINTER(ctypes.c_int)
                self._get_errno_func = lambda: location().contents.value
            except AttributeError:
                pass

        # Eventually check that libc has needed inotify bindings.
        if (not hasattr(self._libc, 'inotify_init') or
            not hasattr(self._libc, 'inotify_add_watch') or
            not hasattr(self._libc, 'inotify_rm_watch')):
            return False

        self._libc.inotify_init.argtypes = []
        self._libc.inotify_init.restype = ctypes.c_int
        self._libc.inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p,
                                                 ctypes.c_uint32]
        self._libc.inotify_add_watch.restype = ctypes.c_int
        self._libc.inotify_rm_watch.argtypes = [ctypes.c_int, ctypes.c_int]
        self._libc.inotify_rm_watch.restype = ctypes.c_int
        return True

    def _get_errno(self):
        if self._get_errno_func is not None:
            return self._get_errno_func()
        return None

    def _inotify_init(self):
        assert self._libc is not None
        return self._libc.inotify_init()

    def _inotify_add_watch(self, fd, pathname, mask):
        assert self._libc is not None
        pathname = ctypes.create_string_buffer(pathname)
        return self._libc.inotify_add_watch(fd, pathname, mask)

    def _inotify_rm_watch(self, fd, wd):
        assert self._libc is not None
        return self._libc.inotify_rm_watch(fd, wd)

class DirectoryWatcher(object):
    CREATE = 0x100 # IN_CREATE
    DELETE = 0x200 # IN_DELETE

    def __init__(self, io_loop=None):
        self.io_loop = io_loop or tornado.ioloop.IOLoop.instance()
        self.inotify = _CtypesLibcINotifyWrapper()
        self.enabled = self.inotify.init()
        if self.enabled:
            self.fd = self.inotify._inotify_init()
            tornado.platform.posix._set_nonblocking(self.fd)
            tornado.platform.posix.set_close_exec(self.fd)
            self.io_loop.add_handler(self.fd, self._handle_read,
                                     self.io_loop.ERROR | self.io_loop.READ)
        self.handlers = dict()
        self.paths = dict()
        self.buffer = bytes()

    def watch(self, path, handler):
        if self.enabled:
            w = self.inotify._inotify_add_watch(self.fd, path,
                                                DirectoryWatcher.CREATE |
                                                DirectoryWatcher.DELETE)
            self.handlers[w] = handler
            self.paths[w] = path

    def _handle_read(self, fd, event):
        if event & self.io_loop.ERROR:
            return

        try:
            bufsize = 1024
            data = os.read(self.fd, bufsize)
            i = 0
            while i < len(data):
                (w,) = struct.unpack_from("@i", data, i)
                i += struct.calcsize("@i")
                (mask, cookie, l) = struct.unpack_from("=III", data, i)
                i += 3*4
                (name,) = struct.unpack_from("%ds" % l, data, i)
                while name[-1] == "\x00":
                    name = name[:-1]
                i += l
                self.handlers[w](os.path.join(self.paths[w], name), mask)
        except OSError as e:
            if e.errno in (errno.EWOULDBLOCK, errno.EAGAIN):
                return
            else:
                raise