Line data Source code
1 : /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 : /*
3 : * m_fanotify.c
4 : * This file is part of "Sauvegarde" project.
5 : *
6 : * (C) Copyright 2015 - 2016 Olivier Delhomme
7 : * e-mail : olivier.delhomme@free.fr
8 : *
9 : * "Sauvegarde" is free software: you can redistribute it and/or modify
10 : * it under the terms of the GNU General Public License as published by
11 : * the Free Software Foundation, either version 3 of the License, or
12 : * (at your option) any later version.
13 : *
14 : * "Sauvegarde" is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with "Sauvegarde". If not, see <http://www.gnu.org/licenses/>
21 : */
22 :
23 : /**
24 : * @file m_fanotify.c
25 : *
26 : * This file does fanotify's monitor interface. This file is heavily based
27 : * on Aleksander Morgado fanotify-example.c's file (ie mainly copied !)
28 : * @todo : do something with the ugly code of this file (testing things for
29 : * now - but should not be like that after tests).
30 : */
31 :
32 : #include "client.h"
33 :
34 : static gchar *get_file_path_from_fd(gint fd);
35 : static char *get_program_name_from_pid(int pid);
36 : static void prepare_before_saving(main_struct_t *main_struct, gchar *path);
37 : static GSList *does_event_concerns_monitored_directory(gchar *path, GSList *dir_list);
38 : static gboolean filter_out_if_necessary(GSList *head, struct fanotify_event_metadata *event);
39 : static void event_process(main_struct_t *main_struct, struct fanotify_event_metadata *event, GSList *dir_list);
40 :
41 :
42 : /**
43 : * Stops signal handling
44 : */
45 0 : void stop_signals(int signal_fd)
46 : {
47 0 : close(signal_fd);
48 0 : }
49 :
50 :
51 : /**
52 : * Starts signal handling
53 : */
54 1 : gint start_signals(void)
55 : {
56 1 : gint signal_fd = -1;
57 : sigset_t sigmask;
58 :
59 : /* We want to handle SIGINT and SIGTERM in the signal_fd, so we block them. */
60 1 : sigemptyset(&sigmask);
61 1 : sigaddset(&sigmask, SIGINT);
62 1 : sigaddset(&sigmask, SIGTERM);
63 :
64 1 : if (sigprocmask(SIG_BLOCK, &sigmask, NULL) < 0)
65 : {
66 0 : print_error(__FILE__, __LINE__, _("Couldn't block signals: %s\n"), strerror(errno));
67 : }
68 :
69 : /* Get new FD to read signals from it */
70 1 : if ((signal_fd = signalfd(-1, &sigmask, 0)) < 0)
71 : {
72 0 : print_error(__FILE__, __LINE__, _("Couldn't setup signal FD: %s\n"), strerror(errno));
73 : }
74 :
75 1 : return signal_fd;
76 : }
77 :
78 :
79 : /**
80 : * Inits and starts fanotify notifications
81 : * @param opt : a filled options_t * structure that contains all options
82 : * by default, read into the file or selected in the command line.
83 : */
84 1 : gint start_fanotify(options_t *opt)
85 : {
86 1 : gint fanotify_fd = -1;
87 1 : GSList *head = NULL;
88 :
89 : /** Leaving only FAN_CLOSE_WRITE for some tests */
90 : /* Setup fanotify notifications (FAN) mask. All these defined in linux/fanotify.h. */
91 : static uint64_t event_mask =
92 : (FAN_CLOSE_WRITE | /* Writtable file closed */
93 : FAN_ONDIR | /* We want to be reported of events in the directory */
94 : FAN_EVENT_ON_CHILD); /* We want to be reported of events in files of the directory */
95 :
96 1 : unsigned int mark_flags = FAN_MARK_ADD | FAN_MARK_MOUNT;
97 :
98 1 : if (opt != NULL)
99 : {
100 : /* Create new fanotify device */
101 1 : if ((fanotify_fd = fanotify_init(FAN_CLOEXEC, O_RDONLY | O_CLOEXEC | O_LARGEFILE)) < 0)
102 : {
103 0 : print_error(__FILE__, __LINE__, _("Couldn't setup new fanotify device: %s\n"), strerror(errno));
104 : }
105 : else
106 : {
107 1 : head = opt->dirname_list;
108 :
109 6 : while (head != NULL)
110 : {
111 4 : if (fanotify_mark(fanotify_fd, mark_flags, event_mask, AT_FDCWD, head->data) < 0)
112 : {
113 0 : print_error(__FILE__, __LINE__, _("Couldn't add monitor in directory %s: %s\n"), head->data , strerror(errno));
114 : }
115 : else
116 : {
117 4 : print_debug(_("Started monitoring directory %s\n"), head->data);
118 : }
119 :
120 4 : head = g_slist_next(head);
121 : }
122 : }
123 : }
124 :
125 1 : return fanotify_fd;
126 : }
127 :
128 :
129 : /**
130 : * gets path from file descriptor
131 : * @param fd is the file descriptor as seen in /proc filesystem
132 : * @returns the name of the file pointed to by this file descriptor
133 : */
134 331281 : static gchar *get_file_path_from_fd(gint fd)
135 : {
136 331281 : gchar *path = NULL;
137 331281 : gchar *proc = NULL;
138 331281 : ssize_t len = 0;
139 :
140 331281 : if (fd <= 0)
141 : {
142 0 : print_error(__FILE__, __LINE__, _("Invalid file descriptor: %d\n"), fd);
143 0 : return NULL;
144 : }
145 :
146 331281 : proc = g_strdup_printf("/proc/self/fd/%d", fd);
147 :
148 331281 : path = (gchar *) g_malloc((PATH_MAX) * sizeof(gchar));
149 :
150 331281 : if ((len = readlink(proc, path, PATH_MAX - 1)) < 0)
151 : {
152 0 : print_error(__FILE__, __LINE__, _("'readlink' error: %s\n"), strerror(errno));
153 0 : free_variable(proc);
154 0 : free_variable(path);
155 0 : return NULL;
156 : }
157 :
158 331281 : free_variable(proc);
159 331281 : path[len] = '\0';
160 :
161 331281 : return path;
162 : }
163 :
164 :
165 15 : static char *get_program_name_from_pid(int pid)
166 : {
167 15 : int fd = 0;
168 15 : ssize_t len = 0;
169 15 : char *aux = NULL;
170 15 : gchar *cmd = NULL;
171 15 : gchar *buffer = NULL;
172 :
173 : /* Try to get program name by PID */
174 15 : cmd = g_strdup_printf("/proc/%d/cmdline", pid);
175 :
176 15 : if ((fd = open(cmd, O_RDONLY)) < 0)
177 : {
178 : return NULL;
179 : }
180 :
181 15 : buffer = (gchar *) g_malloc(PATH_MAX);
182 :
183 : /* Read file contents into buffer */
184 15 : if ((len = read(fd, buffer, PATH_MAX - 1)) <= 0)
185 : {
186 0 : close (fd);
187 0 : free_variable(buffer);
188 0 : return NULL;
189 : }
190 : else
191 : {
192 15 : close (fd);
193 :
194 15 : buffer[len] = '\0';
195 15 : aux = strstr(buffer, "^@");
196 :
197 15 : if (aux)
198 : {
199 0 : *aux = '\0';
200 : }
201 :
202 15 : return buffer;
203 : }
204 : }
205 :
206 :
207 : /**
208 : * Prepares everything in order to call save_one_file function that does
209 : * everything to save one file !
210 : * @param main_struct : main structure of the program
211 : * @param path is the entire path and name of the considered file.
212 : */
213 15 : static void prepare_before_saving(main_struct_t *main_struct, gchar *path)
214 : {
215 15 : gchar *directory = NULL;
216 15 : GFileInfo *fileinfo = NULL;
217 15 : GFile *file = NULL;
218 15 : GError *error = NULL;
219 15 : file_event_t *file_event = NULL;
220 :
221 15 : if (main_struct != NULL && path != NULL)
222 : {
223 15 : directory = g_path_get_dirname(path);
224 15 : file = g_file_new_for_path(path);
225 15 : fileinfo = g_file_query_info(file, "*", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error);
226 :
227 15 : if (error == NULL && fileinfo != NULL)
228 : {
229 : /* file_event is used and freed in the thread
230 : * save_one_file_threaded where the queue save_queue
231 : * is used
232 : */
233 8 : file_event = new_file_event_t(directory, fileinfo);
234 8 : g_async_queue_push(main_struct->save_queue, file_event);
235 :
236 8 : fileinfo = free_object(fileinfo);
237 8 : file = free_object(file);
238 8 : free_variable(directory);
239 : }
240 : else
241 : {
242 7 : print_error(__FILE__, __LINE__, _("Unable to get meta data for file %s: %s\n"), path, error->message);
243 7 : error = free_error(error);
244 : }
245 : }
246 15 : }
247 :
248 :
249 : /**
250 : * Returns the pointer to the concerned directory if found NULL otherwise
251 : * @param path path where the event occured
252 : * @param dir_list monitored directory list
253 : * @returns a pointer to the monitored directory where the event occured
254 : */
255 331281 : static GSList *does_event_concerns_monitored_directory(gchar *path, GSList *dir_list)
256 : {
257 331281 : GSList *head = dir_list;
258 331281 : gchar *pathutf8 = NULL; /* path where the received event occured */
259 331281 : gchar *dirutf8 = NULL;
260 331281 : gboolean found = FALSE;
261 :
262 331281 : pathutf8 = g_utf8_casefold(path, -1);
263 :
264 1987656 : while (head != NULL && found == FALSE)
265 : {
266 1325094 : dirutf8 = head->data;
267 :
268 1325094 : if (g_str_has_prefix(pathutf8, dirutf8) == TRUE)
269 : {
270 : found = TRUE;
271 : }
272 : else
273 : {
274 1325079 : head = g_slist_next(head);
275 : }
276 : }
277 :
278 331281 : pathutf8 = free_variable(pathutf8);
279 :
280 331281 : if (found == TRUE)
281 : {
282 15 : return head;
283 : }
284 : else
285 : {
286 : return NULL;
287 : }
288 : }
289 :
290 :
291 : /**
292 : * Filters out and returns TRUE if the event concerns a file that has to
293 : * be saved FALSE otherwise
294 : * @param head is the matching monitired directory
295 : * @param event is the fanotify's structure event
296 : */
297 331281 : static gboolean filter_out_if_necessary(GSList *head, struct fanotify_event_metadata *event)
298 : {
299 331281 : gchar *progname = NULL;
300 :
301 :
302 331281 : if (head != NULL)
303 : {
304 15 : progname = get_program_name_from_pid(event->pid);
305 :
306 15 : if (g_strcmp0(PROGRAM_NAME, progname) != 0)
307 : {
308 : /* Save files that does not come from our activity */
309 15 : free_variable(progname);
310 :
311 : return TRUE;
312 : }
313 : }
314 :
315 : return FALSE;
316 : }
317 :
318 :
319 : /**
320 : * Processes events
321 : * @param main_struct is the maion structure
322 : * @param event is the fanotify's structure event
323 : * @param dir_list MUST be a list of gchar * g_utf8_casefold()
324 : * transformed.
325 : */
326 331281 : static void event_process(main_struct_t *main_struct, struct fanotify_event_metadata *event, GSList *dir_list)
327 : {
328 331281 : gchar *path = NULL;
329 331281 : gboolean to_save = FALSE;
330 331281 : GSList *head = NULL;
331 :
332 331281 : path = get_file_path_from_fd(event->fd);
333 :
334 331281 : if (path != NULL)
335 : {
336 : /* Does the event concern a monitored directory ? */
337 331281 : head = does_event_concerns_monitored_directory(path, dir_list);
338 :
339 : /* Do we need to save this file ? Is it excluded somehow ? */
340 331281 : to_save = filter_out_if_necessary(head, event);
341 :
342 331281 : if (to_save == TRUE)
343 : {
344 15 : print_debug(_("Received event file/directory: %s\n"), path);
345 15 : print_debug(_(" matching directory is: %s\n"), head->data);
346 :
347 : /* we are only watching this event so it is not necessary to print it !
348 : * if (event->mask & FAN_CLOSE_WRITE)
349 : * {
350 : * print_debug(_("\tFAN_CLOSE_WRITE\n"));
351 : * }
352 : */
353 :
354 : /* Saving the file effectively */
355 15 : prepare_before_saving(main_struct, path);
356 :
357 15 : fflush(stdout);
358 : }
359 :
360 331281 : close(event->fd);
361 331281 : free_variable(path);
362 : }
363 331281 : }
364 :
365 :
366 : /**
367 : * Stops fanotify notifications
368 : * @param opt is the options of the program
369 : * @param fanotify_fd is the file descriptor of the file which is
370 : * concerned by the event.
371 : */
372 1 : void stop_fanotify(options_t *opt, int fanotify_fd)
373 : {
374 1 : GSList *head = NULL;
375 : /* Setup fanotify notifications (FAN) mask. All these defined in linux/fanotify.h. */
376 : static uint64_t event_mask =
377 : (FAN_CLOSE_WRITE | /* Writtable file closed */
378 : FAN_ONDIR | /* We want to be reported of events in the directory */
379 : FAN_EVENT_ON_CHILD); /* We want to be reported of events in files of the directory */
380 :
381 1 : if (opt != NULL)
382 : {
383 1 : head = opt->dirname_list;
384 :
385 6 : while (head != NULL)
386 : {
387 4 : fanotify_mark(fanotify_fd, FAN_MARK_REMOVE, event_mask, AT_FDCWD, head->data);
388 4 : head = g_slist_next(head);
389 : }
390 :
391 : }
392 :
393 1 : close(fanotify_fd);
394 1 : }
395 :
396 :
397 : /**
398 : * Transforms a list of directory into a list of directory ready for
399 : * comparison
400 : * @param dir_list is the list of directories to be transformed. This
401 : * list is leaved untouched.
402 : * @returns a newly allocated list that may be freed when no longer
403 : * needed.
404 : */
405 1 : static GSList *transform_to_utf8_casefold(GSList *dir_list)
406 : {
407 1 : GSList *head = NULL;
408 1 : GSList *utf8 = NULL;
409 1 : gchar *charutf8 = NULL;
410 :
411 1 : head = dir_list;
412 :
413 6 : while (head != NULL)
414 : {
415 4 : charutf8 = g_utf8_casefold(head->data, -1);
416 :
417 : /* Order of utf8 list is not very important */
418 4 : utf8 = g_slist_prepend(utf8, charutf8);
419 :
420 4 : head = g_slist_next(head);
421 : }
422 :
423 1 : return utf8;
424 : }
425 :
426 :
427 : /**
428 : * fanotify main loop
429 : * @todo simplify code (CCN is 12 already !)
430 : */
431 1 : void fanotify_loop(main_struct_t *main_struct)
432 : {
433 : struct pollfd fds[FD_POLL_MAX];
434 : struct signalfd_siginfo fdsi;
435 : char buffer[FANOTIFY_BUFFER_SIZE];
436 1 : ssize_t length = 0;
437 1 : struct fanotify_event_metadata *fe_mdata = NULL;
438 1 : GSList *dir_list_utf8 = NULL;
439 :
440 :
441 1 : gint signal_fd = 0;
442 1 : gint fanotify_fd = 0;
443 :
444 1 : if (main_struct != NULL)
445 : {
446 1 : signal_fd = main_struct->signal_fd;
447 1 : fanotify_fd = main_struct->fanotify_fd;
448 :
449 :
450 : /* Setup polling */
451 1 : fds[FD_POLL_SIGNAL].fd = signal_fd;
452 1 : fds[FD_POLL_SIGNAL].events = POLLIN;
453 1 : fds[FD_POLL_FANOTIFY].fd = fanotify_fd;
454 1 : fds[FD_POLL_FANOTIFY].events = POLLIN;
455 :
456 1 : dir_list_utf8 = transform_to_utf8_casefold(main_struct->opt->dirname_list);
457 :
458 : while (1)
459 : {
460 : /* Block until there is something to be read */
461 288453 : if (poll(fds, FD_POLL_MAX, -1) < 0)
462 : {
463 32 : print_error(__FILE__, __LINE__, _("Couldn't poll(): '%s'\n"), strerror(errno));
464 : }
465 :
466 : /* Signal received ? */
467 288453 : if (fds[FD_POLL_SIGNAL].revents & POLLIN)
468 : {
469 1 : if (read(fds[FD_POLL_SIGNAL].fd, &fdsi, sizeof(fdsi)) != sizeof(fdsi))
470 : {
471 0 : print_error(__FILE__, __LINE__, _("Couldn't read signal, wrong size read\n"));
472 : }
473 :
474 : /* Break loop if we got SIGINT or SIGTERM */
475 1 : if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM)
476 : {
477 1 : stop_fanotify(main_struct->opt, main_struct->fanotify_fd);
478 1 : free_list(dir_list_utf8);
479 1 : break;
480 : }
481 :
482 0 : print_error(__FILE__, __LINE__, _("Received unexpected signal\n"));
483 : }
484 :
485 :
486 : /* fanotify event received ? */
487 288452 : if (fds[FD_POLL_FANOTIFY].revents & POLLIN)
488 : {
489 : /* Read from the FD. It will read all events available up to
490 : * the given buffer size. */
491 288420 : if ((length = read(fds[FD_POLL_FANOTIFY].fd, buffer, FANOTIFY_BUFFER_SIZE)) > 0)
492 : {
493 : fe_mdata = (struct fanotify_event_metadata *) buffer;
494 :
495 619701 : while (FAN_EVENT_OK(fe_mdata, length))
496 : {
497 331281 : event_process(main_struct, fe_mdata, dir_list_utf8);
498 :
499 331281 : if (fe_mdata->fd > 0)
500 : {
501 331281 : close(fe_mdata->fd);
502 : }
503 :
504 331281 : fe_mdata = FAN_EVENT_NEXT(fe_mdata, length);
505 : }
506 : }
507 : }
508 : }
509 :
510 : }
511 :
512 1 : }
513 :
514 :
515 :
|