/* $Id$ Copyright (C) 2005-2011 tooar This file is part of emelFM2. emelFM2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. emelFM2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with emelFM2; see the file GPL. If not, see http://www.gnu.org/licenses. */ /** @file src/filesystem/e2_fs_FAM_kqueue.c @brief functions related to file-alteration monitoring using kqueue on BSD/OSX */ /** \page kqueue kqueue operations A connection to the kernel's kqueue apparatus is established at session-start, and closed at session-end. Some associated setup and cleanup happens, respectivley. Whenever a filelist-directory is opened in the application, a "watch" is set for that directory (except if it is already watched in the other pane). NOTE this may be insufficient, may need to watch watch each item in the dir!! Similarly, if the appropriate option is in effect, a watch is set on the application config-data file. Feedback from the kernel about any watch is detected by periodic polling (every QPOLL_MSEC) of the kernel's event-reports-queue. We don't use select() or poll() on the connection, because we don't want to block (in a separate thread), and we do want to be able to abort/suspend without delay. Downside is continuing CPU activity within the application, due to the timer, even when no item changes. All such feedback is logged as quickly as possible, and later processed according to the context. A watch-specific timer is used to defer such processing. When a report is receieved, and the corresponding timer is inactive, then we start the timer with a short interval (IN_SHORTMSEC) to allow accumulation of any further reports (which often come in blocks). When the short timer ends, if refresh is enabled, initiate refresh for the subject, clean up kqueue data, and restart the timer with a longer interval (IN_LONGMSEC) during which further reports will be logged but not processed. This is to prevent rapid un-necessary repeating of refreshes. When the long timer ends, if refresh is enabled, and if report(s) have been logged, process them, clean up kqueue data, and restart the long timer. If no report has been logged, do not restart, pending further report. When a timer ends and refresh is disabled, do not immediately clean up reports/ kqueue data. When refresh is enabled or requested, if short timer is running, do nothing. If long timer is running, stop it, and treat as for end of long timer, as above. Count(s) of logged event-reports of are accessed only during timer callbacks, which should not overlap. So no protection against contemporary access is provided. */ #include "emelfm2.h" #ifdef E2_FAM_KQUEUE #include #include #include #include #include #include "e2_filelist.h" #define EXTRA_MESSAGES /* event-flags used for monitoring dirs when full refreshing is in effect There's no ACCESS-specific event, unless NOTE_ATTRIB includes that */ #define KQDIR_FLAGS \ NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE /* flags used when limited refreshing is in effect (i.e. no ACCESS reporting) */ #define KQDIR_SHORT_FLAGS NOTE_DELETE | NOTE_WRITE | NOTE_RENAME | NOTE_REVOKE //CHECKME /* flags used to supplement KQDIR_SHORT_FLAGS to get back to full operation */ #define KQDIR_XTRA_FLAGS 0 /*for the config file we're interested only in events that would trigger a reload of that file, ie modification or creation. Not deletion, as a replacement may not yet be available. */ #define KQCFG_FLAGS NOTE_WRITE //timer interval between kqueue queue polls #define QPOLL_MSEC 350 //short timer interval (between any 'initial' event-report and refresh-request) //a bit longer than QPOLL_MSEC #define IN_SHORTMSEC 400 //long timer interval (between any refresh-request and another one, if needed) #define IN_LONGMSEC 3000 //the connection to kqueue static gint kqueue_fd = 0; //for deferring processing after any report is received from kqueue static guint target0_timerid = 0; static guint target1_timerid = 0; static guint target2_timerid = 0; //for logging whether respective timers are in long or short phase static guint target0_timer_interval = 0; static guint target1_timer_interval = 0; static guint target2_timer_interval = 0; extern gint p1dirty, p2dirty, cfgdirty; //path of monitored config-file, cached to avoid repeated create+free in timer calllback static gchar *config_file; //array-columns enumerator enum { REPORT_WD, REPORT_COUNT, REPORT_ROWS }; //FAMreq value for blocking use of a watch value, must be < 0 #define WD_BUSY -2 //array REPORT_WD value for an error #define WD_ERR -1 //array REPORT_WD value for an unused row, intentionally the same as value from //a failed kqueue function-call #define WD_UNUSED -1 /* store for logging reports and associated stuff for up to 3 fd's (1 or 2 pane-dirs and config file NOTE pane1 not necessarily target/row 0, pane2 not nec. target/row 1) Rows are used as per the enum above*/ static gint watch_reports[3][REPORT_ROWS] = {{WD_UNUSED}, {WD_UNUSED}, {WD_UNUSED}}; //cached strings for each row of watch_reports array. Contains the path of //respective watched item, used for checking path is still relevant when timer //finishes. For speed, mManipulated with glibc funcs, not glib equivalents. static gchar *watch_paths [3]; //path of monitored config-file, cached to avoid repeated create+free in timer calllback static gchar *config_file; /** @brief timer callback after any 'short' or 'long' interval between refresh requests If event-report(s) have been logged, and if refresh is enabled, process the event(s), clean up kqueue data, and start or continue the long timer. If no report has been logged, abort timer pending further reporting. @param user_data pointerised enumerator 0..2, for row in watch_reports array @return TRUE if this is a long timer callback, and refresh has been initiated while here */ static gboolean _e2_fs_FAM_kqueue_timeout (gpointer user_data) { gboolean more = FALSE; //default guint target_id = GPOINTER_TO_UINT (user_data); printd (DEBUG, "In _e2_fs_FAM_kqueue_timeout, for target %u", target_id); gchar *target_path = (watch_reports[target_id][REPORT_WD] != WD_UNUSED) ? watch_paths[target_id] : NULL; if (G_LIKELY (target_path != NULL)) { if (watch_reports[target_id][REPORT_COUNT] > 0) //report(s) available { #ifdef EXTRA_MESSAGES // printd (DEBUG, " target %d (watch %d) watch-reports count = %d", // target_id, watch_reports[target_id][REPORT_WD], watch_reports[target_id][REPORT_COUNT]); #endif if (strcmp (target_path, app.pane1.path) == 0) { #ifdef E2_REFRESH_DEBUG printd (DEBUG,"dirty set TRUE for pane 1"); #endif g_atomic_int_set (&p1dirty, 1); //set shared flag to trigger refresh more = TRUE; } //both panes may be the same if (strcmp (target_path, app.pane2.path) == 0) { #ifdef E2_REFRESH_DEBUG printd (DEBUG,"dirty set TRUE for pane 2"); #endif g_atomic_int_set (&p2dirty, 1); more = TRUE; } if (strcmp (target_path, config_file) == 0) { //CHECKME involve "auto-refresh-config" option ? #ifdef E2_REFRESH_DEBUG printd (DEBUG,"dirty set TRUE for config"); #endif g_atomic_int_set (&cfgdirty, 1); more = TRUE; } if (more) e2_filelist_check_dirty (NULL); //start a refresh ASAP #ifdef EXTRA_MESSAGES printd (DEBUG, "clear reports-count for %s", watch_paths[target_id]); #endif printall ("after requesting refresh for %s (watch %d), reports-count (%d) is zeroed", watch_paths [target_id], watch_reports[target_id][REPORT_WD], watch_reports[target_id][REPORT_COUNT]); E2_BLOCK watch_reports[target_id][REPORT_COUNT] = 0; E2_UNBLOCK } } else //target_path == NULL, i.e. cached data is for some item not monitored now if (watch_reports[target_id][REPORT_WD] != WD_UNUSED) { printd (DEBUG, "OOPS no cached path for target"); //clear data E2_BLOCK watch_reports[target_id][REPORT_WD] = WD_UNUSED; watch_reports[target_id][REPORT_COUNT] = 0; E2_UNBLOCK } switch (target_id) { case 0: if (more) { if (target0_timer_interval < IN_LONGMSEC) { target0_timer_interval = IN_LONGMSEC; //start long timer, and later return FALSE to kill this one target0_timerid = g_timeout_add (IN_LONGMSEC, _e2_fs_FAM_kqueue_timeout, GUINT_TO_POINTER (0)); more = FALSE; } } else { //no more reports to process, this timer will be killed // printd (DEBUG, "clear timer parameters"); target0_timer_interval = 0; target0_timerid = 0; } break; case 1: if (more) { if (target1_timer_interval < IN_LONGMSEC) { target1_timer_interval = IN_LONGMSEC; //start long timer, and later return FALSE to kill this one target1_timerid = g_timeout_add (IN_LONGMSEC, _e2_fs_FAM_kqueue_timeout, GUINT_TO_POINTER (1)); more = FALSE; } } else { target1_timer_interval = 0; target1_timerid = 0; } break; case 2: if (more) { if (target2_timer_interval < IN_LONGMSEC) { target2_timer_interval = IN_LONGMSEC; //start long timer, and later return FALSE to kill this one target2_timerid = g_timeout_add (IN_LONGMSEC, _e2_fs_FAM_kqueue_timeout, GUINT_TO_POINTER (2)); more = FALSE; } } else { target2_timer_interval = 0; target2_timerid = 0; } break; default: more = FALSE; break; } #ifdef EXTRA_MESSAGES printd (DEBUG, " kqueue timer will %s", (more)? "continue":"stop now"); #endif return more; //continue long timer if a refresh was requested } /** @brief cleanup cached data for @a wd If @a path is non-NULL, this stops after cearing the first match of both @a path and @a wd in the watch_reports table. If @a path is NULL, data for all instances of @a wd are cleared. @param path UTF-8 path string, for comparison with watch_paths[] @param wd watched file-descriptor number @return */ static void _e2_fs_FAM_clean_local (const gchar *path, gint wd) { E2_BLOCK gint i; for (i=0; i<3; i++) { if (watch_reports[i][REPORT_WD] == wd) { if (path == NULL || strcmp (path, watch_paths[i]) == 0) { watch_reports[i][REPORT_WD] = WD_UNUSED; free (watch_paths[i]); watch_paths[i] = NULL; if (path != NULL) break; } } } E2_UNBLOCK } /** @brief get any kqueue reports, and record the essential data for later processing This is a timer callback fn, as well as being called directly. It must not do any processing per se - that takes too long and risks loss of reports @param user_data pointer, generally NULL, specified when the source was created or this func was called @return TRUE always, so timer is never cancelled */ static gboolean _e2_fs_FAM_kqueue_read (gpointer user_data) { struct timespec wait; memset (&wait, 0, sizeof (struct timespec)); struct kevent ke; EV_SET (&ke, 0, EVFILT_VNODE, 0, 0, 0, NULL); //loop to read all the queued kqueue reports guint i; for (i = 0; i < 3; i++) { if (watch_reports[i][REPORT_WD] == WD_UNUSED) continue; ke.ident = watch_reports[i][REPORT_WD]; gint nreports = kevent (kqueue_fd, NULL, 0, &ke, 1, &wait); //poll the Q if (nreports == 0) continue; else if (nreports == -1) { //TODO handle error continue; } if (ke.flags & EV_ERROR) { //TODO handle error, use ev.data continue; } //log the report(s) E2_BLOCK watch_reports[i][REPORT_COUNT] += nreports; #ifdef EXTRA_MESSAGES printd (DEBUG, "kqueue reported wd %d mask 0x%x for item %s", ke.ident, ke.fflags, watch_paths[i]); printd (DEBUG, "%d reports logged for wd %d", watch_reports[i][REPORT_COUNT], ke.ident); #endif E2_UNBLOCK } /* when report(s) received, and the corresponding timer is inactive, start the timer with a short interval */ if (watch_reports[0][REPORT_COUNT] > 0 && target0_timer_interval == 0) { target0_timer_interval = IN_SHORTMSEC; //flag that short delay is happening //timer-callback data = pointerised enumerator of array row target0_timerid = g_timeout_add (IN_SHORTMSEC, _e2_fs_FAM_kqueue_timeout, GUINT_TO_POINTER (0)); } if (watch_reports[1][REPORT_COUNT] > 0 && target1_timer_interval == 0) { target1_timer_interval = IN_SHORTMSEC; target1_timerid = g_timeout_add (IN_SHORTMSEC, _e2_fs_FAM_kqueue_timeout, GUINT_TO_POINTER (1)); } if (watch_reports[2][REPORT_COUNT] > 0 && target2_timer_interval == 0) { target2_timer_interval = IN_SHORTMSEC; target2_timerid = g_timeout_add (IN_SHORTMSEC, _e2_fs_FAM_kqueue_timeout, GUINT_TO_POINTER (2)); } return TRUE; } //hack, for debugging at least #ifndef O_EVTONLY #define O_EVTONLY 0x8000 /* descriptor requested for event notifications only */ #endif /** @brief register @a path with kqueue This assumes @a path will not be registered more than once at any time, even though kqueue supports that. The first-available row in watch_reports array is used, so watch_reports[0] is not necessarily for pane1, etc. This func can be called from within a threaded cd @param path UTF-8 string with path of item to be monitored @param mask flag(s) indicating the type of reports wanted @return a file-descriptor for @a path, <0 for an error */ static gint _e2_fs_FAM_kqueue_monitor_item (gchar *path, guint mask) { //don't bother checking for path already monitored, just open it gchar *local = F_FILENAME_TO_LOCALE (path); gint wd = e2_fs_safeopen (local, O_EVTONLY | O_NONBLOCK, -1); //or O_RDONLY | O_NONBLOCK ? F_FREE (local, path); if (wd >= 0) { struct kevent ke; EV_SET (&ke, wd, EVFILT_VNODE, EV_ADD | EV_CLEAR, mask, 0, NULL); if (kevent (kqueue_fd, &ke, 1, NULL, 0, NULL) >= 0) { //setup the reports log gint i; E2_BLOCK for (i=0; i<3; i++) { if (watch_reports[i][REPORT_WD] == WD_UNUSED) { watch_reports[i][REPORT_WD] = wd; watch_reports[i][REPORT_COUNT] = 0; watch_paths[i] = strdup (path); printd (DEBUG, "Added kqueue watch %d for %s", wd, path); printall ("Added kqueue watch %d for %s", wd, path); break; } } E2_UNBLOCK #ifdef EXTRA_MESSAGES if (i == 3) printd (WARN, "Failed to add kqueue watch for %s\n%s", path, "No space in reports-table"); printd (DEBUG, "Tabled watches now are %d, %d, %d", watch_reports[0][REPORT_WD], watch_reports[1][REPORT_WD], watch_reports[2][REPORT_WD]); #endif printall ("Tabled watches now are %d, %d, %d", watch_reports[0][REPORT_WD], watch_reports[1][REPORT_WD], watch_reports[2][REPORT_WD]); return wd; } } printd (WARN, "Failed to add kqueue watch for %s\n%s", path, strerror (errno)); return -1; } /** @brief change the scope of kqueue-monitoring for @a path @param path UTF-8 string with path of item to be changed, to be compared with rt->path's @param mask bitflags for changed monitoring scope @return */ static void _e2_fs_FAM_kqueue_remonitor_item (gchar *path, guint mask) { gint fd; #ifdef E2_VFSTMP //FIXME path for non-mounted dirs #endif if (strcmp (path, curr_pane->path) == 0) fd = g_atomic_int_get (&curr_pane->FAMreq); else if (strcmp (path, other_pane->path) == 0) fd = g_atomic_int_get (&other_pane->FAMreq); else return; if (fd >= 0) { struct kevent ke; EV_SET (&ke, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, mask, 0, NULL); if (kevent (kqueue_fd, &ke, 1, NULL, 0, NULL) < 0) printd (DEBUG, "kqueue watch-change failed"); } } /** @brief de-register @a path from kqueue This func can be called from within a threaded refresh. Do NOT call this when the same watch applies to more than one directory. @param path UTF-8 string with path of item no longer to be monitored @param wd watch descriptor number that is to be cancelled @return TRUE if successfully cancelled */ static gboolean _e2_fs_FAM_kqueue_demonitor_item (gchar *path, gint wd) { gboolean retval; struct kevent ke; if (wd < 0) return FALSE; EV_SET (&ke, wd, EVFILT_VNODE, EV_DELETE, 0, 0, NULL); if (kevent (kqueue_fd, &ke, 1, NULL, 0, NULL) >= 0) { printd (DEBUG, "Removed kqueue %d watch for %s", wd, path); retval = TRUE; } else { //error may be because monitored item (e.g. config file or unmounted dir) is gone now printd (DEBUG, "%s error when trying to remove kqueue watch %d for %s", strerror (errno), wd, path); retval = FALSE; } //CHECKME need to flush the reports queue _e2_fs_FAM_clean_local (path, wd); #ifdef EXTRA_MESSAGES printd (DEBUG, "Tabled watches now are %d, %d, %d", watch_reports[0][REPORT_WD], watch_reports[1][REPORT_WD], watch_reports[2][REPORT_WD]); #endif printall ("After cancelling watch for %s\n tabled watches now are %d, %d, %d", path, watch_reports[0][REPORT_WD], watch_reports[1][REPORT_WD], watch_reports[2][REPORT_WD]); return retval; } /** @brief Initialize kqueue monitoring. @return TRUE if initialization succeeded */ static gboolean _e2_fs_FAM_kqueue_init (void) { kqueue_fd = kqueue (); if (kqueue_fd < 0) { printd (WARN, "Could not initialize kqueue"); return FALSE; } #ifdef E2_REFRESH_DEBUG printd (DEBUG, "start Q-poll timer @ init"); #endif /* Use one of the 'main' timers so its suspension can be managed like others This one would be used for 'default' polling if kernel-fam were not available Some cleanup code assumes the same timer for both modes - do not change it BUT shutting this down will also block config updating */ app.timers[DIRTYCHECK_T] = g_timeout_add (QPOLL_MSEC, _e2_fs_FAM_kqueue_read, NULL); return TRUE; } /** @brief cleanup as part of ending kqueue monitoring. @return */ static void _e2_fs_FAM_kqueue_abandon (void) { if (app.timers[DIRTYCHECK_T] != 0) { g_source_remove (app.timers[DIRTYCHECK_T]); app.timers[DIRTYCHECK_T] = 0; } if (target0_timerid != 0) { g_source_remove (target0_timerid); // target0_timerid = 0; } if (target1_timerid != 0) { g_source_remove (target1_timerid); // target1_timerid = 0; } if (target2_timerid != 0) { g_source_remove (target2_timerid); // target2_timerid = 0; } //cancel all monitoring that's in force //any queued pending reports are cleaned by kernel //CHECKME is it a real file-descriptor e2_fs_safeclose (kqueue_fd); guint i; for (i=0; i<3; i++) { if (watch_reports[i][REPORT_WD] > 0) { e2_fs_safeclose (watch_reports[i][REPORT_WD]); // watch_reports[i][REPORT_WD] = WD_UNUSED; } if (watch_paths[i] != NULL) { free (watch_paths[i]); // watch_paths[i] = NULL; } } } /**********************/ /** public functions **/ /**********************/ /** @brief establish kqueue connection @return */ void e2_fs_FAM_connect (void) { if (_e2_fs_FAM_kqueue_init ()) app.monitor_type = E2_MONITOR_FAM; else app.monitor_type = E2_MONITOR_DEFAULT; } /** @brief terminate kqueue connection Call at session-end @return */ void e2_fs_FAM_disconnect (void) { if (app.monitor_type == E2_MONITOR_FAM) _e2_fs_FAM_kqueue_abandon (); if (config_file != NULL) { g_free (config_file); config_file = NULL; } } /** @brief put kqueue monitoring to 'sleep' i.e. suspend polling activity @return */ void e2_fs_FAM_suspend (void) { if (app.timers[DIRTYCHECK_T] != 0) { g_source_remove (app.timers[DIRTYCHECK_T]); app.timers[DIRTYCHECK_T] = 0; } } /** @brief wake up kqueue monitoring @return */ void e2_fs_FAM_resume (void) { if (app.timers[DIRTYCHECK_T] == 0) app.timers[DIRTYCHECK_T] = g_timeout_add (QPOLL_MSEC, _e2_fs_FAM_kqueue_read, NULL); } /** @brief change monitored directory from @a oldir to @a rt ->view.dir Called during threaded cd. If @a olddir not also in the other pane, cancel old-dir monitoring. If the dir for @a rt (new-dir) is not also in other pane, start monitoring new-dir. If new-dir is not monitored (FAM error) the FAM request for the pane is set to WD_UNUSED @a olddir needs trailing '/', for valid comparing with rt->view.dir etc @param olddir UTF-8 string with absolute path of dir to stop monitoring @param rt data struct for the pane to which the cd applies, including the new dir in rt->view.dir @return */ void e2_fs_FAM_change (gchar *olddir, E2_PaneRuntime *rt) { if (G_LIKELY (app.monitor_type == E2_MONITOR_FAM)) { //kqueue connection set up E2_PaneRuntime *ort; gint old, new, other; ort = (rt == curr_pane) ? other_pane : curr_pane; //atomic access cuz' other threads may want to use FAMreq(s) old = g_atomic_int_get (&rt->FAMreq); other = g_atomic_int_get (&ort->FAMreq); g_atomic_int_set (&rt->FAMreq, WD_BUSY); //block other threads from messing with it //if both panes are now using the same watch (FAMreq), or this pane is //unwatched, we can't cancel the watch for olddir if (!(old == other || old == WD_UNUSED)) _e2_fs_FAM_kqueue_demonitor_item (olddir, old); //#ifdef EXTRA_MESSAGES // else // printd (DEBUG, "skip demonitor, old wd = %d, other wd = %d", old, other); //#endif else printall ("During CD, skip demonitor, old wd = %d, other wd = %d", old, other); //if both panes will be using the same watch, no need to start a new //watch, just replicate data if (strcmp (rt->view.dir, ort->view.dir) == 0 && other > 0) { new = other; //#ifdef EXTRA_MESSAGES // printd (DEBUG, "Set pane FAMreq = %d from other pane", new); //#endif } else { new = _e2_fs_FAM_kqueue_monitor_item (rt->view.dir, INDIR_FLAGS); //#ifdef EXTRA_MESSAGES // printd (DEBUG, "Set pane FAMreq = %d from new watch", new); //#endif } g_atomic_int_set (&rt->FAMreq, new); printall ("After kqueue setup during CD, FAMreq's are %d, %d", curr_pane->FAMreq, other_pane->FAMreq); } } /** @brief finish or suspend monitoring of dir related to @a rt @return TRUE if the connection was successfully removed */ gboolean e2_fs_FAM_cancel_monitor_dir (E2_PaneRuntime *rt) { gboolean result; gint old = g_atomic_int_get (&rt->FAMreq); if (old > 0) { result = _e2_fs_FAM_kqueue_demonitor_item (rt->path, old); if (result) { g_atomic_int_set (&rt->FAMreq, WD_UNUSED); /*#ifdef DEBUG_MESSAGES printd (DEBUG, "FAM cancel for %s succeeded", path); #endif */ } /*#ifdef DEBUG_MESSAGES else printd (WARN, "FAM cancel for %s failed", path); #endif */ } else result = FALSE; return result; } /** @brief reduce the scope of event-monitoring for @a path Essentially this abandons ACCESS reporting, normally used for a directory. Any prior reports logged for the dir are not affected. @param path UTF-8 string with path of dir whose watch is to be altered @return */ void e2_fs_FAM_less_monitor_dir (gchar *path) { /* ATM there is no change of ACCESS reporting #ifdef EXTRA_MESSAGES printd (DEBUG, "Cancel ACCESS event-reports for %s", path); #endif _e2_fs_FAM_kqueue_remonitor_item (path, KQDIR_SHORT_FLAGS); */ } /** @brief resume normal scope of event-monitoring for @a path This is needed after e2_fs_FAM_less_monitor_dir(), normally used for a directory. Essentially this resumes ACCESS reporting. Any prior event-reports for the dir are not affected. @param path UTF-8 string with path of dir whose watch is to be altered @return */ void e2_fs_FAM_more_monitor_dir (gchar *path) { /* ATM there is no change of ACCESS reporting #ifdef EXTRA_MESSAGES printd (DEBUG, "Resume ACCESS event-reports for %s", path); #endif _e2_fs_FAM_kqueue_remonitor_item (path, KQDIR_FLAGS); */ } /** @brief begin or resume monitoring of config file @return TRUE if the connection was successfully established */ gboolean e2_fs_FAM_monitor_config (void) { if (config_file != NULL) g_free (config_file); config_file = g_build_filename (e2_cl_options.config_dir, default_config_file, NULL); gint new = _e2_fs_FAM_kqueue_monitor_item (config_file, KQCFG_FLAGS); //atomic access cuz' other threads may want to use FAMreq g_atomic_int_set (&app.FAMreq, new); return (new > 0); } /** @brief finish or suspend monitoring of config file @return TRUE if the connection was successfully removed */ gboolean e2_fs_FAM_cancel_monitor_config (void) { gboolean result; gint old = g_atomic_int_get (&app.FAMreq); if (old > 0) { result = _e2_fs_FAM_kqueue_demonitor_item (config_file, old); if (result) { g_atomic_int_set (&app.FAMreq, WD_UNUSED); /*#ifdef DEBUG_MESSAGES printd (DEBUG, "FAM cancel for %s succeeded", config_file); #endif */ } /*#ifdef DEBUG_MESSAGES else printd (WARN, "FAM cancel for %s failed", config_file); #endif */ } else result = FALSE; return result; } #ifdef EXTRA_MESSAGES # undef EXTRA_MESSAGES #endif #endif //def E2_FAM_KQUEUE