LCOV - code coverage report
Current view: top level - client - m_fanotify.c (source / functions) Hit Total Coverage
Test: coverage-client.info Lines: 149 168 88.7 %
Date: 2016-02-03 22:31:46 Functions: 11 12 91.7 %

          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             : 

Generated by: LCOV version 1.11