LCOV - code coverage report
Current view: top level - glib/gio - glocalfilemonitor.c (source / functions) Hit Total Coverage
Test: unnamed Lines: 311 366 85.0 %
Date: 2024-04-23 05:16:05 Functions: 37 41 90.2 %
Branches: 91 136 66.9 %

           Branch data     Line data    Source code
       1                 :            : /* GIO - GLib Input, Output and Streaming Library
       2                 :            :  *
       3                 :            :  * Copyright (C) 2006-2007 Red Hat, Inc.
       4                 :            :  *
       5                 :            :  * SPDX-License-Identifier: LGPL-2.1-or-later
       6                 :            :  *
       7                 :            :  * This library is free software; you can redistribute it and/or
       8                 :            :  * modify it under the terms of the GNU Lesser General Public
       9                 :            :  * License as published by the Free Software Foundation; either
      10                 :            :  * version 2.1 of the License, or (at your option) any later version.
      11                 :            :  *
      12                 :            :  * This library is distributed in the hope that it will be useful,
      13                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      14                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15                 :            :  * Lesser General Public License for more details.
      16                 :            :  *
      17                 :            :  * You should have received a copy of the GNU Lesser General
      18                 :            :  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
      19                 :            :  *
      20                 :            :  * Author: Alexander Larsson <alexl@redhat.com>
      21                 :            :  */
      22                 :            : 
      23                 :            : #include "config.h"
      24                 :            : 
      25                 :            : #include "gioenumtypes.h"
      26                 :            : #include "glocalfilemonitor.h"
      27                 :            : #include "giomodule-priv.h"
      28                 :            : #include "gioerror.h"
      29                 :            : #include "glibintl.h"
      30                 :            : #include "glocalfile.h"
      31                 :            : #include "glib-private.h"
      32                 :            : 
      33                 :            : #include <string.h>
      34                 :            : 
      35                 :            : #define DEFAULT_RATE_LIMIT                           800 * G_TIME_SPAN_MILLISECOND
      36                 :            : #define VIRTUAL_CHANGES_DONE_DELAY                     2 * G_TIME_SPAN_SECOND
      37                 :            : 
      38                 :            : /* GFileMonitorSource is a GSource responsible for emitting the changed
      39                 :            :  * signals in the owner-context of the GFileMonitor.
      40                 :            :  *
      41                 :            :  * It contains functionality for cross-thread queuing of events.  It
      42                 :            :  * also handles merging of CHANGED events and emission of CHANGES_DONE
      43                 :            :  * events.
      44                 :            :  *
      45                 :            :  * We use the "priv" pointer in the external struct to store it.
      46                 :            :  */
      47                 :            : struct _GFileMonitorSource {
      48                 :            :   GSource       source;
      49                 :            : 
      50                 :            :   GMutex        lock;
      51                 :            :   GWeakRef      instance_ref;
      52                 :            :   GFileMonitorFlags flags;
      53                 :            :   gchar        *dirname;
      54                 :            :   gchar        *basename;
      55                 :            :   gchar        *filename;
      56                 :            :   GSequence    *pending_changes; /* sorted by ready time */
      57                 :            :   GHashTable   *pending_changes_table;
      58                 :            :   GQueue        event_queue;
      59                 :            :   gint64        rate_limit;
      60                 :            : };
      61                 :            : 
      62                 :            : /* PendingChange is a struct to keep track of a file that needs to have
      63                 :            :  * (at least) a CHANGES_DONE_HINT event sent for it in the near future.
      64                 :            :  *
      65                 :            :  * If 'dirty' is TRUE then a CHANGED event also needs to be sent.
      66                 :            :  *
      67                 :            :  * last_emission is the last time a CHANGED event was emitted.  It is
      68                 :            :  * used to calculate the time to send the next event.
      69                 :            :  */
      70                 :            : typedef struct {
      71                 :            :   gchar    *child;
      72                 :            :   guint64   last_emission : 63;
      73                 :            :   guint64   dirty         :  1;
      74                 :            : } PendingChange;
      75                 :            : 
      76                 :            : /* QueuedEvent is a signal that will be sent immediately, as soon as the
      77                 :            :  * source gets a chance to dispatch.  The existence of any queued event
      78                 :            :  * implies that the source is ready now.
      79                 :            :  */
      80                 :            : typedef struct
      81                 :            : {
      82                 :            :   GFileMonitorEvent event_type;
      83                 :            :   GFile *child;
      84                 :            :   GFile *other;
      85                 :            : } QueuedEvent;
      86                 :            : 
      87                 :            : static gint64
      88                 :        125 : pending_change_get_ready_time (const PendingChange *change,
      89                 :            :                                GFileMonitorSource  *fms)
      90                 :            : {
      91         [ +  + ]:        125 :   if (change->dirty)
      92                 :         77 :     return change->last_emission + fms->rate_limit;
      93                 :            :   else
      94                 :         48 :     return change->last_emission + VIRTUAL_CHANGES_DONE_DELAY;
      95                 :            : }
      96                 :            : 
      97                 :            : static int
      98                 :          0 : pending_change_compare_ready_time (gconstpointer a_p,
      99                 :            :                                    gconstpointer b_p,
     100                 :            :                                    gpointer      user_data)
     101                 :            : {
     102                 :          0 :   GFileMonitorSource *fms = user_data;
     103                 :          0 :   const PendingChange *a = a_p;
     104                 :          0 :   const PendingChange *b = b_p;
     105                 :            :   gint64 ready_time_a;
     106                 :            :   gint64 ready_time_b;
     107                 :            : 
     108                 :          0 :   ready_time_a = pending_change_get_ready_time (a, fms);
     109                 :          0 :   ready_time_b = pending_change_get_ready_time (b, fms);
     110                 :            : 
     111         [ #  # ]:          0 :   if (ready_time_a < ready_time_b)
     112                 :          0 :     return -1;
     113                 :            :   else
     114                 :          0 :     return ready_time_a > ready_time_b;
     115                 :            : }
     116                 :            : 
     117                 :            : static void
     118                 :         86 : pending_change_free (gpointer data)
     119                 :            : {
     120                 :         86 :   PendingChange *change = data;
     121                 :            : 
     122                 :         86 :   g_free (change->child);
     123                 :            : 
     124                 :         86 :   g_slice_free (PendingChange, change);
     125                 :         86 : }
     126                 :            : 
     127                 :            : static void
     128                 :        914 : queued_event_free (QueuedEvent *event)
     129                 :            : {
     130                 :        914 :   g_object_unref (event->child);
     131         [ +  + ]:        914 :   if (event->other)
     132                 :          4 :     g_object_unref (event->other);
     133                 :            : 
     134                 :        914 :   g_slice_free (QueuedEvent, event);
     135                 :        914 : }
     136                 :            : 
     137                 :            : static gint64
     138                 :       1948 : g_file_monitor_source_get_ready_time (GFileMonitorSource *fms)
     139                 :            : {
     140                 :            :   GSequenceIter *iter;
     141                 :            : 
     142         [ +  + ]:       1948 :   if (fms->event_queue.length)
     143                 :        947 :     return 0;
     144                 :            : 
     145                 :       1001 :   iter = g_sequence_get_begin_iter (fms->pending_changes);
     146         [ +  + ]:       1001 :   if (g_sequence_iter_is_end (iter))
     147                 :        926 :     return -1;
     148                 :            : 
     149                 :         75 :   return pending_change_get_ready_time (g_sequence_get (iter), fms);
     150                 :            : }
     151                 :            : 
     152                 :            : static void
     153                 :       1948 : g_file_monitor_source_update_ready_time (GFileMonitorSource *fms)
     154                 :            : {
     155                 :       1948 :   g_source_set_ready_time ((GSource *) fms, g_file_monitor_source_get_ready_time (fms));
     156                 :       1948 : }
     157                 :            : 
     158                 :            : static GSequenceIter *
     159                 :        947 : g_file_monitor_source_find_pending_change (GFileMonitorSource *fms,
     160                 :            :                                            const gchar        *child)
     161                 :            : {
     162                 :        947 :   return g_hash_table_lookup (fms->pending_changes_table, child);
     163                 :            : }
     164                 :            : 
     165                 :            : static void
     166                 :         86 : g_file_monitor_source_add_pending_change (GFileMonitorSource *fms,
     167                 :            :                                           const gchar        *child,
     168                 :            :                                           gint64              now)
     169                 :            : {
     170                 :            :   PendingChange *change;
     171                 :            :   GSequenceIter *iter;
     172                 :            : 
     173                 :         86 :   change = g_slice_new (PendingChange);
     174                 :         86 :   change->child = g_strdup (child);
     175                 :         86 :   change->last_emission = now;
     176                 :         86 :   change->dirty = FALSE;
     177                 :            : 
     178                 :         86 :   iter = g_sequence_insert_sorted (fms->pending_changes, change, pending_change_compare_ready_time, fms);
     179                 :         86 :   g_hash_table_insert (fms->pending_changes_table, change->child, iter);
     180                 :         86 : }
     181                 :            : 
     182                 :            : static gboolean
     183                 :         52 : g_file_monitor_source_set_pending_change_dirty (GFileMonitorSource *fms,
     184                 :            :                                                 GSequenceIter      *iter)
     185                 :            : {
     186                 :            :   PendingChange *change;
     187                 :            : 
     188                 :         52 :   change = g_sequence_get (iter);
     189                 :            : 
     190                 :            :   /* if it was already dirty then this change is 'uninteresting' */
     191         [ +  + ]:         52 :   if (change->dirty)
     192                 :          4 :     return FALSE;
     193                 :            : 
     194                 :         48 :   change->dirty = TRUE;
     195                 :            : 
     196                 :         48 :   g_sequence_sort_changed (iter, pending_change_compare_ready_time, fms);
     197                 :            : 
     198                 :         48 :   return TRUE;
     199                 :            : }
     200                 :            : 
     201                 :            : static gboolean
     202                 :         49 : g_file_monitor_source_get_pending_change_dirty (GFileMonitorSource *fms,
     203                 :            :                                                 GSequenceIter      *iter)
     204                 :            : {
     205                 :            :   PendingChange *change;
     206                 :            : 
     207                 :         49 :   change = g_sequence_get (iter);
     208                 :            : 
     209                 :         49 :   return change->dirty;
     210                 :            : }
     211                 :            : 
     212                 :            : static void
     213                 :         49 : g_file_monitor_source_remove_pending_change (GFileMonitorSource *fms,
     214                 :            :                                              GSequenceIter      *iter,
     215                 :            :                                              const gchar        *child)
     216                 :            : {
     217                 :            :   /* must remove the hash entry first -- its key is owned by the data
     218                 :            :    * which will be freed when removing the sequence iter
     219                 :            :    */
     220                 :         49 :   g_hash_table_remove (fms->pending_changes_table, child);
     221                 :         49 :   g_sequence_remove (iter);
     222                 :         49 : }
     223                 :            : 
     224                 :            : static void
     225                 :        914 : g_file_monitor_source_queue_event (GFileMonitorSource *fms,
     226                 :            :                                    GFileMonitorEvent   event_type,
     227                 :            :                                    const gchar        *child,
     228                 :            :                                    GFile              *other)
     229                 :            : {
     230                 :            :   QueuedEvent *event;
     231                 :            : 
     232                 :        914 :   event = g_slice_new (QueuedEvent);
     233                 :        914 :   event->event_type = event_type;
     234   [ +  +  +  + ]:        914 :   if (child != NULL && fms->dirname != NULL)
     235                 :        701 :     event->child = g_local_file_new_from_dirname_and_basename (fms->dirname, child);
     236         [ +  + ]:        213 :   else if (child != NULL)
     237                 :            :     {
     238                 :          2 :       gchar *dirname = g_path_get_dirname (fms->filename);
     239                 :          2 :       event->child = g_local_file_new_from_dirname_and_basename (dirname, child);
     240                 :          2 :       g_free (dirname);
     241                 :            :     }
     242         [ +  + ]:        211 :   else if (fms->dirname)
     243                 :        209 :     event->child = _g_local_file_new (fms->dirname);
     244         [ +  - ]:          2 :   else if (fms->filename)
     245                 :          2 :     event->child = _g_local_file_new (fms->filename);
     246                 :        914 :   event->other = other;
     247         [ +  + ]:        914 :   if (other)
     248                 :          4 :     g_object_ref (other);
     249                 :            : 
     250                 :        914 :   g_queue_push_tail (&fms->event_queue, event);
     251                 :        914 : }
     252                 :            : 
     253                 :            : static gboolean
     254                 :         62 : g_file_monitor_source_file_changed (GFileMonitorSource *fms,
     255                 :            :                                     const gchar        *child,
     256                 :            :                                     gint64              now)
     257                 :            : {
     258                 :            :   GSequenceIter *pending;
     259                 :            :   gboolean interesting;
     260                 :            : 
     261                 :         62 :   pending = g_file_monitor_source_find_pending_change (fms, child);
     262                 :            : 
     263                 :            :   /* If there is no pending change, emit one and create a record,
     264                 :            :    * else: just mark the existing record as dirty.
     265                 :            :    */
     266         [ +  + ]:         62 :   if (!pending)
     267                 :            :     {
     268                 :         10 :       g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, child, NULL);
     269                 :         10 :       g_file_monitor_source_add_pending_change (fms, child, now);
     270                 :         10 :       interesting = TRUE;
     271                 :            :     }
     272                 :            :   else
     273                 :         52 :     interesting = g_file_monitor_source_set_pending_change_dirty (fms, pending);
     274                 :            : 
     275                 :         62 :   g_file_monitor_source_update_ready_time (fms);
     276                 :            : 
     277                 :         62 :   return interesting;
     278                 :            : }
     279                 :            : 
     280                 :            : static void
     281                 :        885 : g_file_monitor_source_file_changes_done (GFileMonitorSource *fms,
     282                 :            :                                          const gchar        *child)
     283                 :            : {
     284                 :            :   GSequenceIter *pending;
     285                 :            : 
     286                 :        885 :   pending = g_file_monitor_source_find_pending_change (fms, child);
     287         [ +  + ]:        885 :   if (pending)
     288                 :            :     {
     289                 :            :       /* If it is dirty, make sure we push out the last CHANGED event */
     290         [ +  + ]:         49 :       if (g_file_monitor_source_get_pending_change_dirty (fms, pending))
     291                 :         22 :         g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, child, NULL);
     292                 :            : 
     293                 :         49 :       g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, child, NULL);
     294                 :         49 :       g_file_monitor_source_remove_pending_change (fms, pending, child);
     295                 :            :     }
     296                 :        885 : }
     297                 :            : 
     298                 :            : static void
     299                 :         76 : g_file_monitor_source_file_created (GFileMonitorSource *fms,
     300                 :            :                                     const gchar        *child,
     301                 :            :                                     gint64              event_time)
     302                 :            : {
     303                 :            :   /* Unlikely, but if we have pending changes for this filename, make
     304                 :            :    * sure we flush those out first, before creating the new ones.
     305                 :            :    */
     306                 :         76 :   g_file_monitor_source_file_changes_done (fms, child);
     307                 :            : 
     308                 :            :   /* Emit CREATE and add a pending changes record */
     309                 :         76 :   g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CREATED, child, NULL);
     310                 :         76 :   g_file_monitor_source_add_pending_change (fms, child, event_time);
     311                 :         76 : }
     312                 :            : 
     313                 :            : static void
     314                 :        694 : g_file_monitor_source_send_event (GFileMonitorSource *fms,
     315                 :            :                                   GFileMonitorEvent   event_type,
     316                 :            :                                   const gchar        *child,
     317                 :            :                                   GFile              *other)
     318                 :            : {
     319                 :            :   /* always flush any pending changes before we queue a new event */
     320                 :        694 :   g_file_monitor_source_file_changes_done (fms, child);
     321                 :        694 :   g_file_monitor_source_queue_event (fms, event_type, child, other);
     322                 :        694 : }
     323                 :            : 
     324                 :            : static void
     325                 :         30 : g_file_monitor_source_send_synthetic_created (GFileMonitorSource *fms,
     326                 :            :                                               const gchar        *child)
     327                 :            : {
     328                 :         30 :   g_file_monitor_source_file_changes_done (fms, child);
     329                 :         30 :   g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CREATED, child, NULL);
     330                 :         30 :   g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, child, NULL);
     331                 :         30 : }
     332                 :            : 
     333                 :            : #ifndef G_DISABLE_ASSERT
     334                 :            : static gboolean
     335                 :        735 : is_basename (const gchar *name)
     336                 :            : {
     337   [ +  +  -  +  :        735 :   if (name[0] == '.' && ((name[1] == '.' && name[2] == '\0') || name[1] == '\0'))
             -  -  -  + ]
     338                 :          0 :     return FALSE;
     339                 :            : 
     340                 :        735 :   return !strchr (name, '/');
     341                 :            : }
     342                 :            : #endif  /* !G_DISABLE_ASSERT */
     343                 :            : 
     344                 :            : gboolean
     345                 :        915 : g_file_monitor_source_handle_event (GFileMonitorSource *fms,
     346                 :            :                                     GFileMonitorEvent   event_type,
     347                 :            :                                     const gchar        *child,
     348                 :            :                                     const gchar        *rename_to,
     349                 :            :                                     GFile              *other,
     350                 :            :                                     gint64              event_time)
     351                 :            : {
     352                 :        915 :   gboolean interesting = TRUE;
     353                 :            : 
     354                 :        915 :   g_assert (!child || is_basename (child));
     355                 :        915 :   g_assert (!rename_to || is_basename (rename_to));
     356                 :            : 
     357   [ +  +  +  -  :        915 :   if (fms->basename && (!child || !g_str_equal (child, fms->basename))
                   +  + ]
     358   [ +  -  -  + ]:         14 :                     && (!rename_to || !g_str_equal (rename_to, fms->basename)))
     359                 :          0 :     return TRUE;
     360                 :            : 
     361                 :        915 :   g_mutex_lock (&fms->lock);
     362                 :            : 
     363                 :            :   /* NOTE:
     364                 :            :    *
     365                 :            :    * We process events even if the file monitor has already been disposed.
     366                 :            :    * The reason is that we must not take a reference to the instance here as
     367                 :            :    * destroying it from the event handling thread will lead to a deadlock when
     368                 :            :    * taking the lock in _ih_sub_cancel.
     369                 :            :    *
     370                 :            :    * This results in seemingly-unbounded growth of the `event_queue` with the
     371                 :            :    * calls to `g_file_monitor_source_queue_event()`. However, each of those sets
     372                 :            :    * the ready time on the #GSource, which means that it will be dispatched in
     373                 :            :    * a subsequent iteration of the #GMainContext it’s attached to. At that
     374                 :            :    * point, `g_file_monitor_source_dispatch()` will return %FALSE, and this will
     375                 :            :    * trigger finalisation of the source. That will clear the `event_queue`.
     376                 :            :    *
     377                 :            :    * If the source is no longer attached, this will return early to prevent
     378                 :            :    * unbounded queueing.
     379                 :            :    */
     380         [ -  + ]:        915 :   if (g_source_is_destroyed ((GSource *) fms))
     381                 :            :     {
     382                 :          0 :       g_mutex_unlock (&fms->lock);
     383                 :          0 :       return TRUE;
     384                 :            :     }
     385                 :            : 
     386   [ +  +  +  +  :        915 :   switch (event_type)
             +  +  +  - ]
     387                 :            :     {
     388                 :         76 :     case G_FILE_MONITOR_EVENT_CREATED:
     389                 :         76 :       g_assert (!other && !rename_to);
     390                 :         76 :       g_file_monitor_source_file_created (fms, child, event_time);
     391                 :         76 :       break;
     392                 :            : 
     393                 :         62 :     case G_FILE_MONITOR_EVENT_CHANGED:
     394                 :         62 :       g_assert (!other && !rename_to);
     395                 :         62 :       interesting = g_file_monitor_source_file_changed (fms, child, event_time);
     396                 :         62 :       break;
     397                 :            : 
     398                 :         82 :     case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
     399                 :         82 :       g_assert (!other && !rename_to);
     400                 :         82 :       g_file_monitor_source_file_changes_done (fms, child);
     401                 :         82 :       break;
     402                 :            : 
     403                 :          2 :     case G_FILE_MONITOR_EVENT_MOVED_IN:
     404                 :          2 :       g_assert (!rename_to);
     405         [ +  + ]:          2 :       if (fms->flags & G_FILE_MONITOR_WATCH_MOVES)
     406                 :          1 :         g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED_IN, child, other);
     407                 :            :       else
     408                 :          1 :         g_file_monitor_source_send_synthetic_created (fms, child);
     409                 :          2 :       break;
     410                 :            : 
     411                 :          2 :     case G_FILE_MONITOR_EVENT_MOVED_OUT:
     412                 :          2 :       g_assert (!rename_to);
     413         [ +  - ]:          2 :       if (fms->flags & G_FILE_MONITOR_WATCH_MOVES)
     414                 :          2 :         g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED_OUT, child, other);
     415   [ #  #  #  # ]:          0 :       else if (other && (fms->flags & G_FILE_MONITOR_SEND_MOVED))
     416                 :          0 :         g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_MOVED, child, other);
     417                 :            :       else
     418                 :          0 :         g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_DELETED, child, NULL);
     419                 :          2 :       break;
     420                 :            : 
     421                 :         32 :     case G_FILE_MONITOR_EVENT_RENAMED:
     422                 :         32 :       g_assert (!other && rename_to);
     423         [ +  + ]:         32 :       if (fms->flags & (G_FILE_MONITOR_WATCH_MOVES | G_FILE_MONITOR_SEND_MOVED))
     424                 :            :         {
     425                 :            :           GFile *other_file;
     426                 :            :           const gchar *dirname;
     427                 :          3 :           gchar *allocated_dirname = NULL;
     428                 :            :           GFileMonitorEvent event;
     429                 :            : 
     430         [ +  - ]:          3 :           event = (fms->flags & G_FILE_MONITOR_WATCH_MOVES) ? G_FILE_MONITOR_EVENT_RENAMED : G_FILE_MONITOR_EVENT_MOVED;
     431                 :            : 
     432         [ +  + ]:          3 :           if (fms->dirname != NULL)
     433                 :          2 :             dirname = fms->dirname;
     434                 :            :           else
     435                 :            :             {
     436                 :          1 :               allocated_dirname = g_path_get_dirname (fms->filename);
     437                 :          1 :               dirname = allocated_dirname;
     438                 :            :             }
     439                 :            : 
     440                 :          3 :           other_file = g_local_file_new_from_dirname_and_basename (dirname, rename_to);
     441                 :          3 :           g_file_monitor_source_file_changes_done (fms, rename_to);
     442                 :          3 :           g_file_monitor_source_send_event (fms, event, child, other_file);
     443                 :            : 
     444                 :          3 :           g_object_unref (other_file);
     445                 :          3 :           g_free (allocated_dirname);
     446                 :            :         }
     447                 :            :       else
     448                 :            :         {
     449                 :         29 :           g_file_monitor_source_send_event (fms, G_FILE_MONITOR_EVENT_DELETED, child, NULL);
     450                 :         29 :           g_file_monitor_source_send_synthetic_created (fms, rename_to);
     451                 :            :         }
     452                 :         32 :       break;
     453                 :            : 
     454                 :        659 :     case G_FILE_MONITOR_EVENT_DELETED:
     455                 :            :     case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
     456                 :            :     case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
     457                 :            :     case G_FILE_MONITOR_EVENT_UNMOUNTED:
     458                 :        659 :       g_assert (!other && !rename_to);
     459                 :        659 :       g_file_monitor_source_send_event (fms, event_type, child, NULL);
     460                 :        659 :       break;
     461                 :            : 
     462                 :          0 :     case G_FILE_MONITOR_EVENT_MOVED:
     463                 :            :       /* was never available in this API */
     464                 :            :     default:
     465                 :            :       g_assert_not_reached ();
     466                 :            :     }
     467                 :            : 
     468                 :        915 :   g_file_monitor_source_update_ready_time (fms);
     469                 :            : 
     470                 :        915 :   g_mutex_unlock (&fms->lock);
     471                 :            : 
     472                 :        915 :   return interesting;
     473                 :            : }
     474                 :            : 
     475                 :            : static gint64
     476                 :          0 : g_file_monitor_source_get_rate_limit (GFileMonitorSource *fms)
     477                 :            : {
     478                 :            :   gint64 rate_limit;
     479                 :            : 
     480                 :          0 :   g_mutex_lock (&fms->lock);
     481                 :          0 :   rate_limit = fms->rate_limit;
     482                 :          0 :   g_mutex_unlock (&fms->lock);
     483                 :            : 
     484                 :          0 :   return rate_limit;
     485                 :            : }
     486                 :            : 
     487                 :            : static gboolean
     488                 :         12 : g_file_monitor_source_set_rate_limit (GFileMonitorSource *fms,
     489                 :            :                                       gint64              rate_limit)
     490                 :            : {
     491                 :            :   gboolean changed;
     492                 :            : 
     493                 :         12 :   g_mutex_lock (&fms->lock);
     494                 :            : 
     495         [ +  - ]:         12 :   if (rate_limit != fms->rate_limit)
     496                 :            :     {
     497                 :         12 :       fms->rate_limit = rate_limit;
     498                 :            : 
     499                 :         12 :       g_sequence_sort (fms->pending_changes, pending_change_compare_ready_time, fms);
     500                 :         12 :       g_file_monitor_source_update_ready_time (fms);
     501                 :            : 
     502                 :         12 :       changed = TRUE;
     503                 :            :     }
     504                 :            :   else
     505                 :          0 :     changed = FALSE;
     506                 :            : 
     507                 :         12 :   g_mutex_unlock (&fms->lock);
     508                 :            : 
     509                 :         12 :   return changed;
     510                 :            : }
     511                 :            : 
     512                 :            : static gboolean
     513                 :        519 : g_file_monitor_source_dispatch (GSource     *source,
     514                 :            :                                 GSourceFunc  callback,
     515                 :            :                                 gpointer     user_data)
     516                 :            : {
     517                 :        519 :   GFileMonitorSource *fms = (GFileMonitorSource *) source;
     518                 :            :   QueuedEvent *event;
     519                 :            :   GQueue event_queue;
     520                 :            :   gint64 now;
     521                 :        519 :   GFileMonitor *instance = NULL;
     522                 :            : 
     523                 :            :   /* make sure the monitor still exists */
     524                 :        519 :   instance = g_weak_ref_get (&fms->instance_ref);
     525         [ -  + ]:        519 :   if (instance == NULL)
     526                 :          0 :     return FALSE;
     527                 :            : 
     528                 :        519 :   now = g_source_get_time (source);
     529                 :            : 
     530                 :            :   /* Acquire the lock once and grab all events in one go, handling the
     531                 :            :    * queued events first.  This avoids strange possibilities in cases of
     532                 :            :    * long delays, such as CHANGED events coming before CREATED events.
     533                 :            :    *
     534                 :            :    * We do this by converting the applicable pending changes into queued
     535                 :            :    * events (after the ones already queued) and then stealing the entire
     536                 :            :    * event queue in one go.
     537                 :            :    */
     538                 :        519 :   g_mutex_lock (&fms->lock);
     539                 :            : 
     540                 :            :   /* Create events for any pending changes that are due to fire */
     541         [ +  + ]:        522 :   while (!g_sequence_is_empty (fms->pending_changes))
     542                 :            :     {
     543                 :         50 :       GSequenceIter *iter = g_sequence_get_begin_iter (fms->pending_changes);
     544                 :         50 :       PendingChange *pending = g_sequence_get (iter);
     545                 :            : 
     546                 :            :       /* We've gotten to a pending change that's not ready.  Stop. */
     547         [ +  + ]:         50 :       if (pending_change_get_ready_time (pending, fms) > now)
     548                 :         47 :         break;
     549                 :            : 
     550         [ +  - ]:          3 :       if (pending->dirty)
     551                 :            :         {
     552                 :            :           /* It's time to send another CHANGED and update the record */
     553                 :          3 :           g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGED, pending->child, NULL);
     554                 :          3 :           pending->last_emission = now;
     555                 :          3 :           pending->dirty = FALSE;
     556                 :            : 
     557                 :          3 :           g_sequence_sort_changed (iter, pending_change_compare_ready_time, fms);
     558                 :            :         }
     559                 :            :       else
     560                 :            :         {
     561                 :            :           /* It's time to send CHANGES_DONE and remove the pending record */
     562                 :          0 :           g_file_monitor_source_queue_event (fms, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, pending->child, NULL);
     563                 :          0 :           g_file_monitor_source_remove_pending_change (fms, iter, pending->child);
     564                 :            :         }
     565                 :            :     }
     566                 :            : 
     567                 :            :   /* Steal the queue */
     568                 :        519 :   memcpy (&event_queue, &fms->event_queue, sizeof event_queue);
     569                 :        519 :   memset (&fms->event_queue, 0, sizeof fms->event_queue);
     570                 :            : 
     571                 :        519 :   g_file_monitor_source_update_ready_time (fms);
     572                 :            : 
     573                 :        519 :   g_mutex_unlock (&fms->lock);
     574                 :        519 :   g_clear_object (&instance);
     575                 :            : 
     576                 :            :   /* We now have our list of events to deliver */
     577         [ +  + ]:       1347 :   while ((event = g_queue_pop_head (&event_queue)))
     578                 :            :     {
     579                 :            :       /* an event handler could destroy 'instance', so check each time */
     580                 :        828 :       instance = g_weak_ref_get (&fms->instance_ref);
     581         [ +  + ]:        828 :       if (instance != NULL)
     582                 :        586 :         g_file_monitor_emit_event (instance, event->child, event->other, event->event_type);
     583                 :            : 
     584                 :        828 :       g_clear_object (&instance);
     585                 :        828 :       queued_event_free (event);
     586                 :            :     }
     587                 :            : 
     588                 :        519 :   return TRUE;
     589                 :            : }
     590                 :            : 
     591                 :            : static void
     592                 :        440 : g_file_monitor_source_dispose (GFileMonitorSource *fms)
     593                 :            : {
     594                 :            :   GHashTableIter iter;
     595                 :            :   gpointer seqiter;
     596                 :            :   QueuedEvent *event;
     597                 :            : 
     598                 :        440 :   g_mutex_lock (&fms->lock);
     599                 :            : 
     600                 :        440 :   g_hash_table_iter_init (&iter, fms->pending_changes_table);
     601         [ +  + ]:        477 :   while (g_hash_table_iter_next (&iter, NULL, &seqiter))
     602                 :            :     {
     603                 :         37 :       g_hash_table_iter_remove (&iter);
     604                 :         37 :       g_sequence_remove (seqiter);
     605                 :            :     }
     606                 :            : 
     607         [ +  + ]:        526 :   while ((event = g_queue_pop_head (&fms->event_queue)))
     608                 :         86 :     queued_event_free (event);
     609                 :            : 
     610                 :        440 :   g_assert (g_sequence_is_empty (fms->pending_changes));
     611                 :        440 :   g_assert (g_hash_table_size (fms->pending_changes_table) == 0);
     612                 :        440 :   g_assert (fms->event_queue.length == 0);
     613                 :        440 :   g_weak_ref_set (&fms->instance_ref, NULL);
     614                 :            : 
     615                 :        440 :   g_file_monitor_source_update_ready_time (fms);
     616                 :            : 
     617                 :        440 :   g_source_destroy ((GSource *) fms);
     618                 :            : 
     619                 :        440 :   g_mutex_unlock (&fms->lock);
     620                 :        440 : }
     621                 :            : 
     622                 :            : static void
     623                 :        440 : g_file_monitor_source_finalize (GSource *source)
     624                 :            : {
     625                 :        440 :   GFileMonitorSource *fms = (GFileMonitorSource *) source;
     626                 :            : 
     627                 :            :   /* should already have been cleared in dispose of the monitor */
     628                 :        440 :   g_assert (g_weak_ref_get (&fms->instance_ref) == NULL);
     629                 :        440 :   g_weak_ref_clear (&fms->instance_ref);
     630                 :            : 
     631                 :        440 :   g_assert (g_sequence_is_empty (fms->pending_changes));
     632                 :        440 :   g_assert (g_hash_table_size (fms->pending_changes_table) == 0);
     633                 :        440 :   g_assert (fms->event_queue.length == 0);
     634                 :            : 
     635                 :        440 :   g_hash_table_unref (fms->pending_changes_table);
     636                 :        440 :   g_sequence_free (fms->pending_changes);
     637                 :            : 
     638                 :        440 :   g_free (fms->dirname);
     639                 :        440 :   g_free (fms->basename);
     640                 :        440 :   g_free (fms->filename);
     641                 :            : 
     642                 :        440 :   g_mutex_clear (&fms->lock);
     643                 :        440 : }
     644                 :            : 
     645                 :            : static guint
     646                 :       1082 : str_hash0 (gconstpointer str)
     647                 :            : {
     648         [ +  + ]:       1082 :   return str ? g_str_hash (str) : 0;
     649                 :            : }
     650                 :            : 
     651                 :            : static gboolean
     652                 :        150 : str_equal0 (gconstpointer a,
     653                 :            :             gconstpointer b)
     654                 :            : {
     655                 :        150 :   return g_strcmp0 (a, b) == 0;
     656                 :            : }
     657                 :            : 
     658                 :            : static GFileMonitorSource *
     659                 :        637 : g_file_monitor_source_new (gpointer           instance,
     660                 :            :                            const gchar       *filename,
     661                 :            :                            gboolean           is_directory,
     662                 :            :                            GFileMonitorFlags  flags)
     663                 :            : {
     664                 :            :   static GSourceFuncs source_funcs = {
     665                 :            :     NULL, NULL,
     666                 :            :     g_file_monitor_source_dispatch,
     667                 :            :     g_file_monitor_source_finalize,
     668                 :            :     NULL, NULL
     669                 :            :   };
     670                 :            :   GFileMonitorSource *fms;
     671                 :            :   GSource *source;
     672                 :            : 
     673                 :        637 :   source = g_source_new (&source_funcs, sizeof (GFileMonitorSource));
     674                 :        637 :   fms = (GFileMonitorSource *) source;
     675                 :            : 
     676                 :        637 :   g_source_set_static_name (source, "GFileMonitorSource");
     677                 :            : 
     678                 :        637 :   g_mutex_init (&fms->lock);
     679                 :        637 :   g_weak_ref_init (&fms->instance_ref, instance);
     680                 :        637 :   fms->pending_changes = g_sequence_new (pending_change_free);
     681                 :        637 :   fms->pending_changes_table = g_hash_table_new (str_hash0, str_equal0);
     682                 :        637 :   fms->rate_limit = DEFAULT_RATE_LIMIT;
     683                 :        637 :   fms->flags = flags;
     684                 :            : 
     685         [ +  + ]:        637 :   if (is_directory)
     686                 :            :     {
     687                 :        521 :       fms->dirname = g_strdup (filename);
     688                 :        521 :       fms->basename = NULL;
     689                 :        521 :       fms->filename = NULL;
     690                 :            :     }
     691         [ +  + ]:        116 :   else if (flags & G_FILE_MONITOR_WATCH_HARD_LINKS)
     692                 :            :     {
     693                 :          1 :       fms->dirname = NULL;
     694                 :          1 :       fms->basename = NULL;
     695                 :          1 :       fms->filename = g_strdup (filename);
     696                 :            :     }
     697                 :            :   else
     698                 :            :     {
     699                 :        115 :       fms->dirname = g_path_get_dirname (filename);
     700                 :        115 :       fms->basename = g_path_get_basename (filename);
     701                 :        115 :       fms->filename = NULL;
     702                 :            :     }
     703                 :            : 
     704                 :        637 :   return fms;
     705                 :            : }
     706                 :            : 
     707   [ +  +  +  -  :       2108 : G_DEFINE_ABSTRACT_TYPE (GLocalFileMonitor, g_local_file_monitor, G_TYPE_FILE_MONITOR)
                   +  + ]
     708                 :            : 
     709                 :            : enum {
     710                 :            :   PROP_0,
     711                 :            :   PROP_RATE_LIMIT,
     712                 :            : };
     713                 :            : 
     714                 :            : static void
     715                 :          0 : g_local_file_monitor_get_property (GObject *object, guint prop_id,
     716                 :            :                                    GValue *value, GParamSpec *pspec)
     717                 :            : {
     718                 :          0 :   GLocalFileMonitor *monitor = G_LOCAL_FILE_MONITOR (object);
     719                 :            :   gint64 rate_limit;
     720                 :            : 
     721                 :          0 :   g_assert (prop_id == PROP_RATE_LIMIT);
     722                 :            : 
     723                 :          0 :   rate_limit = g_file_monitor_source_get_rate_limit (monitor->source);
     724                 :          0 :   rate_limit /= G_TIME_SPAN_MILLISECOND;
     725                 :            : 
     726                 :          0 :   g_value_set_int (value, rate_limit);
     727                 :          0 : }
     728                 :            : 
     729                 :            : static void
     730                 :         12 : g_local_file_monitor_set_property (GObject *object, guint prop_id,
     731                 :            :                                    const GValue *value, GParamSpec *pspec)
     732                 :            : {
     733                 :         12 :   GLocalFileMonitor *monitor = G_LOCAL_FILE_MONITOR (object);
     734                 :            :   gint64 rate_limit;
     735                 :            : 
     736                 :         12 :   g_assert (prop_id == PROP_RATE_LIMIT);
     737                 :            : 
     738                 :         12 :   rate_limit = g_value_get_int (value);
     739                 :         12 :   rate_limit *= G_TIME_SPAN_MILLISECOND;
     740                 :            : 
     741         [ +  - ]:         12 :   if (g_file_monitor_source_set_rate_limit (monitor->source, rate_limit))
     742                 :         12 :     g_object_notify (object, "rate-limit");
     743                 :         12 : }
     744                 :            : 
     745                 :            : #ifndef G_OS_WIN32
     746                 :            : static void
     747                 :          0 : g_local_file_monitor_mounts_changed (GUnixMountMonitor *mount_monitor,
     748                 :            :                                      gpointer           user_data)
     749                 :            : {
     750                 :          0 :   GLocalFileMonitor *local_monitor = user_data;
     751                 :            :   GUnixMountEntry *mount;
     752                 :            :   gboolean is_mounted;
     753                 :            :   GFile *file;
     754                 :            : 
     755                 :            :   /* Emulate unmount detection */
     756                 :          0 :   mount = g_unix_mount_at (local_monitor->source->dirname, NULL);
     757                 :            : 
     758                 :          0 :   is_mounted = mount != NULL;
     759                 :            : 
     760         [ #  # ]:          0 :   if (mount)
     761                 :          0 :     g_unix_mount_free (mount);
     762                 :            : 
     763         [ #  # ]:          0 :   if (local_monitor->was_mounted != is_mounted)
     764                 :            :     {
     765   [ #  #  #  # ]:          0 :       if (local_monitor->was_mounted && !is_mounted)
     766                 :            :         {
     767                 :          0 :           file = g_file_new_for_path (local_monitor->source->dirname);
     768                 :          0 :           g_file_monitor_emit_event (G_FILE_MONITOR (local_monitor), file, NULL, G_FILE_MONITOR_EVENT_UNMOUNTED);
     769                 :          0 :           g_object_unref (file);
     770                 :            :         }
     771                 :          0 :       local_monitor->was_mounted = is_mounted;
     772                 :            :     }
     773                 :          0 : }
     774                 :            : #endif
     775                 :            : 
     776                 :            : static void
     777                 :        637 : g_local_file_monitor_start (GLocalFileMonitor *local_monitor,
     778                 :            :                             const gchar       *filename,
     779                 :            :                             gboolean           is_directory,
     780                 :            :                             GFileMonitorFlags  flags,
     781                 :            :                             GMainContext      *context)
     782                 :            : {
     783                 :        637 :   GLocalFileMonitorClass *class = G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor);
     784                 :            :   GFileMonitorSource *source;
     785                 :            : 
     786                 :        637 :   g_return_if_fail (G_IS_LOCAL_FILE_MONITOR (local_monitor));
     787                 :            : 
     788                 :        637 :   g_assert (!local_monitor->source);
     789                 :            : 
     790                 :        637 :   source = g_file_monitor_source_new (local_monitor, filename, is_directory, flags);
     791                 :        637 :   local_monitor->source = source; /* owns the ref */
     792                 :            : 
     793   [ +  +  -  +  :        637 :   if (is_directory && !class->mount_notify && (flags & G_FILE_MONITOR_WATCH_MOUNTS))
                   -  - ]
     794                 :            :     {
     795                 :            : #ifdef G_OS_WIN32
     796                 :            :       /*claim everything was mounted */
     797                 :            :       local_monitor->was_mounted = TRUE;
     798                 :            : #else
     799                 :            :       GUnixMountEntry *mount;
     800                 :            : 
     801                 :            :       /* Emulate unmount detection */
     802                 :            : 
     803                 :          0 :       mount = g_unix_mount_at (local_monitor->source->dirname, NULL);
     804                 :            : 
     805                 :          0 :       local_monitor->was_mounted = mount != NULL;
     806                 :            : 
     807         [ #  # ]:          0 :       if (mount)
     808                 :          0 :         g_unix_mount_free (mount);
     809                 :            : 
     810                 :          0 :       local_monitor->mount_monitor = g_unix_mount_monitor_get ();
     811                 :          0 :       g_signal_connect_object (local_monitor->mount_monitor, "mounts-changed",
     812                 :            :                                G_CALLBACK (g_local_file_monitor_mounts_changed), local_monitor,
     813                 :            :                                G_CONNECT_DEFAULT);
     814                 :            : #endif
     815                 :            :     }
     816                 :            : 
     817                 :        637 :   g_source_attach ((GSource *) source, context);
     818                 :            : 
     819                 :        637 :   G_LOCAL_FILE_MONITOR_GET_CLASS (local_monitor)->start (local_monitor,
     820                 :        637 :                                                          source->dirname, source->basename, source->filename,
     821                 :            :                                                          source);
     822                 :            : }
     823                 :            : 
     824                 :            : static void
     825                 :        440 : g_local_file_monitor_dispose (GObject *object)
     826                 :            : {
     827                 :        440 :   GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
     828                 :            : 
     829                 :        440 :   g_file_monitor_source_dispose (local_monitor->source);
     830                 :            : 
     831                 :        440 :   G_OBJECT_CLASS (g_local_file_monitor_parent_class)->dispose (object);
     832                 :        440 : }
     833                 :            : 
     834                 :            : static void
     835                 :        440 : g_local_file_monitor_finalize (GObject *object)
     836                 :            : {
     837                 :        440 :   GLocalFileMonitor *local_monitor = G_LOCAL_FILE_MONITOR (object);
     838                 :            : 
     839                 :        440 :   g_source_unref ((GSource *) local_monitor->source);
     840                 :            : 
     841                 :        440 :   G_OBJECT_CLASS (g_local_file_monitor_parent_class)->finalize (object);
     842                 :        440 : }
     843                 :            : 
     844                 :            : static void
     845                 :        637 : g_local_file_monitor_init (GLocalFileMonitor* local_monitor)
     846                 :            : {
     847                 :        637 : }
     848                 :            : 
     849                 :         57 : static void g_local_file_monitor_class_init (GLocalFileMonitorClass *class)
     850                 :            : {
     851                 :         57 :   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
     852                 :            : 
     853                 :         57 :   gobject_class->get_property = g_local_file_monitor_get_property;
     854                 :         57 :   gobject_class->set_property = g_local_file_monitor_set_property;
     855                 :         57 :   gobject_class->dispose = g_local_file_monitor_dispose;
     856                 :         57 :   gobject_class->finalize = g_local_file_monitor_finalize;
     857                 :            : 
     858                 :         57 :   g_object_class_override_property (gobject_class, PROP_RATE_LIMIT, "rate-limit");
     859                 :         57 : }
     860                 :            : 
     861                 :            : static GLocalFileMonitor *
     862                 :        637 : g_local_file_monitor_new (gboolean   is_remote_fs,
     863                 :            :                           gboolean   is_directory,
     864                 :            :                           GError   **error)
     865                 :            : {
     866                 :        637 :   GType type = G_TYPE_INVALID;
     867                 :            : 
     868         [ -  + ]:        637 :   if (is_remote_fs)
     869                 :          0 :     type = _g_io_module_get_default_type (G_NFS_FILE_MONITOR_EXTENSION_POINT_NAME,
     870                 :            :                                           "GIO_USE_FILE_MONITOR",
     871                 :            :                                           G_STRUCT_OFFSET (GLocalFileMonitorClass, is_supported));
     872                 :            : 
     873                 :            :   /* Fallback rather to poll file monitor for remote files, see gfile.c. */
     874   [ +  -  -  +  :        637 :   if (type == G_TYPE_INVALID && (!is_remote_fs || is_directory))
                   -  - ]
     875                 :        637 :     type = _g_io_module_get_default_type (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
     876                 :            :                                           "GIO_USE_FILE_MONITOR",
     877                 :            :                                           G_STRUCT_OFFSET (GLocalFileMonitorClass, is_supported));
     878                 :            : 
     879         [ -  + ]:        637 :   if (type == G_TYPE_INVALID)
     880                 :            :     {
     881                 :          0 :       g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
     882                 :            :                            _("Unable to find default local file monitor type"));
     883                 :          0 :       return NULL;
     884                 :            :     }
     885                 :            : 
     886                 :        637 :   return g_object_new (type, NULL);
     887                 :            : }
     888                 :            : 
     889                 :            : GFileMonitor *
     890                 :        126 : g_local_file_monitor_new_for_path (const gchar        *pathname,
     891                 :            :                                    gboolean            is_directory,
     892                 :            :                                    GFileMonitorFlags   flags,
     893                 :            :                                    GError            **error)
     894                 :            : {
     895                 :            :   GLocalFileMonitor *monitor;
     896                 :            :   gboolean is_remote_fs;
     897                 :            : 
     898                 :        126 :   is_remote_fs = g_local_file_is_nfs_home (pathname);
     899                 :            : 
     900                 :        126 :   monitor = g_local_file_monitor_new (is_remote_fs, is_directory, error);
     901                 :            : 
     902         [ +  - ]:        126 :   if (monitor)
     903                 :        126 :     g_local_file_monitor_start (monitor, pathname, is_directory, flags, g_main_context_get_thread_default ());
     904                 :            : 
     905                 :        126 :   return G_FILE_MONITOR (monitor);
     906                 :            : }
     907                 :            : 
     908                 :            : GFileMonitor *
     909                 :        511 : g_local_file_monitor_new_in_worker (const gchar           *pathname,
     910                 :            :                                     gboolean               is_directory,
     911                 :            :                                     GFileMonitorFlags      flags,
     912                 :            :                                     GFileMonitorCallback   callback,
     913                 :            :                                     gpointer               user_data,
     914                 :            :                                     GClosureNotify         destroy_user_data,
     915                 :            :                                     GError               **error)
     916                 :            : {
     917                 :            :   GLocalFileMonitor *monitor;
     918                 :            :   gboolean is_remote_fs;
     919                 :            : 
     920                 :        511 :   is_remote_fs = g_local_file_is_nfs_home (pathname);
     921                 :            : 
     922                 :        511 :   monitor = g_local_file_monitor_new (is_remote_fs, is_directory, error);
     923                 :            : 
     924         [ +  - ]:        511 :   if (monitor)
     925                 :            :     {
     926         [ +  - ]:        511 :       if (callback)
     927                 :        511 :         g_signal_connect_data (monitor, "changed", G_CALLBACK (callback),
     928                 :            :                                user_data, destroy_user_data, G_CONNECT_DEFAULT);
     929                 :            : 
     930                 :        511 :       g_local_file_monitor_start (monitor, pathname, is_directory, flags, GLIB_PRIVATE_CALL(g_get_worker_context) ());
     931                 :            :     }
     932                 :            : 
     933                 :        511 :   return G_FILE_MONITOR (monitor);
     934                 :            : }

Generated by: LCOV version 1.14