1 /*
2 * Tvheadend - File/Directory monitoring
3 *
4 * Copyright (C) 2014 Adam Sutton
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "tvheadend.h"
21 #include "fsmonitor.h"
22 #include "redblack.h"
23 #include "queue.h"
24
25 #if ENABLE_INOTIFY
26
27 #include <signal.h>
28 #include <sys/inotify.h>
29 #include <sys/stat.h>
30
31 /* Global list of monitorer paths (and inotify FD) */
32 RB_HEAD(,fsmonitor_path) fsmonitor_paths;
33 int fsmonitor_fd;
34
35 /* RB tree sorting of paths */
36 static int
fmp_cmp(fsmonitor_path_t * a,fsmonitor_path_t * b)37 fmp_cmp ( fsmonitor_path_t *a, fsmonitor_path_t *b )
38 {
39 return strcmp(a->fmp_path, b->fmp_path);
40 }
41
42 /*
43 * Inotify thread for handling events
44 */
45 static void *
fsmonitor_thread(void * p)46 fsmonitor_thread ( void* p )
47 {
48 int fd, c, i;
49 uint8_t buf[sizeof(struct inotify_event) * 10];
50 char path[1024];
51 struct inotify_event *ev;
52 fsmonitor_path_t *fmp;
53 fsmonitor_link_t *fml;
54 fsmonitor_t *fsm;
55
56 while (atomic_get(&tvheadend_running)) {
57
58 fd = atomic_get(&fsmonitor_fd);
59 if (fd < 0)
60 break;
61
62 /* Wait for event */
63 c = read(fd, buf, sizeof(buf));
64 if (c < 0)
65 break;
66
67 /* Process */
68 pthread_mutex_lock(&global_lock);
69 i = 0;
70 while ( i < c ) {
71 ev = (struct inotify_event*)&buf[i];
72 i += sizeof(struct inotify_event) + ev->len;
73 if (i > c)
74 break;
75 tvhtrace(LS_FSMONITOR, "event fd %d name %s mask %08X",
76 ev->wd, ev->len ? ev->name : NULL, ev->mask);
77
78 /* Find */
79 // TODO: make this more efficient (especially if number of
80 // watched paths gets big)
81 RB_FOREACH(fmp, &fsmonitor_paths, fmp_link)
82 if (fmp->fmp_fd == ev->wd)
83 break;
84 if (!fmp) continue;
85
86 /* Full path */
87 snprintf(path, sizeof(path), "%s/%s", fmp->fmp_path, ev->name);
88
89 /* Process listeners */
90 LIST_FOREACH(fml, &fmp->fmp_monitors, fml_plink) {
91 fsm = fml->fml_monitor;
92 if (ev->mask & IN_CREATE && fsm->fsm_create)
93 fsm->fsm_create(fsm, path);
94 else if (ev->mask & IN_DELETE && fsm->fsm_delete)
95 fsm->fsm_delete(fsm, path);
96 }
97 }
98 pthread_mutex_unlock(&global_lock);
99 }
100 return NULL;
101 }
102
103 /*
104 * Start the fsmonitor subsystem
105 */
106 pthread_t fsmonitor_tid;
107
108 void
fsmonitor_init(void)109 fsmonitor_init ( void )
110 {
111 /* Intialise inotify */
112 atomic_set(&fsmonitor_fd, inotify_init1(IN_CLOEXEC));
113 tvhthread_create(&fsmonitor_tid, NULL, fsmonitor_thread, NULL, "fsmonitor");
114 }
115
116 /*
117 * Stop the fsmonitor subsystem
118 */
119 void
fsmonitor_done(void)120 fsmonitor_done ( void )
121 {
122 int fd = atomic_exchange(&fsmonitor_fd, -1);
123 if (fd >= 0) close(fd);
124 pthread_kill(fsmonitor_tid, SIGTERM);
125 pthread_join(fsmonitor_tid, NULL);
126 }
127
128 /*
129 * Add a new path
130 */
131 int
fsmonitor_add(const char * path,fsmonitor_t * fsm)132 fsmonitor_add ( const char *path, fsmonitor_t *fsm )
133 {
134 int fd, mask;
135 fsmonitor_path_t *skel;
136 fsmonitor_path_t *fmp;
137 fsmonitor_link_t *fml;
138
139 lock_assert(&global_lock);
140
141 skel = calloc(1, sizeof(fsmonitor_path_t));
142 skel->fmp_path = (char*)path;
143
144 /* Build mask */
145 mask = IN_CREATE | IN_DELETE;
146
147 /* Find */
148 fmp = RB_INSERT_SORTED(&fsmonitor_paths, skel, fmp_link, fmp_cmp);
149 if (!fmp) {
150 fmp = skel;
151 fd = atomic_get(&fsmonitor_fd);
152 if (fd >= 0)
153 fmp->fmp_fd = inotify_add_watch(fd, path, mask);
154 else
155 fmp->fmp_fd = -1;
156
157 /* Failed */
158 if (fmp->fmp_fd <= 0) {
159 RB_REMOVE(&fsmonitor_paths, fmp, fmp_link);
160 free(fmp);
161 tvhdebug(LS_FSMONITOR, "failed to add %s (exists?)", path);
162 printf("ERROR: failed to add %s\n", path);
163 return -1;
164 }
165
166 /* Setup */
167 fmp->fmp_path = strdup(path);
168 tvhdebug(LS_FSMONITOR, "watch %s", fmp->fmp_path);
169 } else {
170 free(skel);
171 }
172
173 /* Check doesn't exist */
174 // TODO: could make this more efficient
175 LIST_FOREACH(fml, &fmp->fmp_monitors, fml_plink)
176 if (fml->fml_monitor == fsm)
177 return 0;
178
179 /* Add */
180 fml = calloc(1, sizeof(fsmonitor_link_t));
181 fml->fml_path = fmp;
182 fml->fml_monitor = fsm;
183 LIST_INSERT_HEAD(&fmp->fmp_monitors, fml, fml_plink);
184 LIST_INSERT_HEAD(&fsm->fsm_paths, fml, fml_mlink);
185 return 0;
186 }
187
188 /*
189 * Remove an existing path
190 */
191 void
fsmonitor_del(const char * path,fsmonitor_t * fsm)192 fsmonitor_del ( const char *path, fsmonitor_t *fsm )
193 {
194 static fsmonitor_path_t skel;
195 fsmonitor_path_t *fmp;
196 fsmonitor_link_t *fml;
197 int fd;
198
199 lock_assert(&global_lock);
200
201 skel.fmp_path = (char*)path;
202
203 /* Find path */
204 fmp = RB_FIND(&fsmonitor_paths, &skel, fmp_link, fmp_cmp);
205 if (fmp) {
206
207 /* Find link */
208 LIST_FOREACH(fml, &fmp->fmp_monitors, fml_plink)
209 if (fml->fml_monitor == fsm)
210 break;
211
212 /* Remove link */
213 if (fml) {
214 LIST_REMOVE(fml, fml_plink);
215 LIST_REMOVE(fml, fml_mlink);
216 free(fml);
217 }
218
219 /* Remove path */
220 if (LIST_EMPTY(&fmp->fmp_monitors)) {
221 tvhdebug(LS_FSMONITOR, "unwatch %s", fmp->fmp_path);
222 RB_REMOVE(&fsmonitor_paths, fmp, fmp_link);
223 fd = atomic_get(&fsmonitor_fd);
224 if (fd >= 0)
225 inotify_rm_watch(fd, fmp->fmp_fd);
226 free(fmp->fmp_path);
227 free(fmp);
228 }
229 }
230 }
231
232 #else /* ENABLE_INOTIFY */
233
234 void
fsmonitor_init(void)235 fsmonitor_init ( void )
236 {
237 }
238
239 void
fsmonitor_done(void)240 fsmonitor_done ( void )
241 {
242 }
243
244 int
fsmonitor_add(const char * path,fsmonitor_t * fsm)245 fsmonitor_add ( const char *path, fsmonitor_t *fsm )
246 {
247 return 0; // TODO: is this the right value?
248 }
249
250 void
fsmonitor_del(const char * path,fsmonitor_t * fsm)251 fsmonitor_del ( const char *path, fsmonitor_t *fsm )
252 {
253 }
254
255 #endif
256