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