Initial commit
commit
2c2c55d0ef
|
@ -0,0 +1,5 @@
|
||||||
|
/build/
|
||||||
|
/dist/
|
||||||
|
*.egg-info/
|
||||||
|
__pycache__/
|
||||||
|
*.py[co]
|
|
@ -0,0 +1,27 @@
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
dist = pkg_resources.get_distribution('linuxapi')
|
||||||
|
|
||||||
|
project = dist.project_name
|
||||||
|
version = release = dist.version
|
||||||
|
copyright = '2016, FireMon, LLC.'
|
||||||
|
author = 'Dustin C. Hatch'
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
]
|
||||||
|
|
||||||
|
templates_path = ['_templates']
|
||||||
|
source_suffix = '.rst'
|
||||||
|
master_doc = 'index'
|
||||||
|
language = None
|
||||||
|
exclude_patterns = ['_build']
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
todo_include_todos = False
|
||||||
|
autodoc_member_order = 'groupwise'
|
||||||
|
|
||||||
|
intersphinx_mapping = {
|
||||||
|
'http://docs.python.org/': None,
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
====================
|
||||||
|
``linuxapi.ifaddrs``
|
||||||
|
====================
|
||||||
|
|
||||||
|
|
||||||
|
.. automodule:: linuxapi.ifaddrs
|
||||||
|
:members:
|
|
@ -0,0 +1,18 @@
|
||||||
|
Welcome to linuxapi's documentation!
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Contents:
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:glob:
|
||||||
|
|
||||||
|
*
|
||||||
|
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
|
@ -0,0 +1,11 @@
|
||||||
|
====================
|
||||||
|
``linuxapi.inotify``
|
||||||
|
====================
|
||||||
|
|
||||||
|
|
||||||
|
.. automodule:: linuxapi.inotify
|
||||||
|
:members:
|
||||||
|
:exclude-members: Event
|
||||||
|
|
||||||
|
.. autoclass:: Event
|
||||||
|
:no-members:
|
|
@ -0,0 +1,13 @@
|
||||||
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='linuxapi',
|
||||||
|
version='1.0',
|
||||||
|
description='Pure Python bindings for Linux standard library features',
|
||||||
|
author='Dustin C. Hatch',
|
||||||
|
author_email='dustin.hatch@firemon.com',
|
||||||
|
url='https://www.firemon.com/',
|
||||||
|
license='GPL-2',
|
||||||
|
packages=find_packages('src'),
|
||||||
|
package_dir={'': 'src'},
|
||||||
|
)
|
|
@ -0,0 +1,238 @@
|
||||||
|
# Copyright 2016 FireMon. All rights reserved.
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
'''\
|
||||||
|
This module provides a Pythonic interface to the Linux implementation of
|
||||||
|
:manpage:`getifaddrs(3)`.
|
||||||
|
|
||||||
|
The :py:func:`getifaddrs` function returns a sequence of
|
||||||
|
:py:class:`Interface` objects representing the network interfaces on
|
||||||
|
the system. Each object has a list of :py:class:`InterfaceAddress`
|
||||||
|
objects that describe the addresses associated with the interface.
|
||||||
|
|
||||||
|
At this time, only :py:data:`~socket.AF_INET` and
|
||||||
|
:py:data:`~socket.AF_INET6` addresses are supported, even though the
|
||||||
|
Linux implementation of :manpage:`getifaddrs(3)` may return more than
|
||||||
|
that (e.g. ``AF_PACKET``).
|
||||||
|
'''
|
||||||
|
|
||||||
|
import ctypes.util
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'IfaddrError',
|
||||||
|
'Interface',
|
||||||
|
'InterfaceAddress',
|
||||||
|
'getifaddrs',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
_libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||||
|
|
||||||
|
|
||||||
|
_errno = _libc.__errno_location
|
||||||
|
_errno.restype = ctypes.POINTER(ctypes.c_int)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class in_addr(ctypes.Structure):
|
||||||
|
|
||||||
|
_fields_ = [
|
||||||
|
('s_addr', ctypes.c_byte * 4),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class in6_addr(ctypes.Structure):
|
||||||
|
|
||||||
|
_fields_ = [
|
||||||
|
('s_addr', ctypes.c_byte * 16),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class sockaddr_in(ctypes.Structure):
|
||||||
|
|
||||||
|
_fields_ = [
|
||||||
|
('sin_family', ctypes.c_ushort),
|
||||||
|
('sin_port', ctypes.c_uint16),
|
||||||
|
('sin_addr', in_addr),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class sockaddr_in6(ctypes.Structure):
|
||||||
|
|
||||||
|
_fields_ = [
|
||||||
|
('sin6_family', ctypes.c_ushort),
|
||||||
|
('sin6_port', ctypes.c_uint16),
|
||||||
|
('sin6_flowinfo', ctypes.c_uint32),
|
||||||
|
('sin6_addr', in6_addr),
|
||||||
|
('sin6_scope', ctypes.c_uint32),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class sockaddr(ctypes.Structure):
|
||||||
|
|
||||||
|
_fields_ = [
|
||||||
|
('sa_family', ctypes.c_ushort),
|
||||||
|
('sa_data', ctypes.c_void_p),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ifa_ifu(ctypes.Union):
|
||||||
|
|
||||||
|
_fields_ = [
|
||||||
|
('ifu_broadaddr', ctypes.POINTER(sockaddr)),
|
||||||
|
('ifu_dstaddr', ctypes.POINTER(sockaddr)),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ifaddrs(ctypes.Structure):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
ifaddrs._fields_ = [
|
||||||
|
('ifa_next', ctypes.POINTER(ifaddrs)),
|
||||||
|
('ifa_name', ctypes.c_char_p),
|
||||||
|
('ifa_flags', ctypes.c_uint),
|
||||||
|
('ifa_addr', ctypes.POINTER(sockaddr)),
|
||||||
|
('ifa_netmask', ctypes.POINTER(sockaddr)),
|
||||||
|
('ifa_ifu', ifa_ifu),
|
||||||
|
('ifa_data', ctypes.c_void_p),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
_libc.getifaddrs.argtypes = [ctypes.POINTER(ctypes.POINTER(ifaddrs))]
|
||||||
|
_libc.getifaddrs.restype = ctypes.c_int
|
||||||
|
|
||||||
|
_libc.freeifaddrs.argtypes = [ctypes.POINTER(ifaddrs)]
|
||||||
|
|
||||||
|
|
||||||
|
class IfaddrError(OSError):
|
||||||
|
'''Raised when an error is returned by getifaddrs'''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_c_err(cls):
|
||||||
|
errno = _errno().contents.value
|
||||||
|
return cls(errno, os.strerror(errno))
|
||||||
|
|
||||||
|
|
||||||
|
class Interface(object):
|
||||||
|
'''A network interface'''
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
'name',
|
||||||
|
'addresses',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, name=None):
|
||||||
|
self.name = name
|
||||||
|
'''The name of the interface'''
|
||||||
|
self.addresses = []
|
||||||
|
'''A list of addresses associated with the interface
|
||||||
|
|
||||||
|
See :py:class:`InterfaceAddress` for details on the objects
|
||||||
|
contained in this list.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceAddress(object):
|
||||||
|
'''An address associated with an interface'''
|
||||||
|
|
||||||
|
__slots__ = (
|
||||||
|
'family',
|
||||||
|
'addr',
|
||||||
|
'netmask',
|
||||||
|
'prefixlen',
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.family = None
|
||||||
|
'''The address family'''
|
||||||
|
self.addr = None
|
||||||
|
'''The numeric address'''
|
||||||
|
self.netmask = None
|
||||||
|
'''The subnet mask'''
|
||||||
|
self.prefixlen = None
|
||||||
|
'''The prefix length'''
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{}/{}'.format(self.addr, self.prefixlen)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_sockaddr(cls, addr_p, netmask_p):
|
||||||
|
self = cls()
|
||||||
|
self.family = addr_p.contents.sa_family
|
||||||
|
if self.family == socket.AF_INET:
|
||||||
|
asin_p = ctypes.cast(addr_p, ctypes.POINTER(sockaddr_in))
|
||||||
|
msin_p = ctypes.cast(netmask_p, ctypes.POINTER(sockaddr_in))
|
||||||
|
abuf = bytes(bytearray(asin_p.contents.sin_addr.s_addr))
|
||||||
|
mbuf = bytes(bytearray(msin_p.contents.sin_addr.s_addr))
|
||||||
|
struct_fmt = 'I'
|
||||||
|
elif self.family == socket.AF_INET6:
|
||||||
|
asin6_p = ctypes.cast(addr_p, ctypes.POINTER(sockaddr_in6))
|
||||||
|
msin6_p = ctypes.cast(netmask_p, ctypes.POINTER(sockaddr_in6))
|
||||||
|
abuf = bytes(bytearray(asin6_p.contents.sin6_addr.s_addr))
|
||||||
|
mbuf = bytes(bytearray(msin6_p.contents.sin6_addr.s_addr))
|
||||||
|
struct_fmt = 'QQ'
|
||||||
|
else:
|
||||||
|
raise ValueError('Unsupported family {}'.format(self.family))
|
||||||
|
self.addr = socket.inet_ntop(self.family, abuf)
|
||||||
|
self.netmask = socket.inet_ntop(self.family, mbuf)
|
||||||
|
self.prefixlen = popcount(sum(struct.unpack(struct_fmt, mbuf)))
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
def getifaddrs():
|
||||||
|
'''Get interface addresses
|
||||||
|
|
||||||
|
:returns: A sequence of :py:class:`Interface` objects
|
||||||
|
:raises: :py:exc:`IfaddrError`
|
||||||
|
'''
|
||||||
|
|
||||||
|
ifaces = {}
|
||||||
|
ifa_p = ctypes.pointer(ifaddrs())
|
||||||
|
rc = _libc.getifaddrs(ctypes.byref(ifa_p))
|
||||||
|
if rc == -1:
|
||||||
|
raise IfaddrError.from_c_err()
|
||||||
|
if not ifa_p:
|
||||||
|
return []
|
||||||
|
i = ifa_p.contents
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
iface = ifaces[i.ifa_name]
|
||||||
|
except KeyError:
|
||||||
|
iface = ifaces[i.ifa_name] = Interface(i.ifa_name)
|
||||||
|
if i.ifa_addr and is_inet(i.ifa_addr.contents.sa_family):
|
||||||
|
addr = InterfaceAddress.from_sockaddr(i.ifa_addr, i.ifa_netmask)
|
||||||
|
iface.addresses.append(addr)
|
||||||
|
if not i.ifa_next:
|
||||||
|
break
|
||||||
|
i = i.ifa_next.contents
|
||||||
|
_libc.freeifaddrs(ifa_p)
|
||||||
|
return ifaces.values()
|
||||||
|
|
||||||
|
|
||||||
|
def is_inet(family):
|
||||||
|
return family in (socket.AF_INET, socket.AF_INET6)
|
||||||
|
|
||||||
|
|
||||||
|
def popcount(num):
|
||||||
|
c = 0
|
||||||
|
v = num
|
||||||
|
while v:
|
||||||
|
v &= v - 1
|
||||||
|
c += 1
|
||||||
|
return c
|
|
@ -0,0 +1,240 @@
|
||||||
|
# Copyright 2016 FireMon. All rights reserved.
|
||||||
|
#
|
||||||
|
# This file is a part of the FireMon codebase. The contents of this
|
||||||
|
# file are confidential and cannot be distributed without prior
|
||||||
|
# written authorization.
|
||||||
|
#
|
||||||
|
# Warning: This computer program is protected by copyright law and
|
||||||
|
# international treaties. Unauthorized reproduction or distribution of
|
||||||
|
# this program, or any portion of it, may result in severe civil and
|
||||||
|
# criminal penalties, and will be prosecuted to the maximum extent
|
||||||
|
# possible under the law.
|
||||||
|
'''\
|
||||||
|
This module provides Python bindings for the Linux inotify system, which
|
||||||
|
is a means for receiving events about file access and modification.
|
||||||
|
|
||||||
|
All inotify operations are handled by the :py:class:`Inotify` class.
|
||||||
|
When an instance is created, the inotify system is initialized and an
|
||||||
|
inotify file descriptor is assigned.
|
||||||
|
|
||||||
|
To begin watching files, use the :py:meth:`Inotify.add_watch` method.
|
||||||
|
Messages can then be obtained by iterating over the results of the
|
||||||
|
:py:meth:`Inotify.read` method.
|
||||||
|
|
||||||
|
>>> inot = Inotify()
|
||||||
|
>>> inot.add_watch('/tmp', IN_CREATE | IN_MOVE)
|
||||||
|
>>> for event in inot.read():
|
||||||
|
... print(event)
|
||||||
|
|
||||||
|
The :py:meth:`Inotify.read` method will block until an event is
|
||||||
|
received. To avoid blocking, use an I/O multiplexing mechanism such as
|
||||||
|
:py:func:`~select.select` or :py:class:`~select.epoll`.
|
||||||
|
:py:class:`Inotify` instances can be passed directly to these
|
||||||
|
mechanisms, or the underlying file descriptor can be obtained by calling
|
||||||
|
the :py:meth:`Inotify.fileno` method.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import ctypes.util
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
_libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||||
|
|
||||||
|
_errno = _libc.__errno_location
|
||||||
|
_errno.restype = ctypes.POINTER(ctypes.c_int)
|
||||||
|
|
||||||
|
_libc.inotify_add_watch.argtypes = (ctypes.c_int, ctypes.c_char_p,
|
||||||
|
ctypes.c_uint32)
|
||||||
|
_libc.inotify_rm_watch.argtypes = (ctypes.c_int, ctypes.c_int)
|
||||||
|
|
||||||
|
IN_NONBLOCK = 0x800
|
||||||
|
IN_CLOEXEC = 0x80000
|
||||||
|
|
||||||
|
#: File was accessed.
|
||||||
|
IN_ACCESS = 0x1
|
||||||
|
#: File was modified.
|
||||||
|
IN_MODIFY = 0x2
|
||||||
|
#: Metadata changed.
|
||||||
|
IN_ATTRIB = 0x4
|
||||||
|
#: Writtable file was closed.
|
||||||
|
IN_CLOSE_WRITE = 0x8
|
||||||
|
#: Unwrittable file closed.
|
||||||
|
IN_CLOSE_NOWRITE = 0x10
|
||||||
|
#: Close.
|
||||||
|
IN_CLOSE = IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
|
||||||
|
#: File was opened.
|
||||||
|
IN_OPEN = 0x20
|
||||||
|
#: File was moved from X.
|
||||||
|
IN_MOVED_FROM = 0x40
|
||||||
|
#: File was moved to Y.
|
||||||
|
IN_MOVED_TO = 0x80
|
||||||
|
#: Moves.
|
||||||
|
IN_MOVE = IN_MOVED_FROM | IN_MOVED_TO
|
||||||
|
#: Subfile was created.
|
||||||
|
IN_CREATE = 0x100
|
||||||
|
#: Subfile was deleted.
|
||||||
|
IN_DELETE = 0x200
|
||||||
|
#: Self was deleted.
|
||||||
|
IN_DELETE_SELF = 0x400
|
||||||
|
#: Self was moved.
|
||||||
|
IN_MOVE_SELF = 0x800
|
||||||
|
|
||||||
|
#: Backing fs was unmounted.
|
||||||
|
IN_UNMOUNT = 0x2000
|
||||||
|
#: Event queued overflowed.
|
||||||
|
IN_Q_OVERFLOW = 0x4000
|
||||||
|
#: File was ignored.
|
||||||
|
IN_IGNORED = 0x8000
|
||||||
|
|
||||||
|
#: Only watch the path if it is a directory.
|
||||||
|
IN_ONLYDIR = 0x1000000
|
||||||
|
#: DO not follow a sym link.
|
||||||
|
IN_DONT_FOLLOW = 0x2000000
|
||||||
|
#: Exclude events on unlinked objects.
|
||||||
|
IN_EXCL_UNLINK = 0x4000000
|
||||||
|
#: Add the mask of an already existing watch.
|
||||||
|
IN_MASK_ADD = 0x20000000
|
||||||
|
#: Event occurred against dir.
|
||||||
|
IN_ISDIR = 0x40000000
|
||||||
|
#: Only send event once.
|
||||||
|
IN_ONESHOT = 0x80000000
|
||||||
|
|
||||||
|
#: All events which a program can wait on.
|
||||||
|
IN_ALL_EVENTS = (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE |
|
||||||
|
IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO |
|
||||||
|
IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF)
|
||||||
|
|
||||||
|
|
||||||
|
class InotifyError(OSError):
|
||||||
|
'''Raised when an error is returned by inotify'''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_c_err(cls):
|
||||||
|
errno = _errno().contents.value
|
||||||
|
return cls(errno, os.strerror(errno))
|
||||||
|
|
||||||
|
|
||||||
|
class Inotify(object):
|
||||||
|
'''Wrapper class for Linux inotify capabilities'''
|
||||||
|
|
||||||
|
BUFSIZE = 4096
|
||||||
|
STRUCT_FMT = '@iIII'
|
||||||
|
STRUCT_SIZE = struct.calcsize(STRUCT_FMT)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
fd = _libc.inotify_init()
|
||||||
|
if fd == -1:
|
||||||
|
raise InotifyError.from_c_err()
|
||||||
|
self.__fd = fd
|
||||||
|
self.__watches = {}
|
||||||
|
|
||||||
|
def fileno(self):
|
||||||
|
'''Return the underlying inotify file descriptor'''
|
||||||
|
|
||||||
|
return self.__fd
|
||||||
|
|
||||||
|
def add_watch(self, pathname, mask):
|
||||||
|
'''Add a new watch
|
||||||
|
|
||||||
|
:param pathname: The path to the file or directory to watch
|
||||||
|
:param mask: The events to watch, as a bit field
|
||||||
|
:returns: The new watch descriptor
|
||||||
|
:raises: :py:exc:`InotifyError`
|
||||||
|
'''
|
||||||
|
|
||||||
|
wd = _libc.inotify_add_watch(self.__fd, pathname, mask)
|
||||||
|
if wd == -1:
|
||||||
|
raise InotifyError.from_c_err()
|
||||||
|
self.__watches[wd] = pathname
|
||||||
|
return wd
|
||||||
|
|
||||||
|
def rm_watch(self, wd):
|
||||||
|
'''Remove an existing watch
|
||||||
|
|
||||||
|
:param wd: The watch descriptor to remove
|
||||||
|
:raises: :py:exc:`InotifyError`
|
||||||
|
'''
|
||||||
|
|
||||||
|
ret = _libc.inotify_rm_watch(self.__fd, wd)
|
||||||
|
if ret == -1:
|
||||||
|
raise InotifyError.from_c_err()
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
'''Iterate over received events
|
||||||
|
|
||||||
|
:returns: An iterator that yields :py:class:`Event` objects
|
||||||
|
|
||||||
|
This method returns an iterator for all of the events received
|
||||||
|
in a single batch. Iterating over the returned value will block
|
||||||
|
until an event is received
|
||||||
|
'''
|
||||||
|
|
||||||
|
buf = memoryview(os.read(self.__fd, self.BUFSIZE))
|
||||||
|
nread = len(buf)
|
||||||
|
i = 0
|
||||||
|
while i < nread:
|
||||||
|
wd, mask, cookie, sz = self._unpack(buf)
|
||||||
|
end = self.STRUCT_SIZE
|
||||||
|
while end < nread and buf[end:end + 1] != b'\x00':
|
||||||
|
end += 1
|
||||||
|
name = buf[self.STRUCT_SIZE:end].tobytes()
|
||||||
|
pathname = self.__watches[wd]
|
||||||
|
i += self.STRUCT_SIZE + sz
|
||||||
|
yield Event(wd, mask, cookie, name, pathname)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
'''Close all watch descriptors and the inotify descriptor'''
|
||||||
|
|
||||||
|
for wd in self.__watches:
|
||||||
|
try:
|
||||||
|
self.rm_watch(wd)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
os.close(self.__fd)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _unpack(cls, buf):
|
||||||
|
return struct.unpack(cls.STRUCT_FMT, buf[:cls.STRUCT_SIZE])
|
||||||
|
|
||||||
|
|
||||||
|
Event = collections.namedtuple('Event', (
|
||||||
|
'wd',
|
||||||
|
'mask',
|
||||||
|
'cookie',
|
||||||
|
'name',
|
||||||
|
'pathname',
|
||||||
|
))
|
||||||
|
'''A tuple containing information about a single event
|
||||||
|
|
||||||
|
Each tuple contains the following items:
|
||||||
|
|
||||||
|
.. py:attribute:: wd
|
||||||
|
|
||||||
|
identifies the watch for which this event occurs. It is one of the
|
||||||
|
watch descriptors returned by a previous call to
|
||||||
|
:py:meth:`Inotify.add_watch`
|
||||||
|
|
||||||
|
.. py:attribute:: mask
|
||||||
|
|
||||||
|
contains bits that describe the event that occurred
|
||||||
|
|
||||||
|
.. py:attribute:: cookie
|
||||||
|
|
||||||
|
A unique integer that connects related events. Currently this is used
|
||||||
|
only for rename events, and allows the resulting pair of
|
||||||
|
:py:data:`IN_MOVED_FROM` and :py:data`IN_MOVED_TO` events to be
|
||||||
|
connected by the application. For all other event types, cookie is set
|
||||||
|
to ``0``.
|
||||||
|
|
||||||
|
.. py:attribute:: name
|
||||||
|
|
||||||
|
The ``name`` field is present only when an event is returned for a
|
||||||
|
file inside a watched directory; it identifies the filename within to
|
||||||
|
the watched directory.
|
||||||
|
|
||||||
|
.. py:attribute:: pathname
|
||||||
|
|
||||||
|
The path of the watched file or directory that emitted the event
|
||||||
|
'''
|
Loading…
Reference in New Issue