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 <string.h> /* strcmp() */
10 
11 #include <event2/event.h>
12 #include <event2/util.h>
13 
14 #define __LIBTRANSMISSION_WATCHDIR_MODULE__
15 
16 #include "transmission.h"
17 #include "error.h"
18 #include "error-types.h"
19 #include "file.h"
20 #include "log.h"
21 #include "ptrarray.h"
22 #include "tr-assert.h"
23 #include "utils.h"
24 #include "watchdir.h"
25 #include "watchdir-common.h"
26 
27 /***
28 ****
29 ***/
30 
31 #define log_debug(...) (!tr_logLevelIsActive(TR_LOG_DEBUG) ? (void)0 : \
32     tr_logAddMessage(__FILE__, __LINE__, TR_LOG_DEBUG, "watchdir", __VA_ARGS__))
33 
34 #define log_error(...) (!tr_logLevelIsActive(TR_LOG_ERROR) ? (void)0 : \
35     tr_logAddMessage(__FILE__, __LINE__, TR_LOG_ERROR, "watchdir", __VA_ARGS__))
36 
37 /***
38 ****
39 ***/
40 
41 struct tr_watchdir
42 {
43     char* path;
44     tr_watchdir_cb callback;
45     void* callback_user_data;
46     struct event_base* event_base;
47     tr_watchdir_backend* backend;
48     tr_ptrArray active_retries;
49 };
50 
51 /***
52 ****
53 ***/
54 
is_regular_file(char const * dir,char const * name)55 static bool is_regular_file(char const* dir, char const* name)
56 {
57     char* const path = tr_buildPath(dir, name, NULL);
58     tr_sys_path_info path_info;
59     tr_error* error = NULL;
60     bool ret;
61 
62     if ((ret = tr_sys_path_get_info(path, 0, &path_info, &error)))
63     {
64         ret = path_info.type == TR_SYS_PATH_IS_FILE;
65     }
66     else
67     {
68         if (!TR_ERROR_IS_ENOENT(error->code))
69         {
70             log_error("Failed to get type of \"%s\" (%d): %s", path, error->code, error->message);
71         }
72 
73         tr_error_free(error);
74     }
75 
76     tr_free(path);
77     return ret;
78 }
79 
watchdir_status_to_string(tr_watchdir_status status)80 static char const* watchdir_status_to_string(tr_watchdir_status status)
81 {
82     switch (status)
83     {
84     case TR_WATCHDIR_ACCEPT:
85         return "accept";
86 
87     case TR_WATCHDIR_IGNORE:
88         return "ignore";
89 
90     case TR_WATCHDIR_RETRY:
91         return "retry";
92 
93     default:
94         return "???";
95     }
96 }
97 
tr_watchdir_process_impl(tr_watchdir_t handle,char const * name)98 static tr_watchdir_status tr_watchdir_process_impl(tr_watchdir_t handle, char const* name)
99 {
100     /* File may be gone while we're retrying */
101     if (!is_regular_file(tr_watchdir_get_path(handle), name))
102     {
103         return TR_WATCHDIR_IGNORE;
104     }
105 
106     tr_watchdir_status const ret = (*handle->callback)(handle, name, handle->callback_user_data);
107 
108     TR_ASSERT(ret == TR_WATCHDIR_ACCEPT || ret == TR_WATCHDIR_IGNORE || ret == TR_WATCHDIR_RETRY);
109 
110     log_debug("Callback decided to %s file \"%s\"", watchdir_status_to_string(ret), name);
111 
112     return ret;
113 }
114 
115 /***
116 ****
117 ***/
118 
119 typedef struct tr_watchdir_retry
120 {
121     tr_watchdir_t handle;
122     char* name;
123     unsigned int counter;
124     struct event* timer;
125     struct timeval interval;
126 }
127 tr_watchdir_retry;
128 
129 /* Non-static and mutable for unit tests */
130 unsigned int tr_watchdir_retry_limit = 3;
131 struct timeval tr_watchdir_retry_start_interval = { .tv_sec = 1, .tv_usec = 0 };
132 struct timeval tr_watchdir_retry_max_interval = { .tv_sec = 10, .tv_usec = 0 };
133 
134 #define tr_watchdir_retries_init(r) (void)0
135 #define tr_watchdir_retries_destroy(r) tr_ptrArrayDestruct((r), (PtrArrayForeachFunc) & tr_watchdir_retry_free)
136 #define tr_watchdir_retries_insert(r, v) tr_ptrArrayInsertSorted((r), (v), &compare_retry_names)
137 #define tr_watchdir_retries_remove(r, v) tr_ptrArrayRemoveSortedPointer((r), (v), &compare_retry_names)
138 #define tr_watchdir_retries_find(r, v) tr_ptrArrayFindSorted((r), (v), &compare_retry_names)
139 
compare_retry_names(void const * a,void const * b)140 static int compare_retry_names(void const* a, void const* b)
141 {
142     return strcmp(((tr_watchdir_retry*)a)->name, ((tr_watchdir_retry*)b)->name);
143 }
144 
145 static void tr_watchdir_retry_free(tr_watchdir_retry* retry);
146 
tr_watchdir_on_retry_timer(evutil_socket_t fd UNUSED,short type UNUSED,void * context)147 static void tr_watchdir_on_retry_timer(evutil_socket_t fd UNUSED, short type UNUSED, void* context)
148 {
149     TR_ASSERT(context != NULL);
150 
151     tr_watchdir_retry* const retry = context;
152     tr_watchdir_t const handle = retry->handle;
153 
154     if (tr_watchdir_process_impl(handle, retry->name) == TR_WATCHDIR_RETRY)
155     {
156         if (++retry->counter < tr_watchdir_retry_limit)
157         {
158             evutil_timeradd(&retry->interval, &retry->interval, &retry->interval);
159 
160             if (evutil_timercmp(&retry->interval, &tr_watchdir_retry_max_interval, >))
161             {
162                 retry->interval = tr_watchdir_retry_max_interval;
163             }
164 
165             evtimer_del(retry->timer);
166             evtimer_add(retry->timer, &retry->interval);
167             return;
168         }
169 
170         log_error("Failed to add (corrupted?) torrent file: %s", retry->name);
171     }
172 
173     tr_watchdir_retries_remove(&handle->active_retries, retry);
174     tr_watchdir_retry_free(retry);
175 }
176 
tr_watchdir_retry_new(tr_watchdir_t handle,char const * name)177 static tr_watchdir_retry* tr_watchdir_retry_new(tr_watchdir_t handle, char const* name)
178 {
179     tr_watchdir_retry* retry;
180 
181     retry = tr_new0(tr_watchdir_retry, 1);
182     retry->handle = handle;
183     retry->name = tr_strdup(name);
184     retry->timer = evtimer_new(handle->event_base, &tr_watchdir_on_retry_timer, retry);
185     retry->interval = tr_watchdir_retry_start_interval;
186 
187     evtimer_add(retry->timer, &retry->interval);
188 
189     return retry;
190 }
191 
tr_watchdir_retry_free(tr_watchdir_retry * retry)192 static void tr_watchdir_retry_free(tr_watchdir_retry* retry)
193 {
194     if (retry == NULL)
195     {
196         return;
197     }
198 
199     if (retry->timer != NULL)
200     {
201         evtimer_del(retry->timer);
202         event_free(retry->timer);
203     }
204 
205     tr_free(retry->name);
206     tr_free(retry);
207 }
208 
tr_watchdir_retry_restart(tr_watchdir_retry * retry)209 static void tr_watchdir_retry_restart(tr_watchdir_retry* retry)
210 {
211     TR_ASSERT(retry != NULL);
212 
213     evtimer_del(retry->timer);
214 
215     retry->counter = 0;
216     retry->interval = tr_watchdir_retry_start_interval;
217 
218     evtimer_add(retry->timer, &retry->interval);
219 }
220 
221 /***
222 ****
223 ***/
224 
tr_watchdir_new(char const * path,tr_watchdir_cb callback,void * callback_user_data,struct event_base * event_base,bool force_generic)225 tr_watchdir_t tr_watchdir_new(char const* path, tr_watchdir_cb callback, void* callback_user_data,
226     struct event_base* event_base, bool force_generic)
227 {
228     tr_watchdir_t handle;
229 
230     handle = tr_new0(struct tr_watchdir, 1);
231     handle->path = tr_strdup(path);
232     handle->callback = callback;
233     handle->callback_user_data = callback_user_data;
234     handle->event_base = event_base;
235     tr_watchdir_retries_init(&handle->active_retries);
236 
237     if (!force_generic)
238     {
239 #ifdef WITH_INOTIFY
240 
241         if (handle->backend == NULL)
242         {
243             handle->backend = tr_watchdir_inotify_new(handle);
244         }
245 
246 #endif
247 
248 #ifdef WITH_KQUEUE
249 
250         if (handle->backend == NULL)
251         {
252             handle->backend = tr_watchdir_kqueue_new(handle);
253         }
254 
255 #endif
256 
257 #ifdef _WIN32
258 
259         if (handle->backend == NULL)
260         {
261             handle->backend = tr_watchdir_win32_new(handle);
262         }
263 
264 #endif
265     }
266 
267     if (handle->backend == NULL)
268     {
269         handle->backend = tr_watchdir_generic_new(handle);
270     }
271 
272     if (handle->backend == NULL)
273     {
274         tr_watchdir_free(handle);
275         handle = NULL;
276     }
277     else
278     {
279         TR_ASSERT(handle->backend->free_func != NULL);
280     }
281 
282     return handle;
283 }
284 
tr_watchdir_free(tr_watchdir_t handle)285 void tr_watchdir_free(tr_watchdir_t handle)
286 {
287     if (handle == NULL)
288     {
289         return;
290     }
291 
292     tr_watchdir_retries_destroy(&handle->active_retries);
293 
294     if (handle->backend != NULL)
295     {
296         handle->backend->free_func(handle->backend);
297     }
298 
299     tr_free(handle->path);
300     tr_free(handle);
301 }
302 
tr_watchdir_get_path(tr_watchdir_t handle)303 char const* tr_watchdir_get_path(tr_watchdir_t handle)
304 {
305     TR_ASSERT(handle != NULL);
306 
307     return handle->path;
308 }
309 
tr_watchdir_get_backend(tr_watchdir_t handle)310 tr_watchdir_backend* tr_watchdir_get_backend(tr_watchdir_t handle)
311 {
312     TR_ASSERT(handle != NULL);
313 
314     return handle->backend;
315 }
316 
tr_watchdir_get_event_base(tr_watchdir_t handle)317 struct event_base* tr_watchdir_get_event_base(tr_watchdir_t handle)
318 {
319     TR_ASSERT(handle != NULL);
320 
321     return handle->event_base;
322 }
323 
324 /***
325 ****
326 ***/
327 
tr_watchdir_process(tr_watchdir_t handle,char const * name)328 void tr_watchdir_process(tr_watchdir_t handle, char const* name)
329 {
330     TR_ASSERT(handle != NULL);
331 
332     tr_watchdir_retry const search_key = { .name = (char*)name };
333     tr_watchdir_retry* existing_retry;
334 
335     if ((existing_retry = tr_watchdir_retries_find(&handle->active_retries, &search_key)) != NULL)
336     {
337         tr_watchdir_retry_restart(existing_retry);
338         return;
339     }
340 
341     if (tr_watchdir_process_impl(handle, name) == TR_WATCHDIR_RETRY)
342     {
343         tr_watchdir_retry* retry = tr_watchdir_retry_new(handle, name);
344         tr_watchdir_retries_insert(&handle->active_retries, retry);
345     }
346 }
347 
tr_watchdir_scan(tr_watchdir_t handle,tr_ptrArray * dir_entries)348 void tr_watchdir_scan(tr_watchdir_t handle, tr_ptrArray* dir_entries)
349 {
350     tr_sys_dir_t dir;
351     char const* name;
352     tr_ptrArray new_dir_entries = TR_PTR_ARRAY_INIT_STATIC;
353     PtrArrayCompareFunc const name_compare_func = (PtrArrayCompareFunc)&strcmp;
354     tr_error* error = NULL;
355 
356     if ((dir = tr_sys_dir_open(handle->path, &error)) == TR_BAD_SYS_DIR)
357     {
358         log_error("Failed to open directory \"%s\" (%d): %s", handle->path, error->code, error->message);
359         tr_error_free(error);
360         return;
361     }
362 
363     while ((name = tr_sys_dir_read_name(dir, &error)) != NULL)
364     {
365         if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
366         {
367             continue;
368         }
369 
370         if (dir_entries != NULL)
371         {
372             tr_ptrArrayInsertSorted(&new_dir_entries, tr_strdup(name), name_compare_func);
373 
374             if (tr_ptrArrayFindSorted(dir_entries, name, name_compare_func) != NULL)
375             {
376                 continue;
377             }
378         }
379 
380         tr_watchdir_process(handle, name);
381     }
382 
383     if (error != NULL)
384     {
385         log_error("Failed to read directory \"%s\" (%d): %s", handle->path, error->code, error->message);
386         tr_error_free(error);
387     }
388 
389     tr_sys_dir_close(dir, NULL);
390 
391     if (dir_entries != NULL)
392     {
393         tr_ptrArrayDestruct(dir_entries, &tr_free);
394         *dir_entries = new_dir_entries;
395     }
396 }
397