1 /*
2 * This file Copyright (C) 2015-2016 Mnemosyne LLC
3 *
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 */
8
9 #include <errno.h>
10 #include <string.h> /* strcmp() */
11
12 #include <fcntl.h> /* open() */
13 #include <unistd.h> /* close() */
14
15 #include <sys/types.h>
16 #include <sys/event.h>
17
18 #ifndef O_EVTONLY
19 #define O_EVTONLY O_RDONLY
20 #endif
21
22 #include <event2/event.h>
23
24 #define __LIBTRANSMISSION_WATCHDIR_MODULE__
25
26 #include "transmission.h"
27 #include "log.h"
28 #include "ptrarray.h"
29 #include "tr-assert.h"
30 #include "utils.h"
31 #include "watchdir.h"
32 #include "watchdir-common.h"
33
34 /***
35 ****
36 ***/
37
38 #define log_error(...) (!tr_logLevelIsActive(TR_LOG_ERROR) ? (void)0 : \
39 tr_logAddMessage(__FILE__, __LINE__, TR_LOG_ERROR, "watchdir:kqueue", __VA_ARGS__))
40
41 /***
42 ****
43 ***/
44
45 typedef struct tr_watchdir_kqueue
46 {
47 tr_watchdir_backend base;
48
49 int kq;
50 int dirfd;
51 struct event* event;
52 tr_ptrArray dir_entries;
53 }
54 tr_watchdir_kqueue;
55
56 #define BACKEND_UPCAST(b) ((tr_watchdir_kqueue*)(b))
57
58 #define KQUEUE_WATCH_MASK (NOTE_WRITE | NOTE_EXTEND)
59
60 /***
61 ****
62 ***/
63
tr_watchdir_kqueue_on_event(evutil_socket_t fd UNUSED,short type UNUSED,void * context)64 static void tr_watchdir_kqueue_on_event(evutil_socket_t fd UNUSED, short type UNUSED, void* context)
65 {
66 tr_watchdir_t const handle = context;
67 tr_watchdir_kqueue* const backend = BACKEND_UPCAST(tr_watchdir_get_backend(handle));
68 struct kevent ke;
69 struct timespec const ts = { .tv_sec = 0, .tv_nsec = 0 };
70
71 if (kevent(backend->kq, NULL, 0, &ke, 1, &ts) == -1)
72 {
73 log_error("Failed to fetch kevent: %s", tr_strerror(errno));
74 return;
75 }
76
77 /* Read directory with generic scan */
78 tr_watchdir_scan(handle, &backend->dir_entries);
79 }
80
tr_watchdir_kqueue_free(tr_watchdir_backend * backend_base)81 static void tr_watchdir_kqueue_free(tr_watchdir_backend* backend_base)
82 {
83 tr_watchdir_kqueue* const backend = BACKEND_UPCAST(backend_base);
84
85 if (backend == NULL)
86 {
87 return;
88 }
89
90 TR_ASSERT(backend->base.free_func == &tr_watchdir_kqueue_free);
91
92 if (backend->event != NULL)
93 {
94 event_del(backend->event);
95 event_free(backend->event);
96 }
97
98 if (backend->kq != -1)
99 {
100 close(backend->kq);
101 }
102
103 if (backend->dirfd != -1)
104 {
105 close(backend->dirfd);
106 }
107
108 tr_ptrArrayDestruct(&backend->dir_entries, &tr_free);
109
110 tr_free(backend);
111 }
112
tr_watchdir_kqueue_new(tr_watchdir_t handle)113 tr_watchdir_backend* tr_watchdir_kqueue_new(tr_watchdir_t handle)
114 {
115 char const* const path = tr_watchdir_get_path(handle);
116 struct kevent ke;
117 tr_watchdir_kqueue* backend;
118
119 backend = tr_new0(tr_watchdir_kqueue, 1);
120 backend->base.free_func = &tr_watchdir_kqueue_free;
121 backend->kq = -1;
122 backend->dirfd = -1;
123
124 if ((backend->kq = kqueue()) == -1)
125 {
126 log_error("Failed to start kqueue");
127 goto fail;
128 }
129
130 /* Open fd for watching */
131 if ((backend->dirfd = open(path, O_RDONLY | O_EVTONLY)) == -1)
132 {
133 log_error("Failed to passively watch directory \"%s\": %s", path, tr_strerror(errno));
134 goto fail;
135 }
136
137 /* Register kevent filter with kqueue descriptor */
138 EV_SET(&ke, backend->dirfd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, KQUEUE_WATCH_MASK, 0, NULL);
139
140 if (kevent(backend->kq, &ke, 1, NULL, 0, NULL) == -1)
141 {
142 log_error("Failed to set directory event filter with fd %d: %s", backend->kq, tr_strerror(errno));
143 goto fail;
144 }
145
146 /* Create libevent task for event descriptor */
147 if ((backend->event = event_new(tr_watchdir_get_event_base(handle), backend->kq, EV_READ | EV_ET | EV_PERSIST,
148 &tr_watchdir_kqueue_on_event, handle)) == NULL)
149 {
150 log_error("Failed to create event: %s", tr_strerror(errno));
151 goto fail;
152 }
153
154 if (event_add(backend->event, NULL) == -1)
155 {
156 log_error("Failed to add event: %s", tr_strerror(errno));
157 goto fail;
158 }
159
160 /* Trigger one event for the initial scan */
161 event_active(backend->event, EV_READ, 0);
162
163 return BACKEND_DOWNCAST(backend);
164
165 fail:
166 tr_watchdir_kqueue_free(BACKEND_DOWNCAST(backend));
167 return NULL;
168 }
169