source: trunk/libtransmission/watchdir.c

Last change on this file was 14674, checked in by mikedld, 5 years ago

Make it possible to force generic watchdir implementation in runtime

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