source: trunk/libtransmission/watchdir-inotify.c

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

#5663: Rework directory watching in daemon

Implement BSD/Darwin (kqueue) and Windows (ReadDirectoryChanges?) mechanisms
for receiving directory change notifications. Use events instead of polling
for changes. Retry file parsing up to 3 times before giving up.

Huge thanks to missionsix for preparing first two versions of the patch.

  • Property svn:keywords set to Date Rev Author Id
File size: 5.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-inotify.c 14651 2016-01-02 14:28:59Z mikedld $
8 */
9
10#include <assert.h>
11#include <errno.h>
12#include <limits.h> /* NAME_MAX */
13#include <stdlib.h> /* realloc () */
14
15#include <unistd.h> /* close () */
16
17#include <sys/inotify.h>
18
19#include <event2/bufferevent.h>
20#include <event2/event.h>
21
22#define __LIBTRANSMISSION_WATCHDIR_MODULE__
23
24#include "transmission.h"
25#include "log.h"
26#include "utils.h"
27#include "watchdir.h"
28#include "watchdir-common.h"
29
30/***
31****
32***/
33
34#define log_error(...) (!tr_logLevelIsActive (TR_LOG_ERROR) ? (void) 0 : \
35  tr_logAddMessage (__FILE__, __LINE__, TR_LOG_ERROR, "watchdir:inotify", __VA_ARGS__))
36
37/***
38****
39***/
40
41typedef struct tr_watchdir_inotify
42{
43  tr_watchdir_backend   base;
44
45  int                   infd;
46  int                   inwd;
47  struct bufferevent  * event;
48}
49tr_watchdir_inotify;
50
51#define BACKEND_UPCAST(b) ((tr_watchdir_inotify *) (b))
52
53#define INOTIFY_WATCH_MASK (IN_CLOSE_WRITE | IN_MOVED_TO | IN_CREATE)
54
55/***
56****
57***/
58
59static void
60tr_watchdir_inotify_on_first_scan (evutil_socket_t   fd UNUSED,
61                                   short             type UNUSED,
62                                   void            * context)
63{
64  const tr_watchdir_t handle = context;
65
66  tr_watchdir_scan (handle, NULL);
67}
68
69static void
70tr_watchdir_inotify_on_event (struct bufferevent * event,
71                              void               * context)
72{
73  assert (context != NULL);
74
75  const tr_watchdir_t handle = context;
76  tr_watchdir_inotify * const backend = BACKEND_UPCAST (tr_watchdir_get_backend (handle));
77  struct inotify_event ev;
78  size_t nread;
79  size_t name_size = NAME_MAX + 1;
80  char * name = tr_new (char, name_size);
81
82  /* Read the size of the struct excluding name into buf. Guaranteed to have at
83     least sizeof (ev) available */
84  while ((nread = bufferevent_read (event, &ev, sizeof (ev))) != 0)
85    {
86      if (nread == (size_t) -1)
87        {
88          log_error ("Failed to read inotify event: %s", tr_strerror (errno));
89          break;
90        }
91
92      if (nread != sizeof (ev))
93        {
94          log_error ("Failed to read inotify event: expected %zu, got %zu bytes.",
95                     sizeof (ev), nread);
96          break;
97        }
98
99      assert (ev.wd == backend->inwd);
100      assert ((ev.mask & INOTIFY_WATCH_MASK) != 0);
101      assert (ev.len > 0);
102
103      if (ev.len > name_size)
104        {
105          name_size = ev.len;
106          name = tr_renew (char, name, name_size);
107        }
108
109      /* Consume entire name into buffer */
110      if ((nread = bufferevent_read (event, name, ev.len)) == (size_t) -1)
111        {
112          log_error ("Failed to read inotify name: %s", tr_strerror (errno));
113          break;
114        }
115
116      if (nread != ev.len)
117        {
118          log_error ("Failed to read inotify name: expected %" PRIu32 ", got %zu bytes.",
119                     ev.len, nread);
120          break;
121        }
122
123      tr_watchdir_process (handle, name);
124    }
125
126  tr_free (name);
127}
128
129static void
130tr_watchdir_inotify_free (tr_watchdir_backend * backend_base)
131{
132  tr_watchdir_inotify * const backend = BACKEND_UPCAST (backend_base);
133
134  if (backend == NULL)
135    return;
136
137  assert (backend->base.free_func == &tr_watchdir_inotify_free);
138
139  if (backend->event != NULL)
140    {
141       bufferevent_disable (backend->event, EV_READ);
142       bufferevent_free (backend->event);
143    }
144
145  if (backend->infd != -1)
146    {
147      if (backend->inwd != -1)
148        inotify_rm_watch (backend->infd, backend->inwd);
149      close (backend->infd);
150    }
151
152  tr_free (backend);
153}
154
155tr_watchdir_backend *
156tr_watchdir_inotify_new (tr_watchdir_t handle)
157{
158  const char * const path = tr_watchdir_get_path (handle);
159  tr_watchdir_inotify * backend;
160
161  backend = tr_new0 (tr_watchdir_inotify, 1);
162  backend->base.free_func = &tr_watchdir_inotify_free;
163  backend->infd = -1;
164  backend->inwd = -1;
165
166  if ((backend->infd = inotify_init ()) == -1)
167    {
168      log_error ("Unable to inotify_init: %s", tr_strerror (errno));
169      goto fail;
170    }
171
172  if ((backend->inwd = inotify_add_watch (backend->infd, path,
173                                          INOTIFY_WATCH_MASK | IN_ONLYDIR)) == -1)
174    {
175      log_error ("Failed to setup watchdir \"%s\": %s (%d)", path,
176                 tr_strerror (errno), errno);
177      goto fail;
178    }
179
180  if ((backend->event = bufferevent_socket_new (tr_watchdir_get_event_base (handle),
181                                                backend->infd, 0)) == NULL)
182    {
183      log_error ("Failed to create event buffer: %s", tr_strerror (errno));
184      goto fail;
185    }
186
187  /* Guarantees at least the sizeof an inotify event will be available in the
188     event buffer */
189  bufferevent_setwatermark (backend->event, EV_READ, sizeof (struct inotify_event), 0);
190  bufferevent_setcb (backend->event, &tr_watchdir_inotify_on_event, NULL, NULL, handle);
191  bufferevent_enable (backend->event, EV_READ);
192
193  /* Perform an initial scan on the directory */
194  if (event_base_once (tr_watchdir_get_event_base (handle), -1, EV_TIMEOUT,
195                       &tr_watchdir_inotify_on_first_scan, handle, NULL) == -1)
196    log_error ("Failed to perform initial scan: %s", tr_strerror (errno));
197
198  return BACKEND_DOWNCAST (backend);
199
200fail:
201  tr_watchdir_inotify_free (BACKEND_DOWNCAST (backend));
202  return NULL;
203}
Note: See TracBrowser for help on using the repository browser.