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