1# vim:fileencoding=utf-8:noet
2from __future__ import (unicode_literals, division, absolute_import, print_function)
3
4import sys
5import os
6import errno
7import ctypes
8import struct
9
10from ctypes.util import find_library
11
12from powerline.lib.encoding import get_preferred_file_name_encoding
13
14
15__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
16__docformat__ = 'restructuredtext en'
17
18
19class INotifyError(Exception):
20	pass
21
22
23_inotify = None
24
25
26def load_inotify():
27	''' Initialize the inotify library '''
28	global _inotify
29	if _inotify is None:
30		if hasattr(sys, 'getwindowsversion'):
31			# On windows abort before loading the C library. Windows has
32			# multiple, incompatible C runtimes, and we have no way of knowing
33			# if the one chosen by ctypes is compatible with the currently
34			# loaded one.
35			raise INotifyError('INotify not available on windows')
36		if sys.platform == 'darwin':
37			raise INotifyError('INotify not available on OS X')
38		if not hasattr(ctypes, 'c_ssize_t'):
39			raise INotifyError('You need python >= 2.7 to use inotify')
40		name = find_library('c')
41		if not name:
42			raise INotifyError('Cannot find C library')
43		libc = ctypes.CDLL(name, use_errno=True)
44		for function in ('inotify_add_watch', 'inotify_init1', 'inotify_rm_watch'):
45			if not hasattr(libc, function):
46				raise INotifyError('libc is too old')
47		# inotify_init1()
48		prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, use_errno=True)
49		init1 = prototype(('inotify_init1', libc), ((1, 'flags', 0),))
50
51		# inotify_add_watch()
52		prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32, use_errno=True)
53		add_watch = prototype(('inotify_add_watch', libc), (
54			(1, 'fd'), (1, 'pathname'), (1, 'mask')))
55
56		# inotify_rm_watch()
57		prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, use_errno=True)
58		rm_watch = prototype(('inotify_rm_watch', libc), (
59			(1, 'fd'), (1, 'wd')))
60
61		# read()
62		prototype = ctypes.CFUNCTYPE(ctypes.c_ssize_t, ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t, use_errno=True)
63		read = prototype(('read', libc), (
64			(1, 'fd'), (1, 'buf'), (1, 'count')))
65		_inotify = (init1, add_watch, rm_watch, read)
66	return _inotify
67
68
69class INotify(object):
70
71	# See <sys/inotify.h> for the flags defined below
72
73	# Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.
74	ACCESS = 0x00000001         # File was accessed.
75	MODIFY = 0x00000002         # File was modified.
76	ATTRIB = 0x00000004         # Metadata changed.
77	CLOSE_WRITE = 0x00000008    # Writtable file was closed.
78	CLOSE_NOWRITE = 0x00000010  # Unwrittable file closed.
79	OPEN = 0x00000020           # File was opened.
80	MOVED_FROM = 0x00000040     # File was moved from X.
81	MOVED_TO = 0x00000080       # File was moved to Y.
82	CREATE = 0x00000100         # Subfile was created.
83	DELETE = 0x00000200         # Subfile was deleted.
84	DELETE_SELF = 0x00000400    # Self was deleted.
85	MOVE_SELF = 0x00000800      # Self was moved.
86
87	# Events sent by the kernel.
88	UNMOUNT = 0x00002000     # Backing fs was unmounted.
89	Q_OVERFLOW = 0x00004000  # Event queued overflowed.
90	IGNORED = 0x00008000     # File was ignored.
91
92	# Helper events.
93	CLOSE = (CLOSE_WRITE | CLOSE_NOWRITE)  # Close.
94	MOVE = (MOVED_FROM | MOVED_TO)         # Moves.
95
96	# Special flags.
97	ONLYDIR = 0x01000000      # Only watch the path if it is a directory.
98	DONT_FOLLOW = 0x02000000  # Do not follow a sym link.
99	EXCL_UNLINK = 0x04000000  # Exclude events on unlinked objects.
100	MASK_ADD = 0x20000000     # Add to the mask of an already existing watch.
101	ISDIR = 0x40000000        # Event occurred against dir.
102	ONESHOT = 0x80000000      # Only send event once.
103
104	# All events which a program can wait on.
105	ALL_EVENTS = (
106		ACCESS | MODIFY | ATTRIB | CLOSE_WRITE | CLOSE_NOWRITE | OPEN |
107		MOVED_FROM | MOVED_TO | CREATE | DELETE | DELETE_SELF | MOVE_SELF
108	)
109
110	# See <bits/inotify.h>
111	CLOEXEC = 0x80000
112	NONBLOCK = 0x800
113
114	def __init__(self, cloexec=True, nonblock=True):
115		self._init1, self._add_watch, self._rm_watch, self._read = load_inotify()
116		flags = 0
117		if cloexec:
118			flags |= self.CLOEXEC
119		if nonblock:
120			flags |= self.NONBLOCK
121		self._inotify_fd = self._init1(flags)
122		if self._inotify_fd == -1:
123			raise INotifyError(os.strerror(ctypes.get_errno()))
124
125		self._buf = ctypes.create_string_buffer(5000)
126		self.fenc = get_preferred_file_name_encoding()
127		self.hdr = struct.Struct(b'iIII')
128		# We keep a reference to os to prevent it from being deleted
129		# during interpreter shutdown, which would lead to errors in the
130		# __del__ method
131		self.os = os
132
133	def handle_error(self):
134		eno = ctypes.get_errno()
135		extra = ''
136		if eno == errno.ENOSPC:
137			extra = 'You may need to increase the inotify limits on your system, via /proc/sys/fs/inotify/max_user_*'
138		raise OSError(eno, self.os.strerror(eno) + str(extra))
139
140	def __del__(self):
141		# This method can be called during interpreter shutdown, which means we
142		# must do the absolute minimum here. Note that there could be running
143		# daemon threads that are trying to call other methods on this object.
144		try:
145			self.os.close(self._inotify_fd)
146		except (AttributeError, TypeError):
147			pass
148
149	def close(self):
150		if hasattr(self, '_inotify_fd'):
151			self.os.close(self._inotify_fd)
152			del self.os
153			del self._add_watch
154			del self._rm_watch
155			del self._inotify_fd
156
157	def read(self, get_name=True):
158		buf = []
159		while True:
160			num = self._read(self._inotify_fd, self._buf, len(self._buf))
161			if num == 0:
162				break
163			if num < 0:
164				en = ctypes.get_errno()
165				if en == errno.EAGAIN:
166					break  # No more data
167				if en == errno.EINTR:
168					continue  # Interrupted, try again
169				raise OSError(en, self.os.strerror(en))
170			buf.append(self._buf.raw[:num])
171		raw = b''.join(buf)
172		pos = 0
173		lraw = len(raw)
174		while lraw - pos >= self.hdr.size:
175			wd, mask, cookie, name_len = self.hdr.unpack_from(raw, pos)
176			pos += self.hdr.size
177			name = None
178			if get_name:
179				name = raw[pos:pos + name_len].rstrip(b'\0')
180			pos += name_len
181			self.process_event(wd, mask, cookie, name)
182
183	def process_event(self, *args):
184		raise NotImplementedError()
185