1 /**
2  * watchdog_fsevents.c: Python-C bridge to the OS X FSEvents API.
3  *
4  * Copyright 2018-2024 Mickaël Schoentgen & contributors
5  * Copyright 2012-2018 Google, Inc.
6  * Copyright 2011-2012 Yesudeep Mangalapilly <[email protected]>
7  * Copyright 2010-2011 Malthe Borch <[email protected]>
8  */
9 
10 
11 #include <Python.h>
12 #include <Availability.h>
13 #include <CoreFoundation/CoreFoundation.h>
14 #include <CoreServices/CoreServices.h>
15 #include <stdlib.h>
16 #include <signal.h>
17 
18 
19 /* Compatibility; since fsevents won't set these on earlier macOS versions the properties will always be False */
20 #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
21 #error Watchdog module requires at least macOS 10.13
22 #endif
23 
24 /* Convenience macros to make code more readable. */
25 #define G_NOT(o)                        !o
26 #define G_IS_NULL(o)                    o == NULL
27 #define G_IS_NOT_NULL(o)                o != NULL
28 #define G_RETURN_NULL_IF_NULL(o)        do { if (NULL == o) { return NULL; } } while (0)
29 #define G_RETURN_NULL_IF(condition)     do { if (condition) { return NULL; } } while (0)
30 #define G_RETURN_NULL_IF_NOT(condition) do { if (!condition) { return NULL; } } while (0)
31 #define G_RETURN_IF(condition)          do { if (condition) { return; } } while (0)
32 #define G_RETURN_IF_NOT(condition)      do { if (!condition) { return; } } while (0)
33 #define UNUSED(x)                       (void)x
34 
35 /* Error message definitions. */
36 #define ERROR_CANNOT_CALL_CALLBACK "Unable to call Python callback."
37 
38 /* Other information. */
39 #define MODULE_NAME  "_watchdog_fsevents"
40 
41 /**
42  * Event stream callback contextual information passed to
43  * our ``watchdog_FSEventStreamCallback`` function by the
44  * FSEvents API whenever an event occurs.
45  */
46 typedef struct {
47     /**
48      * A pointer to the Python callback which will
49      * will in turn be called by our event handler
50      * with event information. The Python callback
51      * function must accept 2 arguments, both of which
52      * are Python lists::
53      *
54      *    def python_callback(event_paths, event_inodes, event_flags, event_ids):
55      *        pass
56      */
57     PyObject        *python_callback;
58     /**
59      * A pointer to the associated ``FSEventStream``
60      * instance.
61      */
62     FSEventStreamRef stream_ref;
63     /**
64      * A pointer to the associated ``CFRunLoop``
65      * instance.
66      */
67     CFRunLoopRef     run_loop_ref;
68     /**
69      * A pointer to the state of the Python thread.
70      */
71     PyThreadState   *thread_state;
72 } StreamCallbackInfo;
73 
74 
75 /**
76  * NativeEvent type so that we don't need to expose the FSEvents constants to Python land
77  */
78 typedef struct {
79     PyObject_HEAD
80     const char *path;
81     PyObject *inode;
82     FSEventStreamEventFlags flags;
83     FSEventStreamEventId id;
84 } NativeEventObject;
85 
NativeEventRepr(PyObject * instance)86 PyObject* NativeEventRepr(PyObject* instance) {
87     NativeEventObject *self = (NativeEventObject*)instance;
88 
89     return PyUnicode_FromFormat(
90         "NativeEvent(path=\"%s\", inode=%S, flags=%x, id=%llu)",
91         self->path,
92         self->inode,
93         self->flags,
94         self->id
95     );
96 }
97 
NativeEventTypeFlags(PyObject * instance,void * closure)98 PyObject* NativeEventTypeFlags(PyObject* instance, void* closure)
99 {
100     UNUSED(closure);
101     NativeEventObject *self = (NativeEventObject*)instance;
102     return PyLong_FromLong(self->flags);
103 }
104 
NativeEventTypePath(PyObject * instance,void * closure)105 PyObject* NativeEventTypePath(PyObject* instance, void* closure)
106 {
107     UNUSED(closure);
108     NativeEventObject *self = (NativeEventObject*)instance;
109     return PyUnicode_FromString(self->path);
110 }
111 
NativeEventTypeInode(PyObject * instance,void * closure)112 PyObject* NativeEventTypeInode(PyObject* instance, void* closure)
113 {
114     UNUSED(closure);
115     NativeEventObject *self = (NativeEventObject*)instance;
116     Py_INCREF(self->inode);
117     return self->inode;
118 }
119 
NativeEventTypeID(PyObject * instance,void * closure)120 PyObject* NativeEventTypeID(PyObject* instance, void* closure)
121 {
122     UNUSED(closure);
123     NativeEventObject *self = (NativeEventObject*)instance;
124     return PyLong_FromLong(self->id);
125 }
126 
NativeEventTypeIsCoalesced(PyObject * instance,void * closure)127 PyObject* NativeEventTypeIsCoalesced(PyObject* instance, void* closure)
128 {
129     UNUSED(closure);
130     NativeEventObject *self = (NativeEventObject*)instance;
131 
132     // if any of these bitmasks match then we have a coalesced event and need to do sys calls to figure out what happened
133     FSEventStreamEventFlags coalesced_masks[] = {
134         kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRemoved,
135         kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed,
136         kFSEventStreamEventFlagItemRemoved | kFSEventStreamEventFlagItemRenamed,
137     };
138     for (size_t i = 0; i < sizeof(coalesced_masks) / sizeof(FSEventStreamEventFlags); ++i) {
139         if ((self->flags & coalesced_masks[i]) == coalesced_masks[i]) {
140             Py_RETURN_TRUE;
141         }
142     }
143 
144     Py_RETURN_FALSE;
145 }
146 
147 #define FLAG_PROPERTY(suffix, flag) \
148     PyObject* NativeEventType##suffix(PyObject* instance, void* closure) \
149     { \
150         UNUSED(closure); \
151         NativeEventObject *self = (NativeEventObject*)instance; \
152         if (self->flags & flag) { \
153             Py_RETURN_TRUE; \
154         } \
155         Py_RETURN_FALSE; \
156     }
157 
FLAG_PROPERTY(IsMustScanSubDirs,kFSEventStreamEventFlagMustScanSubDirs)158 FLAG_PROPERTY(IsMustScanSubDirs, kFSEventStreamEventFlagMustScanSubDirs)
159 FLAG_PROPERTY(IsUserDropped, kFSEventStreamEventFlagUserDropped)
160 FLAG_PROPERTY(IsKernelDropped, kFSEventStreamEventFlagKernelDropped)
161 FLAG_PROPERTY(IsEventIdsWrapped, kFSEventStreamEventFlagEventIdsWrapped)
162 FLAG_PROPERTY(IsHistoryDone, kFSEventStreamEventFlagHistoryDone)
163 FLAG_PROPERTY(IsRootChanged, kFSEventStreamEventFlagRootChanged)
164 FLAG_PROPERTY(IsMount, kFSEventStreamEventFlagMount)
165 FLAG_PROPERTY(IsUnmount, kFSEventStreamEventFlagUnmount)
166 FLAG_PROPERTY(IsCreated, kFSEventStreamEventFlagItemCreated)
167 FLAG_PROPERTY(IsRemoved, kFSEventStreamEventFlagItemRemoved)
168 FLAG_PROPERTY(IsInodeMetaMod, kFSEventStreamEventFlagItemInodeMetaMod)
169 FLAG_PROPERTY(IsRenamed, kFSEventStreamEventFlagItemRenamed)
170 FLAG_PROPERTY(IsModified, kFSEventStreamEventFlagItemModified)
171 FLAG_PROPERTY(IsItemFinderInfoMod, kFSEventStreamEventFlagItemFinderInfoMod)
172 FLAG_PROPERTY(IsChangeOwner, kFSEventStreamEventFlagItemChangeOwner)
173 FLAG_PROPERTY(IsXattrMod, kFSEventStreamEventFlagItemXattrMod)
174 FLAG_PROPERTY(IsFile, kFSEventStreamEventFlagItemIsFile)
175 FLAG_PROPERTY(IsDirectory, kFSEventStreamEventFlagItemIsDir)
176 FLAG_PROPERTY(IsSymlink, kFSEventStreamEventFlagItemIsSymlink)
177 FLAG_PROPERTY(IsOwnEvent, kFSEventStreamEventFlagOwnEvent)
178 FLAG_PROPERTY(IsHardlink, kFSEventStreamEventFlagItemIsHardlink)
179 FLAG_PROPERTY(IsLastHardlink, kFSEventStreamEventFlagItemIsLastHardlink)
180 FLAG_PROPERTY(IsCloned, kFSEventStreamEventFlagItemCloned)
181 
182 static int NativeEventInit(NativeEventObject *self, PyObject *args, PyObject *kwds)
183 {
184     static char *kwlist[] = {"path", "inode", "flags", "id", NULL};
185 
186     self->inode = NULL;
187 
188     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sOIL", kwlist, &self->path, &self->inode, &self->flags, &self->id)) {
189         return -1;
190     }
191 
192     Py_INCREF(self->inode);
193 
194     return 0;
195 }
196 
NativeEventDealloc(NativeEventObject * self)197 static void NativeEventDealloc(NativeEventObject *self) {
198     Py_XDECREF(self->inode);
199 }
200 
201 static PyGetSetDef NativeEventProperties[] = {
202     {"flags", NativeEventTypeFlags, NULL, "The raw mask of flags as returned by FSEvents", NULL},
203     {"path", NativeEventTypePath, NULL, "The path for which this event was generated", NULL},
204     {"inode", NativeEventTypeInode, NULL, "The inode for which this event was generated", NULL},
205     {"event_id", NativeEventTypeID, NULL, "The id of the generated event", NULL},
206     {"is_coalesced", NativeEventTypeIsCoalesced, NULL, "True if multiple ambiguous changes to the monitored path happened", NULL},
207     {"must_scan_subdirs", NativeEventTypeIsMustScanSubDirs, NULL, "True if application must rescan all subdirectories", NULL},
208     {"is_user_dropped", NativeEventTypeIsUserDropped, NULL, "True if a failure during event buffering occurred", NULL},
209     {"is_kernel_dropped", NativeEventTypeIsKernelDropped, NULL, "True if a failure during event buffering occurred", NULL},
210     {"is_event_ids_wrapped", NativeEventTypeIsEventIdsWrapped, NULL, "True if event_id wrapped around", NULL},
211     {"is_history_done", NativeEventTypeIsHistoryDone, NULL, "True if all historical events are done", NULL},
212     {"is_root_changed", NativeEventTypeIsRootChanged, NULL, "True if a change to one of the directories along the path to one of the directories you watch occurred", NULL},
213     {"is_mount", NativeEventTypeIsMount, NULL, "True if a volume is mounted underneath one of the paths being monitored", NULL},
214     {"is_unmount", NativeEventTypeIsUnmount, NULL, "True if a volume is unmounted underneath one of the paths being monitored", NULL},
215     {"is_created", NativeEventTypeIsCreated, NULL, "True if self.path was created on the filesystem", NULL},
216     {"is_removed", NativeEventTypeIsRemoved, NULL, "True if self.path was removed from the filesystem", NULL},
217     {"is_inode_meta_mod", NativeEventTypeIsInodeMetaMod, NULL, "True if meta data for self.path was modified ", NULL},
218     {"is_renamed", NativeEventTypeIsRenamed, NULL, "True if self.path was renamed on the filesystem", NULL},
219     {"is_modified", NativeEventTypeIsModified, NULL, "True if self.path was modified", NULL},
220     {"is_item_finder_info_modified", NativeEventTypeIsItemFinderInfoMod, NULL, "True if FinderInfo for self.path was modified", NULL},
221     {"is_owner_change", NativeEventTypeIsChangeOwner, NULL, "True if self.path had its ownership changed", NULL},
222     {"is_xattr_mod", NativeEventTypeIsXattrMod, NULL, "True if extended attributes for self.path were modified ", NULL},
223     {"is_file", NativeEventTypeIsFile, NULL, "True if self.path is a file", NULL},
224     {"is_directory", NativeEventTypeIsDirectory, NULL, "True if self.path is a directory", NULL},
225     {"is_symlink", NativeEventTypeIsSymlink, NULL, "True if self.path is a symbolic link", NULL},
226     {"is_own_event", NativeEventTypeIsOwnEvent, NULL, "True if the event originated from our own process", NULL},
227     {"is_hardlink", NativeEventTypeIsHardlink, NULL, "True if self.path is a hard link", NULL},
228     {"is_last_hardlink", NativeEventTypeIsLastHardlink, NULL, "True if self.path was the last hard link", NULL},
229     {"is_cloned", NativeEventTypeIsCloned, NULL, "True if self.path is a clone or was cloned", NULL},
230     {NULL, NULL, NULL, NULL, NULL},
231 };
232 
233 
234 static PyTypeObject NativeEventType = {
235     PyVarObject_HEAD_INIT(NULL, 0)
236     .tp_name = "_watchdog_fsevents.NativeEvent",
237     .tp_doc = "A wrapper around native FSEvents events",
238     .tp_basicsize = sizeof(NativeEventObject),
239     .tp_itemsize = 0,
240     .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
241     .tp_new = PyType_GenericNew,
242     .tp_getset = NativeEventProperties,
243     .tp_init = (initproc) NativeEventInit,
244     .tp_repr = (reprfunc) NativeEventRepr,
245     .tp_dealloc = (destructor) NativeEventDealloc,
246 };
247 
248 
249 /**
250  * Dictionary to keep track of which run loop
251  * belongs to which emitter thread.
252  */
253 PyObject *thread_to_run_loop = NULL;
254 
255 /**
256  * Dictionary to keep track of which stream
257  * belongs to which watch.
258  */
259 PyObject *watch_to_stream = NULL;
260 
261 
262 /**
263  * PyCapsule destructor.
264  */
watchdog_pycapsule_destructor(PyObject * ptr)265 static void watchdog_pycapsule_destructor(PyObject *ptr)
266 {
267     void *p = PyCapsule_GetPointer(ptr, NULL);
268     if (p) {
269         PyMem_Free(p);
270     }
271 }
272 
273 
274 
275 /**
276  * Converts a ``CFStringRef`` to a Python string object.
277  *
278  * :param cf_string:
279  *      A ``CFStringRef``.
280  * :returns:
281  *      A Python unicode or utf-8 encoded bytestring object.
282  */
CFString_AsPyUnicode(CFStringRef cf_string_ref)283 PyObject * CFString_AsPyUnicode(CFStringRef cf_string_ref)
284 {
285 
286     if (G_IS_NULL(cf_string_ref)) {
287         return PyUnicode_FromString("");
288     }
289 
290     PyObject *py_string;
291 
292     const char *c_string_ptr = CFStringGetCStringPtr(cf_string_ref, kCFStringEncodingUTF8);
293 
294     if (G_IS_NULL(c_string_ptr)) {
295         CFIndex length = CFStringGetLength(cf_string_ref);
296         CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
297         char *buffer = (char *)malloc(max_size);
298         if (CFStringGetCString(cf_string_ref, buffer, max_size, kCFStringEncodingUTF8)) {
299             py_string = PyUnicode_FromString(buffer);
300         }
301         else {
302             py_string = PyUnicode_FromString("");
303         }
304         free(buffer);
305     } else {
306         py_string = PyUnicode_FromString(c_string_ptr);
307     }
308 
309     return py_string;
310 
311 }
312 
313 /**
314  * Converts a ``CFNumberRef`` to a Python string object.
315  *
316  * :param cf_number:
317  *      A ``CFNumberRef``.
318  * :returns:
319  *      A Python unicode or utf-8 encoded bytestring object.
320  */
CFNumberRef_AsPyLong(CFNumberRef cf_number)321 PyObject * CFNumberRef_AsPyLong(CFNumberRef cf_number)
322 {
323     long c_int;
324     PyObject *py_long;
325 
326     CFNumberGetValue(cf_number, kCFNumberSInt64Type, &c_int);
327 
328     py_long = PyLong_FromLong(c_int);
329 
330     return py_long;
331 }
332 
333 
334 /**
335  * This is the callback passed to the FSEvents API, which calls
336  * the Python callback function, in turn, by passing in event data
337  * as Python objects.
338  *
339  * :param stream_ref:
340  *     A pointer to an ``FSEventStream`` instance.
341  * :param stream_callback_info_ref:
342  *     Callback context information passed by the FSEvents API.
343  *     This contains a reference to the Python callback that this
344  *     function calls in turn with information about the events.
345  * :param num_events:
346  *     An unsigned integer representing the number of events
347  *     captured by the FSEvents API.
348  * :param event_paths:
349  *     An array of NUL-terminated C strings representing event paths.
350  * :param event_flags:
351  *     An array of ``FSEventStreamEventFlags`` unsigned integral
352  *     mask values.
353  * :param event_ids:
354  *     An array of 64-bit unsigned integers representing event
355  *     identifiers.
356  */
357 static void
watchdog_FSEventStreamCallback(ConstFSEventStreamRef stream_ref,StreamCallbackInfo * stream_callback_info_ref,size_t num_events,CFArrayRef event_path_info_array_ref,const FSEventStreamEventFlags event_flags[],const FSEventStreamEventId event_ids[])358 watchdog_FSEventStreamCallback(ConstFSEventStreamRef          stream_ref,
359                                StreamCallbackInfo            *stream_callback_info_ref,
360                                size_t                         num_events,
361                                CFArrayRef                     event_path_info_array_ref,
362                                const FSEventStreamEventFlags  event_flags[],
363                                const FSEventStreamEventId     event_ids[])
364 {
365     UNUSED(stream_ref);
366     size_t i = 0;
367     CFDictionaryRef path_info_dict;
368     CFStringRef cf_path;
369     CFNumberRef cf_inode;
370     PyObject *callback_result = NULL;
371     PyObject *path = NULL;
372     PyObject *inode = NULL;
373     PyObject *id = NULL;
374     PyObject *flags = NULL;
375     PyObject *py_event_flags = NULL;
376     PyObject *py_event_ids = NULL;
377     PyObject *py_event_paths = NULL;
378     PyObject *py_event_inodes = NULL;
379     PyThreadState *saved_thread_state = NULL;
380 
381     /* Acquire interpreter lock and save original thread state. */
382     PyGILState_STATE gil_state = PyGILState_Ensure();
383     saved_thread_state = PyThreadState_Swap(stream_callback_info_ref->thread_state);
384 
385     /* Convert event flags and paths to Python ints and strings. */
386     py_event_paths = PyList_New(num_events);
387     py_event_inodes = PyList_New(num_events);
388     py_event_flags = PyList_New(num_events);
389     py_event_ids = PyList_New(num_events);
390     if (G_NOT(py_event_paths && py_event_inodes && py_event_flags && py_event_ids))
391     {
392         Py_XDECREF(py_event_paths);
393         Py_XDECREF(py_event_inodes);
394         Py_XDECREF(py_event_ids);
395         Py_XDECREF(py_event_flags);
396         return /*NULL*/;
397     }
398     for (i = 0; i < num_events; ++i)
399     {
400         id = PyLong_FromLongLong(event_ids[i]);
401         flags = PyLong_FromLong(event_flags[i]);
402 
403         path_info_dict = CFArrayGetValueAtIndex(event_path_info_array_ref, i);
404         cf_path = CFDictionaryGetValue(path_info_dict, kFSEventStreamEventExtendedDataPathKey);
405         cf_inode = CFDictionaryGetValue(path_info_dict, kFSEventStreamEventExtendedFileIDKey);
406 
407         path = CFString_AsPyUnicode(cf_path);
408 
409         if (G_IS_NOT_NULL(cf_inode)) {
410             inode = CFNumberRef_AsPyLong(cf_inode);
411         } else {
412             Py_INCREF(Py_None);
413             inode = Py_None;
414         }
415 
416         if (G_NOT(path && inode && flags && id))
417         {
418             Py_DECREF(py_event_paths);
419             Py_DECREF(py_event_inodes);
420             Py_DECREF(py_event_ids);
421             Py_DECREF(py_event_flags);
422             return /*NULL*/;
423         }
424         PyList_SET_ITEM(py_event_paths, i, path);
425         PyList_SET_ITEM(py_event_inodes, i, inode);
426         PyList_SET_ITEM(py_event_flags, i, flags);
427         PyList_SET_ITEM(py_event_ids, i, id);
428     }
429 
430     /* Call the Python callback function supplied by the stream information
431      * struct. The Python callback function should accept two arguments,
432      * both being Python lists:
433      *
434      *    def python_callback(event_paths, event_flags, event_ids):
435      *        pass
436      */
437     callback_result = \
438         PyObject_CallFunction(stream_callback_info_ref->python_callback,
439                               "OOOO", py_event_paths, py_event_inodes, py_event_flags, py_event_ids);
440     if (G_IS_NULL(callback_result))
441     {
442         if (G_NOT(PyErr_Occurred()))
443         {
444             PyErr_SetString(PyExc_ValueError, ERROR_CANNOT_CALL_CALLBACK);
445         }
446         CFRunLoopStop(stream_callback_info_ref->run_loop_ref);
447     }
448 
449     /* Release the lock and restore thread state. */
450     PyThreadState_Swap(saved_thread_state);
451     PyGILState_Release(gil_state);
452 }
453 
454 
455 /**
456  * Converts a Python string object to an UTF-8 encoded ``CFStringRef``.
457  *
458  * :param py_string:
459  *      A Python unicode or utf-8 encoded bytestring object.
460  * :returns:
461  *      A new ``CFStringRef`` with the contents of ``py_string``, or ``NULL`` if an error occurred.
462  */
PyString_AsUTF8EncodedCFStringRef(PyObject * py_string)463 CFStringRef PyString_AsUTF8EncodedCFStringRef(PyObject *py_string)
464 {
465     CFStringRef cf_string = NULL;
466 
467     if (PyUnicode_Check(py_string)) {
468         PyObject* helper = PyUnicode_AsUTF8String(py_string);
469         if (!helper) {
470             return NULL;
471         }
472         cf_string = CFStringCreateWithCString(kCFAllocatorDefault, PyBytes_AS_STRING(helper), kCFStringEncodingUTF8);
473         Py_DECREF(helper);
474     } else if (PyBytes_Check(py_string)) {
475         PyObject *utf8 = PyUnicode_FromEncodedObject(py_string, NULL, "strict");
476         if (!utf8) {
477             return NULL;
478         }
479         Py_DECREF(utf8);
480         cf_string = CFStringCreateWithCString(kCFAllocatorDefault, PyBytes_AS_STRING(py_string), kCFStringEncodingUTF8);
481     } else {
482         PyErr_SetString(PyExc_TypeError, "Path to watch must be a string or a UTF-8 encoded bytes object.");
483         return NULL;
484     }
485 
486     return cf_string;
487 }
488 
489 /**
490  * Converts a list of Python strings to a ``CFMutableArray`` of
491  * UTF-8 encoded ``CFString`` instances and returns a pointer to
492  * the array.
493  *
494  * :param py_string_list:
495  *     List of Python strings.
496  * :returns:
497  *     A pointer to ``CFMutableArray`` (that is, a
498  *     ``CFMutableArrayRef``) of UTF-8 encoded ``CFString``
499  *     instances.
500  */
501 static CFMutableArrayRef
watchdog_CFMutableArrayRef_from_PyStringList(PyObject * py_string_list)502 watchdog_CFMutableArrayRef_from_PyStringList(PyObject *py_string_list)
503 {
504     Py_ssize_t i = 0;
505     Py_ssize_t string_list_size = 0;
506     CFMutableArrayRef array_of_cf_string = NULL;
507     CFStringRef cf_string = NULL;
508     PyObject *py_string = NULL;
509 
510     G_RETURN_NULL_IF_NULL(py_string_list);
511 
512     string_list_size = PyList_Size(py_string_list);
513 
514     /* Allocate a CFMutableArray. */
515     array_of_cf_string = CFArrayCreateMutable(kCFAllocatorDefault, 1,
516                                               &kCFTypeArrayCallBacks);
517     G_RETURN_NULL_IF_NULL(array_of_cf_string);
518 
519     /* Loop through the Python string list and copy strings to the
520      * CFString array list. */
521     for (i = 0; i < string_list_size; ++i)
522     {
523         py_string = PyList_GetItem(py_string_list, i);
524         G_RETURN_NULL_IF_NULL(py_string);
525         cf_string = PyString_AsUTF8EncodedCFStringRef(py_string);
526         G_RETURN_NULL_IF_NULL(cf_string);
527         CFArraySetValueAtIndex(array_of_cf_string, i, cf_string);
528         CFRelease(cf_string);
529     }
530 
531     return array_of_cf_string;
532 }
533 
534 
535 /**
536  * Creates an instance of ``FSEventStream`` and returns a pointer
537  * to the instance.
538  *
539  * :param stream_callback_info_ref:
540  *      Pointer to the callback context information that will be
541  *      passed by the FSEvents API to the callback handler specified
542  *      by the ``callback`` argument to this function. This
543  *      information contains a reference to the Python callback that
544  *      it must call in turn passing on the event information
545  *      as Python objects to the the Python callback.
546  * :param py_paths:
547  *      A Python list of Python strings representing path names
548  *      to monitor.
549  * :param callback:
550  *      A function pointer of type ``FSEventStreamCallback``.
551  * :returns:
552  *      A pointer to an ``FSEventStream`` instance (that is, it returns
553  *      an ``FSEventStreamRef``).
554  */
555 static FSEventStreamRef
watchdog_FSEventStreamCreate(StreamCallbackInfo * stream_callback_info_ref,PyObject * py_paths,FSEventStreamCallback callback)556 watchdog_FSEventStreamCreate(StreamCallbackInfo *stream_callback_info_ref,
557                              PyObject *py_paths,
558                              FSEventStreamCallback callback)
559 {
560     CFAbsoluteTime stream_latency = 0.01;
561     CFMutableArrayRef paths = NULL;
562     FSEventStreamRef stream_ref = NULL;
563 
564     /* Check arguments. */
565     G_RETURN_NULL_IF_NULL(py_paths);
566     G_RETURN_NULL_IF_NULL(callback);
567 
568     /* Convert the Python paths list to a CFMutableArray. */
569     paths = watchdog_CFMutableArrayRef_from_PyStringList(py_paths);
570     G_RETURN_NULL_IF_NULL(paths);
571 
572     /* Create the event stream. */
573     FSEventStreamContext stream_context = {
574         0, stream_callback_info_ref, NULL, NULL, NULL
575     };
576     stream_ref = FSEventStreamCreate(kCFAllocatorDefault,
577                                      callback,
578                                      &stream_context,
579                                      paths,
580                                      kFSEventStreamEventIdSinceNow,
581                                      stream_latency,
582                                      kFSEventStreamCreateFlagNoDefer
583                                      | kFSEventStreamCreateFlagFileEvents
584                                      | kFSEventStreamCreateFlagWatchRoot
585                                      | kFSEventStreamCreateFlagUseExtendedData
586                                      | kFSEventStreamCreateFlagUseCFTypes);
587     CFRelease(paths);
588     return stream_ref;
589 }
590 
591 
592 PyDoc_STRVAR(watchdog_add_watch__doc__,
593         MODULE_NAME ".add_watch(emitter_thread, watch, callback, paths) -> None\
594 \nAdds a watch into the event loop for the given emitter thread.\n\n\
595 :param emitter_thread:\n\
596     The emitter thread.\n\
597 :param watch:\n\
598     The watch to add.\n\
599 :param callback:\n\
600     The callback function to call when an event occurs.\n\n\
601     Example::\n\n\
602         def callback(paths, flags, ids):\n\
603             for path, flag, event_id in zip(paths, flags, ids):\n\
604                 print(\"%d: %s=%ul\" % (event_id, path, flag))\n\
605 :param paths:\n\
606     A list of paths to monitor.\n");
607 static PyObject *
watchdog_add_watch(PyObject * self,PyObject * args)608 watchdog_add_watch(PyObject *self, PyObject *args)
609 {
610     UNUSED(self);
611     FSEventStreamRef stream_ref = NULL;
612     StreamCallbackInfo *stream_callback_info_ref = NULL;
613     CFRunLoopRef run_loop_ref = NULL;
614     PyObject *emitter_thread = NULL;
615     PyObject *watch = NULL;
616     PyObject *paths_to_watch = NULL;
617     PyObject *python_callback = NULL;
618     PyObject *value = NULL;
619 
620     /* Ensure all arguments are received. */
621     G_RETURN_NULL_IF_NOT(PyArg_ParseTuple(args, "OOOO:schedule",
622                                           &emitter_thread, &watch,
623                                           &python_callback, &paths_to_watch));
624 
625     /* Watch must not already be scheduled. */
626     if(PyDict_Contains(watch_to_stream, watch) == 1) {
627         PyErr_Format(PyExc_RuntimeError, "Cannot add watch %S - it is already scheduled", watch);
628         return NULL;
629     }
630 
631     /* Create an instance of the callback information structure. */
632     stream_callback_info_ref = PyMem_New(StreamCallbackInfo, 1);
633     if(stream_callback_info_ref == NULL) {
634         PyErr_SetString(PyExc_SystemError, "Failed allocating stream callback info");
635         return NULL;
636     }
637 
638     /* Create an FSEvent stream and
639      * Save the stream reference to the global watch-to-stream dictionary. */
640     stream_ref = watchdog_FSEventStreamCreate(stream_callback_info_ref,
641                                               paths_to_watch,
642                                               (FSEventStreamCallback) &watchdog_FSEventStreamCallback);
643     if (!stream_ref) {
644         PyMem_Del(stream_callback_info_ref);
645         PyErr_SetString(PyExc_RuntimeError, "Failed creating fsevent stream");
646         return NULL;
647     }
648     value = PyCapsule_New(stream_ref, NULL, watchdog_pycapsule_destructor);
649     if (!value || !PyCapsule_IsValid(value, NULL)) {
650         PyMem_Del(stream_callback_info_ref);
651         FSEventStreamInvalidate(stream_ref);
652         FSEventStreamRelease(stream_ref);
653         return NULL;
654     }
655     PyDict_SetItem(watch_to_stream, watch, value);
656 
657     /* Get a reference to the runloop for the emitter thread
658      * or to the current runloop. */
659     value = PyDict_GetItem(thread_to_run_loop, emitter_thread);
660     if (G_IS_NULL(value))
661     {
662         run_loop_ref = CFRunLoopGetCurrent();
663     }
664     else
665     {
666         run_loop_ref = PyCapsule_GetPointer(value, NULL);
667     }
668 
669     /* Schedule the stream with the obtained runloop. */
670     FSEventStreamScheduleWithRunLoop(stream_ref, run_loop_ref, kCFRunLoopDefaultMode);
671 
672     /* Set the stream information for the callback.
673      * This data will be passed to our watchdog_FSEventStreamCallback function
674      * by the FSEvents API whenever an event occurs.
675      */
676     stream_callback_info_ref->python_callback = python_callback;
677     stream_callback_info_ref->stream_ref = stream_ref;
678     stream_callback_info_ref->run_loop_ref = run_loop_ref;
679     stream_callback_info_ref->thread_state = PyThreadState_Get();
680     Py_INCREF(python_callback);
681 
682     /* Start the event stream. */
683     if (G_NOT(FSEventStreamStart(stream_ref)))
684     {
685         FSEventStreamInvalidate(stream_ref);
686         FSEventStreamRelease(stream_ref);
687         // There's no documentation on _why_ this might fail - "it ought to always succeed". But if it fails the
688         // documentation says to "fall back to performing recursive scans of the directories [...] as appropriate".
689         PyErr_SetString(PyExc_SystemError, "Cannot start fsevents stream. Use a kqueue or polling observer instead.");
690         return NULL;
691     }
692 
693     Py_INCREF(Py_None);
694     return Py_None;
695 }
696 
697 
698 PyDoc_STRVAR(watchdog_read_events__doc__,
699              MODULE_NAME ".read_events(emitter_thread) -> None\n\
700 Blocking function that runs an event loop associated with an emitter thread.\n\n\
701 :param emitter_thread:\n\
702     The emitter thread for which the event loop will be run.\n");
703 static PyObject *
watchdog_read_events(PyObject * self,PyObject * args)704 watchdog_read_events(PyObject *self, PyObject *args)
705 {
706     UNUSED(self);
707     CFRunLoopRef run_loop_ref = NULL;
708     PyObject *emitter_thread = NULL;
709     PyObject *value = NULL;
710 
711     G_RETURN_NULL_IF_NOT(PyArg_ParseTuple(args, "O:loop", &emitter_thread));
712 
713 // PyEval_InitThreads() does nothing as of Python 3.7 and is deprecated in 3.9.
714 // https://docs.python.org/3/c-api/init.html#c.PyEval_InitThreads
715 #if PY_VERSION_HEX < 0x030700f0
716     PyEval_InitThreads();
717 #endif
718 
719     /* Allocate information and store thread state. */
720     value = PyDict_GetItem(thread_to_run_loop, emitter_thread);
721     if (G_IS_NULL(value))
722     {
723         run_loop_ref = CFRunLoopGetCurrent();
724         value = PyCapsule_New(run_loop_ref, NULL, watchdog_pycapsule_destructor);
725         PyDict_SetItem(thread_to_run_loop, emitter_thread, value);
726         Py_INCREF(emitter_thread);
727         Py_INCREF(value);
728     }
729 
730     /* No timeout, block until events. */
731     Py_BEGIN_ALLOW_THREADS;
732     CFRunLoopRun();
733     Py_END_ALLOW_THREADS;
734 
735     /* Clean up state information. */
736     if (PyDict_DelItem(thread_to_run_loop, emitter_thread) == 0)
737     {
738         Py_DECREF(emitter_thread);
739         Py_INCREF(value);
740     }
741 
742     G_RETURN_NULL_IF(PyErr_Occurred());
743 
744     Py_INCREF(Py_None);
745     return Py_None;
746 }
747 
748 PyDoc_STRVAR(watchdog_flush_events__doc__,
749         MODULE_NAME ".flush_events(watch) -> None\n\
750 Flushes events for the watch.\n\n\
751 :param watch:\n\
752     The watch to flush.\n");
753 static PyObject *
watchdog_flush_events(PyObject * self,PyObject * watch)754 watchdog_flush_events(PyObject *self, PyObject *watch)
755 {
756     UNUSED(self);
757     PyObject *value = PyDict_GetItem(watch_to_stream, watch);
758 
759     FSEventStreamRef stream_ref = PyCapsule_GetPointer(value, NULL);
760 
761     FSEventStreamFlushSync(stream_ref);
762 
763     Py_INCREF(Py_None);
764     return Py_None;
765 }
766 
767 PyDoc_STRVAR(watchdog_remove_watch__doc__,
768         MODULE_NAME ".remove_watch(watch) -> None\n\
769 Removes a watch from the event loop.\n\n\
770 :param watch:\n\
771     The watch to remove.\n");
772 static PyObject *
watchdog_remove_watch(PyObject * self,PyObject * watch)773 watchdog_remove_watch(PyObject *self, PyObject *watch)
774 {
775     UNUSED(self);
776     PyObject *streamref_capsule = PyDict_GetItem(watch_to_stream, watch);
777     if (!streamref_capsule) {
778         // A watch might have been removed explicitly before, in which case we can simply early out.
779         Py_RETURN_NONE;
780     }
781     PyDict_DelItem(watch_to_stream, watch);
782 
783     FSEventStreamRef stream_ref = PyCapsule_GetPointer(streamref_capsule, NULL);
784 
785     FSEventStreamStop(stream_ref);
786     FSEventStreamInvalidate(stream_ref);
787     FSEventStreamRelease(stream_ref);
788 
789     Py_RETURN_NONE;
790 }
791 
792 PyDoc_STRVAR(watchdog_stop__doc__,
793         MODULE_NAME ".stop(emitter_thread) -> None\n\
794 Stops running the event loop from the specified thread.\n\n\
795 :param emitter_thread:\n\
796     The thread for which the event loop will be stopped.\n");
797 static PyObject *
watchdog_stop(PyObject * self,PyObject * emitter_thread)798 watchdog_stop(PyObject *self, PyObject *emitter_thread)
799 {
800     UNUSED(self);
801     PyObject *value = PyDict_GetItem(thread_to_run_loop, emitter_thread);
802     if (G_IS_NULL(value)) {
803       goto success;
804     }
805 
806     CFRunLoopRef run_loop_ref = PyCapsule_GetPointer(value, NULL);
807     G_RETURN_NULL_IF(PyErr_Occurred());
808 
809     /* Stop the run loop. */
810     if (G_IS_NOT_NULL(run_loop_ref))
811     {
812         CFRunLoopStop(run_loop_ref);
813     }
814 
815  success:
816     Py_INCREF(Py_None);
817     return Py_None;
818 }
819 
820 
821 /******************************************************************************
822  * Module initialization.
823  *****************************************************************************/
824 
825 PyDoc_STRVAR(watchdog_fsevents_module__doc__,
826              "Low-level FSEvents Python/C API bridge.");
827 
828 static PyMethodDef watchdog_fsevents_methods[] =
829 {
830     {"add_watch",    watchdog_add_watch,    METH_VARARGS, watchdog_add_watch__doc__},
831     {"read_events",  watchdog_read_events,  METH_VARARGS, watchdog_read_events__doc__},
832     {"flush_events", watchdog_flush_events, METH_O,       watchdog_flush_events__doc__},
833     {"remove_watch", watchdog_remove_watch, METH_O,       watchdog_remove_watch__doc__},
834 
835     /* Aliases for compatibility with macfsevents. */
836     {"schedule",     watchdog_add_watch,    METH_VARARGS, "Alias for add_watch."},
837     {"loop",         watchdog_read_events,  METH_VARARGS, "Alias for read_events."},
838     {"unschedule",   watchdog_remove_watch, METH_O,       "Alias for remove_watch."},
839 
840     {"stop",         watchdog_stop,         METH_O,       watchdog_stop__doc__},
841 
842     {NULL, NULL, 0, NULL},
843 };
844 
845 
846 /**
847  * Initialize the module globals.
848  */
849 static void
watchdog_module_init(void)850 watchdog_module_init(void)
851 {
852     thread_to_run_loop = PyDict_New();
853     watch_to_stream = PyDict_New();
854 }
855 
856 
857 /**
858  * Adds various attributes to the Python module.
859  *
860  * :param module:
861  *     A pointer to the Python module object to inject
862  *     the attributes into.
863  */
864 static void
watchdog_module_add_attributes(PyObject * module)865 watchdog_module_add_attributes(PyObject *module)
866 {
867     PyObject *version_tuple = Py_BuildValue("(iii)",
868                                             WATCHDOG_VERSION_MAJOR,
869                                             WATCHDOG_VERSION_MINOR,
870                                             WATCHDOG_VERSION_BUILD);
871     PyModule_AddIntConstant(module,
872                             "POLLIN",
873                             kCFFileDescriptorReadCallBack);
874     PyModule_AddIntConstant(module,
875                             "POLLOUT",
876                             kCFFileDescriptorWriteCallBack);
877 
878     /* Adds version information. */
879     PyModule_AddObject(module,
880                        "__version__",
881                        version_tuple);
882     PyModule_AddObject(module,
883                        "version_string",
884                        Py_BuildValue("s", WATCHDOG_VERSION_STRING));
885 }
886 
887 static struct PyModuleDef watchdog_fsevents_module = {
888     PyModuleDef_HEAD_INIT,
889     MODULE_NAME,
890     watchdog_fsevents_module__doc__,
891     -1,
892     watchdog_fsevents_methods,
893     NULL,  /* m_slots */
894     NULL,  /* m_traverse */
895     0,     /* m_clear */
896     NULL   /* m_free */
897 };
898 
899 /**
900  * Initialize the Python 3.x module.
901  */
902 PyMODINIT_FUNC
PyInit__watchdog_fsevents(void)903 PyInit__watchdog_fsevents(void){
904     G_RETURN_NULL_IF(PyType_Ready(&NativeEventType) < 0);
905     PyObject *module = PyModule_Create(&watchdog_fsevents_module);
906     G_RETURN_NULL_IF_NULL(module);
907     Py_INCREF(&NativeEventType);
908     if (PyModule_AddObject(module, "NativeEvent", (PyObject*)&NativeEventType) < 0) {
909         Py_DECREF(&NativeEventType);
910         Py_DECREF(module);
911         return NULL;
912     }
913     watchdog_module_add_attributes(module);
914     watchdog_module_init();
915     return module;
916 }
917