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