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