Import Upstream version 2.72.4
This commit is contained in:
commit
4ef3ff9793
2003 changed files with 1332420 additions and 0 deletions
536
gio/kqueue/dep-list.c
Normal file
536
gio/kqueue/dep-list.c
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
/*******************************************************************************
|
||||
Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <stdlib.h> /* calloc */
|
||||
#include <stdio.h> /* printf */
|
||||
#include <dirent.h> /* opendir, readdir, closedir */
|
||||
#include <string.h> /* strcmp */
|
||||
#include <assert.h>
|
||||
|
||||
#include "dep-list.h"
|
||||
|
||||
static gboolean kdl_debug_enabled = FALSE;
|
||||
#define perror_msg if (kdl_debug_enabled) g_warning
|
||||
|
||||
|
||||
/**
|
||||
* Print a list to stdout.
|
||||
*
|
||||
* @param[in] dl A pointer to a list.
|
||||
**/
|
||||
void
|
||||
dl_print (const dep_list *dl)
|
||||
{
|
||||
while (dl != NULL) {
|
||||
printf ("%lld:%s ", (long long int) dl->inode, dl->path);
|
||||
dl = dl->next;
|
||||
}
|
||||
printf ("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new list item.
|
||||
*
|
||||
* Create a new list item and initialize its fields.
|
||||
*
|
||||
* @param[in] path A name of a file (the string is not copied!).
|
||||
* @param[in] inode A file's inode number.
|
||||
* @return A pointer to a new item or NULL in the case of error.
|
||||
**/
|
||||
dep_list* dl_create (char *path, ino_t inode)
|
||||
{
|
||||
dep_list *dl = calloc (1, sizeof (dep_list));
|
||||
if (dl == NULL) {
|
||||
perror_msg ("Failed to create a new dep-list item");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dl->path = path;
|
||||
dl->inode = inode;
|
||||
return dl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a shallow copy of a list.
|
||||
*
|
||||
* A shallow copy is a copy of a structure, but not the copy of the
|
||||
* contents. All data pointers ('path' in our case) of a list and its
|
||||
* shallow copy will point to the same memory.
|
||||
*
|
||||
* @param[in] dl A pointer to list to make a copy. May be NULL.
|
||||
* @return A shallow copy of the list.
|
||||
**/
|
||||
dep_list*
|
||||
dl_shallow_copy (const dep_list *dl)
|
||||
{
|
||||
dep_list *head;
|
||||
dep_list *cp;
|
||||
const dep_list *it;
|
||||
|
||||
if (dl == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
head = calloc (1, sizeof (dep_list));
|
||||
if (head == NULL) {
|
||||
perror_msg ("Failed to allocate head during shallow copy");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cp = head;
|
||||
it = dl;
|
||||
|
||||
while (it != NULL) {
|
||||
cp->path = it->path;
|
||||
cp->inode = it->inode;
|
||||
if (it->next) {
|
||||
cp->next = calloc (1, sizeof (dep_list));
|
||||
if (cp->next == NULL) {
|
||||
perror_msg ("Failed to allocate a new element during shallow copy");
|
||||
dl_shallow_free (head);
|
||||
return NULL;
|
||||
}
|
||||
cp = cp->next;
|
||||
}
|
||||
it = it->next;
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the memory allocated for shallow copy.
|
||||
*
|
||||
* This function will free the memory used by a list structure, but
|
||||
* the list data will remain in the heap.
|
||||
*
|
||||
* @param[in] dl A pointer to a list. May be NULL.
|
||||
**/
|
||||
void
|
||||
dl_shallow_free (dep_list *dl)
|
||||
{
|
||||
while (dl != NULL) {
|
||||
dep_list *ptr = dl;
|
||||
dl = dl->next;
|
||||
free (ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the memory allocated for a list.
|
||||
*
|
||||
* This function will free all the memory used by a list: both
|
||||
* list structure and the list data.
|
||||
*
|
||||
* @param[in] dl A pointer to a list. May be NULL.
|
||||
**/
|
||||
void
|
||||
dl_free (dep_list *dl)
|
||||
{
|
||||
while (dl != NULL) {
|
||||
dep_list *ptr = dl;
|
||||
dl = dl->next;
|
||||
|
||||
free (ptr->path);
|
||||
free (ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a directory listing and return it as a list.
|
||||
*
|
||||
* @param[in] path A path to a directory.
|
||||
* @return A pointer to a list. May return NULL, check errno in this case.
|
||||
**/
|
||||
dep_list*
|
||||
dl_listing (const char *path)
|
||||
{
|
||||
dep_list *head = NULL;
|
||||
dep_list *prev = NULL;
|
||||
DIR *dir;
|
||||
|
||||
assert (path != NULL);
|
||||
|
||||
dir = opendir (path);
|
||||
if (dir != NULL) {
|
||||
struct dirent *ent;
|
||||
|
||||
while ((ent = readdir (dir)) != NULL) {
|
||||
dep_list *iter;
|
||||
|
||||
if (!strcmp (ent->d_name, ".") || !strcmp (ent->d_name, "..")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (head == NULL) {
|
||||
head = calloc (1, sizeof (dep_list));
|
||||
if (head == NULL) {
|
||||
perror_msg ("Failed to allocate head during listing");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
iter = (prev == NULL) ? head : calloc (1, sizeof (dep_list));
|
||||
if (iter == NULL) {
|
||||
perror_msg ("Failed to allocate a new element during listing");
|
||||
goto error;
|
||||
}
|
||||
|
||||
iter->path = strdup (ent->d_name);
|
||||
if (iter->path == NULL) {
|
||||
perror_msg ("Failed to copy a string during listing");
|
||||
goto error;
|
||||
}
|
||||
|
||||
iter->inode = ent->d_ino;
|
||||
iter->next = NULL;
|
||||
if (prev) {
|
||||
prev->next = iter;
|
||||
}
|
||||
prev = iter;
|
||||
}
|
||||
|
||||
closedir (dir);
|
||||
}
|
||||
return head;
|
||||
|
||||
error:
|
||||
if (dir != NULL) {
|
||||
closedir (dir);
|
||||
}
|
||||
dl_free (head);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a diff on lists.
|
||||
*
|
||||
* This function performs something like a set intersection. The same items
|
||||
* will be removed from the both lists. Items are comapred by a filename.
|
||||
*
|
||||
* @param[in,out] before A pointer to a pointer to a list. Will contain items
|
||||
* which were not found in the 'after' list.
|
||||
* @param[in,out] after A pointer to a pointer to a list. Will contain items
|
||||
* which were not found in the 'before' list.
|
||||
**/
|
||||
void
|
||||
dl_diff (dep_list **before, dep_list **after)
|
||||
{
|
||||
dep_list *before_iter;
|
||||
dep_list *before_prev;
|
||||
|
||||
assert (before != NULL);
|
||||
assert (after != NULL);
|
||||
|
||||
if (*before == NULL || *after == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
before_iter = *before;
|
||||
before_prev = NULL;
|
||||
|
||||
while (before_iter != NULL) {
|
||||
dep_list *after_iter = *after;
|
||||
dep_list *after_prev = NULL;
|
||||
dep_list *oldptr;
|
||||
|
||||
int matched = 0;
|
||||
while (after_iter != NULL) {
|
||||
if (strcmp (before_iter->path, after_iter->path) == 0) {
|
||||
matched = 1;
|
||||
/* removing the entry from the both lists */
|
||||
if (before_prev) {
|
||||
before_prev->next = before_iter->next;
|
||||
} else {
|
||||
*before = before_iter->next;
|
||||
}
|
||||
|
||||
if (after_prev) {
|
||||
after_prev->next = after_iter->next;
|
||||
} else {
|
||||
*after = after_iter->next;
|
||||
}
|
||||
free (after_iter);
|
||||
break;
|
||||
}
|
||||
after_prev = after_iter;
|
||||
after_iter = after_iter->next;
|
||||
}
|
||||
|
||||
oldptr = before_iter;
|
||||
before_iter = before_iter->next;
|
||||
if (matched == 0) {
|
||||
before_prev = oldptr;
|
||||
} else {
|
||||
free (oldptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Traverses two lists. Compares items with a supplied expression
|
||||
* and performs the passed code on a match. Removes the matched entries
|
||||
* from the both lists.
|
||||
**/
|
||||
#define EXCLUDE_SIMILAR(removed_list, added_list, match_expr, matched_code) \
|
||||
G_STMT_START { \
|
||||
dep_list *removed_list##_iter; \
|
||||
dep_list *removed_list##_prev; \
|
||||
int productive = 0; \
|
||||
\
|
||||
assert (removed_list != NULL); \
|
||||
assert (added_list != NULL); \
|
||||
\
|
||||
removed_list##_iter = *removed_list; \
|
||||
removed_list##_prev = NULL; \
|
||||
\
|
||||
while (removed_list##_iter != NULL) { \
|
||||
dep_list *added_list##_iter = *added_list; \
|
||||
dep_list *added_list##_prev = NULL; \
|
||||
dep_list *oldptr; \
|
||||
\
|
||||
int matched = 0; \
|
||||
while (added_list##_iter != NULL) { \
|
||||
if (match_expr) { \
|
||||
matched = 1; \
|
||||
++productive; \
|
||||
matched_code; \
|
||||
\
|
||||
if (removed_list##_prev) { \
|
||||
removed_list##_prev->next = removed_list##_iter->next; \
|
||||
} else { \
|
||||
*removed_list = removed_list##_iter->next; \
|
||||
} \
|
||||
if (added_list##_prev) { \
|
||||
added_list##_prev->next = added_list##_iter->next; \
|
||||
} else { \
|
||||
*added_list = added_list##_iter->next; \
|
||||
} \
|
||||
free (added_list##_iter); \
|
||||
break; \
|
||||
} \
|
||||
added_list##_iter = added_list##_iter->next; \
|
||||
} \
|
||||
oldptr = removed_list##_iter; \
|
||||
removed_list##_iter = removed_list##_iter->next; \
|
||||
if (matched == 0) { \
|
||||
removed_list##_prev = oldptr; \
|
||||
} else { \
|
||||
free (oldptr); \
|
||||
} \
|
||||
} \
|
||||
return (productive > 0); \
|
||||
} G_STMT_END
|
||||
|
||||
|
||||
#define cb_invoke(cbs, name, udata, ...) \
|
||||
do { \
|
||||
if (cbs->name) { \
|
||||
(cbs->name) (udata, ## __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Detect and notify about moves in the watched directory.
|
||||
*
|
||||
* A move is what happens when you rename a file in a directory, and
|
||||
* a new name is unique, i.e. you didn't overwrite any existing files
|
||||
* with this one.
|
||||
*
|
||||
* @param[in,out] removed A list of the removed files in the directory.
|
||||
* @param[in,out] added A list of the added files of the directory.
|
||||
* @param[in] cbs A pointer to #traverse_cbs, a user-defined set of
|
||||
* traverse callbacks.
|
||||
* @param[in] udata A pointer to the user-defined data.
|
||||
* @return 0 if no files were renamed, >0 otherwise.
|
||||
**/
|
||||
static int
|
||||
dl_detect_moves (dep_list **removed,
|
||||
dep_list **added,
|
||||
const traverse_cbs *cbs,
|
||||
void *udata)
|
||||
{
|
||||
assert (cbs != NULL);
|
||||
|
||||
EXCLUDE_SIMILAR
|
||||
(removed, added,
|
||||
(removed_iter->inode == added_iter->inode),
|
||||
{
|
||||
cb_invoke (cbs, moved, udata,
|
||||
removed_iter->path, removed_iter->inode,
|
||||
added_iter->path, added_iter->inode);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect and notify about replacements in the watched directory.
|
||||
*
|
||||
* Consider you are watching a directory foo with the following files
|
||||
* insinde:
|
||||
*
|
||||
* foo/bar
|
||||
* foo/baz
|
||||
*
|
||||
* A replacement in a watched directory is what happens when you invoke
|
||||
*
|
||||
* mv /foo/bar /foo/bar
|
||||
*
|
||||
* i.e. when you replace a file in a watched directory with another file
|
||||
* from the same directory.
|
||||
*
|
||||
* @param[in,out] removed A list of the removed files in the directory.
|
||||
* @param[in,out] current A list with the current contents of the directory.
|
||||
* @param[in] cbs A pointer to #traverse_cbs, a user-defined set of
|
||||
* traverse callbacks.
|
||||
* @param[in] udata A pointer to the user-defined data.
|
||||
* @return 0 if no files were renamed, >0 otherwise.
|
||||
**/
|
||||
static int
|
||||
dl_detect_replacements (dep_list **removed,
|
||||
dep_list **current,
|
||||
const traverse_cbs *cbs,
|
||||
void *udata)
|
||||
{
|
||||
assert (cbs != NULL);
|
||||
|
||||
EXCLUDE_SIMILAR
|
||||
(removed, current,
|
||||
(removed_iter->inode == current_iter->inode),
|
||||
{
|
||||
cb_invoke (cbs, replaced, udata,
|
||||
removed_iter->path, removed_iter->inode,
|
||||
current_iter->path, current_iter->inode);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect and notify about overwrites in the watched directory.
|
||||
*
|
||||
* Consider you are watching a directory foo with a file inside:
|
||||
*
|
||||
* foo/bar
|
||||
*
|
||||
* And you also have a directory tmp with a file 1:
|
||||
*
|
||||
* tmp/1
|
||||
*
|
||||
* You do not watching directory tmp.
|
||||
*
|
||||
* An overwrite in a watched directory is what happens when you invoke
|
||||
*
|
||||
* mv /tmp/1 /foo/bar
|
||||
*
|
||||
* i.e. when you overwrite a file in a watched directory with another file
|
||||
* from the another directory.
|
||||
*
|
||||
* @param[in,out] previous A list with the previous contents of the directory.
|
||||
* @param[in,out] current A list with the current contents of the directory.
|
||||
* @param[in] cbs A pointer to #traverse_cbs, a user-defined set of
|
||||
* traverse callbacks.
|
||||
* @param[in] udata A pointer to the user-defined data.
|
||||
* @return 0 if no files were renamed, >0 otherwise.
|
||||
**/
|
||||
static int
|
||||
dl_detect_overwrites (dep_list **previous,
|
||||
dep_list **current,
|
||||
const traverse_cbs *cbs,
|
||||
void *udata)
|
||||
{
|
||||
assert (cbs != NULL);
|
||||
|
||||
EXCLUDE_SIMILAR
|
||||
(previous, current,
|
||||
(strcmp (previous_iter->path, current_iter->path) == 0
|
||||
&& previous_iter->inode != current_iter->inode),
|
||||
{
|
||||
cb_invoke (cbs, overwritten, udata, current_iter->path, current_iter->inode);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Traverse a list and invoke a callback for each item.
|
||||
*
|
||||
* @param[in] list A #dep_list.
|
||||
* @param[in] cb A #single_entry_cb callback function.
|
||||
* @param[in] udata A pointer to the user-defined data.
|
||||
**/
|
||||
static void
|
||||
dl_emit_single_cb_on (dep_list *list,
|
||||
single_entry_cb cb,
|
||||
void *udata)
|
||||
{
|
||||
while (cb && list != NULL) {
|
||||
(cb) (udata, list->path, list->inode);
|
||||
list = list->next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recognize all the changes in the directory, invoke the appropriate callbacks.
|
||||
*
|
||||
* This is the core function of directory diffing submodule.
|
||||
*
|
||||
* @param[in] before The previous contents of the directory.
|
||||
* @param[in] after The current contents of the directory.
|
||||
* @param[in] cbs A pointer to user callbacks (#traverse_callbacks).
|
||||
* @param[in] udata A pointer to user data.
|
||||
**/
|
||||
void
|
||||
dl_calculate (dep_list *before,
|
||||
dep_list *after,
|
||||
const traverse_cbs *cbs,
|
||||
void *udata)
|
||||
{
|
||||
int need_update = 0;
|
||||
dep_list *was = dl_shallow_copy (before);
|
||||
dep_list *pre = dl_shallow_copy (before);
|
||||
dep_list *now = dl_shallow_copy (after);
|
||||
dep_list *lst = dl_shallow_copy (after);
|
||||
|
||||
assert (cbs != NULL);
|
||||
|
||||
dl_diff (&was, &now);
|
||||
|
||||
need_update += dl_detect_moves (&was, &now, cbs, udata);
|
||||
need_update += dl_detect_replacements (&was, &lst, cbs, udata);
|
||||
dl_detect_overwrites (&pre, &lst, cbs, udata);
|
||||
|
||||
if (need_update) {
|
||||
cb_invoke (cbs, names_updated, udata);
|
||||
}
|
||||
|
||||
dl_emit_single_cb_on (was, cbs->removed, udata);
|
||||
dl_emit_single_cb_on (now, cbs->added, udata);
|
||||
|
||||
cb_invoke (cbs, many_added, udata, now);
|
||||
cb_invoke (cbs, many_removed, udata, was);
|
||||
|
||||
dl_shallow_free (lst);
|
||||
dl_shallow_free (now);
|
||||
dl_shallow_free (pre);
|
||||
dl_shallow_free (was);
|
||||
}
|
||||
69
gio/kqueue/dep-list.h
Normal file
69
gio/kqueue/dep-list.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/*******************************************************************************
|
||||
Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef __DEP_LIST_H__
|
||||
#define __DEP_LIST_H__
|
||||
|
||||
#include <sys/types.h> /* ino_t */
|
||||
|
||||
typedef struct dep_list {
|
||||
struct dep_list *next;
|
||||
|
||||
char *path;
|
||||
ino_t inode;
|
||||
} dep_list;
|
||||
|
||||
typedef void (* no_entry_cb) (void *udata);
|
||||
typedef void (* single_entry_cb) (void *udata, const char *path, ino_t inode);
|
||||
typedef void (* dual_entry_cb) (void *udata,
|
||||
const char *from_path, ino_t from_inode,
|
||||
const char *to_path, ino_t to_inode);
|
||||
typedef void (* list_cb) (void *udata, const dep_list *list);
|
||||
|
||||
|
||||
typedef struct traverse_cbs {
|
||||
single_entry_cb added;
|
||||
single_entry_cb removed;
|
||||
dual_entry_cb replaced;
|
||||
single_entry_cb overwritten;
|
||||
dual_entry_cb moved;
|
||||
list_cb many_added;
|
||||
list_cb many_removed;
|
||||
no_entry_cb names_updated;
|
||||
} traverse_cbs;
|
||||
|
||||
dep_list* dl_create (char *path, ino_t inode);
|
||||
void dl_print (const dep_list *dl);
|
||||
dep_list* dl_shallow_copy (const dep_list *dl);
|
||||
void dl_shallow_free (dep_list *dl);
|
||||
void dl_free (dep_list *dl);
|
||||
dep_list* dl_listing (const char *path);
|
||||
void dl_diff (dep_list **before, dep_list **after);
|
||||
|
||||
void
|
||||
dl_calculate (dep_list *before,
|
||||
dep_list *after,
|
||||
const traverse_cbs *cbs,
|
||||
void *udata);
|
||||
|
||||
|
||||
#endif /* __DEP_LIST_H__ */
|
||||
617
gio/kqueue/gkqueuefilemonitor.c
Normal file
617
gio/kqueue/gkqueuefilemonitor.c
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
/*******************************************************************************
|
||||
Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <glib-object.h>
|
||||
#include <glib/gfileutils.h>
|
||||
#include <gio/gfilemonitor.h>
|
||||
#include <gio/glocalfilemonitor.h>
|
||||
#include <gio/giomodule.h>
|
||||
#include <gio/gpollfilemonitor.h>
|
||||
#include <gio/gfile.h>
|
||||
#include <glib-unix.h>
|
||||
#include "glib-private.h"
|
||||
|
||||
#include "kqueue-helper.h"
|
||||
#include "dep-list.h"
|
||||
|
||||
G_LOCK_DEFINE_STATIC (kq_lock);
|
||||
static GSource *kq_source;
|
||||
static int kq_queue = -1;
|
||||
|
||||
#define G_TYPE_KQUEUE_FILE_MONITOR (g_kqueue_file_monitor_get_type ())
|
||||
#define G_KQUEUE_FILE_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
|
||||
G_TYPE_KQUEUE_FILE_MONITOR, GKqueueFileMonitor))
|
||||
|
||||
/* C11 allows type redefinition, but GLib is configured to use C89, which causes
|
||||
* clang to show warnings when we use a C11 feature. Since the C89 requirement
|
||||
* is mostly used to support MSVC, we simply ignore the warning here because
|
||||
* this file is never going to be useful on Windows. */
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wtypedef-redefinition"
|
||||
#endif
|
||||
|
||||
typedef GLocalFileMonitorClass GKqueueFileMonitorClass;
|
||||
|
||||
/* When the file we are monitoring is a directory, sub_dir is subscribed to the
|
||||
* directory itself and sub_file is NULL.
|
||||
*
|
||||
* When the file we are monitoring is a regular file, sub_dir is subscribed to
|
||||
* the directory containing the file and sub_file is subscribed to the file
|
||||
* being monitored. We have to monitor both because it is possible that the
|
||||
* file chosen for monitoring doesn't exist when the file monitor is started.
|
||||
* We monitor on its parent in order to get notification when it is created.
|
||||
*
|
||||
* To distinguish between a directory monitor and a regular file monitor, check
|
||||
* whether sub_file is NULL. */
|
||||
struct _GKqueueFileMonitor
|
||||
{
|
||||
GLocalFileMonitor parent_instance;
|
||||
|
||||
kqueue_sub *sub_dir;
|
||||
kqueue_sub *sub_file;
|
||||
#ifndef O_EVTONLY
|
||||
GFileMonitor *fallback;
|
||||
GFile *fbfile;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
GType g_kqueue_file_monitor_get_type (void);
|
||||
G_DEFINE_TYPE_WITH_CODE (GKqueueFileMonitor, g_kqueue_file_monitor, G_TYPE_LOCAL_FILE_MONITOR,
|
||||
g_io_extension_point_implement (G_LOCAL_FILE_MONITOR_EXTENSION_POINT_NAME,
|
||||
g_define_type_id,
|
||||
"kqueue",
|
||||
20))
|
||||
|
||||
#ifndef O_EVTONLY
|
||||
#define O_KQFLAG O_RDONLY
|
||||
#else
|
||||
#define O_KQFLAG O_EVTONLY
|
||||
#endif
|
||||
|
||||
static inline unsigned int
|
||||
note_all (void)
|
||||
{
|
||||
unsigned int notes = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE;
|
||||
#ifdef NOTE_TRUNCATE
|
||||
notes |= NOTE_TRUNCATE;
|
||||
#endif
|
||||
#ifdef NOTE_CLOSE_WRITE
|
||||
notes |= NOTE_CLOSE_WRITE;
|
||||
#endif
|
||||
return notes;
|
||||
}
|
||||
|
||||
static gboolean g_kqueue_file_monitor_cancel (GFileMonitor* monitor);
|
||||
static gboolean g_kqueue_file_monitor_is_supported (void);
|
||||
|
||||
static kqueue_sub *_kqsub_new (gchar *, gchar *, GKqueueFileMonitor *, GFileMonitorSource *);
|
||||
static void _kqsub_free (kqueue_sub *);
|
||||
static void _kqsub_cancel (kqueue_sub *);
|
||||
|
||||
|
||||
#ifndef O_EVTONLY
|
||||
static void
|
||||
_fallback_callback (GFileMonitor *unused,
|
||||
GFile *first,
|
||||
GFile *second,
|
||||
GFileMonitorEvent event,
|
||||
gpointer udata)
|
||||
{
|
||||
GKqueueFileMonitor *kq_mon = G_KQUEUE_FILE_MONITOR (udata);
|
||||
|
||||
g_file_monitor_emit_event (G_FILE_MONITOR (kq_mon), first, second, event);
|
||||
}
|
||||
|
||||
/*
|
||||
* _ke_is_excluded:
|
||||
* @full_path - a path to file to check.
|
||||
*
|
||||
* Returns: TRUE if the file should be excluded from the kqueue-powered
|
||||
* monitoring, FALSE otherwise.
|
||||
**/
|
||||
static gboolean
|
||||
_ke_is_excluded (const char *full_path)
|
||||
{
|
||||
GFile *f = NULL;
|
||||
GMount *mount = NULL;
|
||||
|
||||
f = g_file_new_for_path (full_path);
|
||||
|
||||
if (f != NULL) {
|
||||
mount = g_file_find_enclosing_mount (f, NULL, NULL);
|
||||
g_object_unref (f);
|
||||
}
|
||||
|
||||
if (mount != NULL && (g_str_has_prefix (full_path, "/media/") || g_str_has_prefix (full_path, "/run/media/")))
|
||||
{
|
||||
g_warning ("Excluding %s from kernel notification, falling back to poll", full_path);
|
||||
if (mount)
|
||||
g_object_unref (mount);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
#endif /* !O_EVTONLY */
|
||||
|
||||
static void
|
||||
g_kqueue_file_monitor_finalize (GObject *object)
|
||||
{
|
||||
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (object);
|
||||
|
||||
if (kqueue_monitor->sub_dir)
|
||||
{
|
||||
_kqsub_cancel (kqueue_monitor->sub_dir);
|
||||
_kqsub_free (kqueue_monitor->sub_dir);
|
||||
kqueue_monitor->sub_dir = NULL;
|
||||
}
|
||||
|
||||
if (kqueue_monitor->sub_file)
|
||||
{
|
||||
_kqsub_cancel (kqueue_monitor->sub_file);
|
||||
_kqsub_free (kqueue_monitor->sub_file);
|
||||
kqueue_monitor->sub_file = NULL;
|
||||
}
|
||||
|
||||
#ifndef O_EVTONLY
|
||||
if (kqueue_monitor->fallback)
|
||||
g_object_unref (kqueue_monitor->fallback);
|
||||
|
||||
if (kqueue_monitor->fbfile)
|
||||
g_object_unref (kqueue_monitor->fbfile);
|
||||
#endif
|
||||
|
||||
if (G_OBJECT_CLASS (g_kqueue_file_monitor_parent_class)->finalize)
|
||||
(*G_OBJECT_CLASS (g_kqueue_file_monitor_parent_class)->finalize) (object);
|
||||
}
|
||||
|
||||
static void
|
||||
g_kqueue_file_monitor_start (GLocalFileMonitor *local_monitor,
|
||||
const gchar *dirname,
|
||||
const gchar *basename,
|
||||
const gchar *filename,
|
||||
GFileMonitorSource *source)
|
||||
{
|
||||
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (local_monitor);
|
||||
kqueue_sub *sub_dir = NULL, *sub_file = NULL;
|
||||
gchar *path_dir, *path_file, *file_basename;
|
||||
|
||||
/* There are three possible cases here:
|
||||
*
|
||||
* 1. Directory: dirname != NULL, basename == NULL, filename == NULL
|
||||
* 2. Regular file: dirname != NULL, basename != NULL, filename == NULL
|
||||
* 3. Hard links: dirname == NULL, basename == NULL, filename != NULL
|
||||
*
|
||||
* Note that we don't distinguish between case 2 and 3. Kqueue monitors
|
||||
* files based on file descriptors, so we always receive events come from
|
||||
* hard links.
|
||||
*/
|
||||
if (filename != NULL)
|
||||
{
|
||||
path_dir = g_path_get_dirname (filename);
|
||||
path_file = g_strdup (filename);
|
||||
file_basename = g_path_get_basename (filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
path_dir = g_strdup (dirname);
|
||||
if (basename != NULL)
|
||||
{
|
||||
path_file = g_build_filename (dirname, basename, NULL);
|
||||
file_basename = g_strdup (basename);
|
||||
}
|
||||
else
|
||||
{
|
||||
path_file = NULL;
|
||||
file_basename = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef O_EVTONLY
|
||||
if (_ke_is_excluded (path_dir))
|
||||
{
|
||||
GFile *file;
|
||||
if (path_file != NULL)
|
||||
file = g_file_new_for_path (path_file);
|
||||
else
|
||||
file = g_file_new_for_path (path_dir);
|
||||
g_free (path_dir);
|
||||
g_free (path_file);
|
||||
g_free (file_basename);
|
||||
kqueue_monitor->fbfile = file;
|
||||
kqueue_monitor->fallback = _g_poll_file_monitor_new (file);
|
||||
g_signal_connect (kqueue_monitor->fallback, "changed",
|
||||
G_CALLBACK (_fallback_callback), kqueue_monitor);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* For a directory monitor, create a subscription object anyway.
|
||||
* It will be used for directory diff calculation routines.
|
||||
* Wait, directory diff in a GKqueueFileMonitor?
|
||||
* Yes, it is. When a file monitor is started on a non-existent
|
||||
* file, GIO uses a GKqueueFileMonitor object for that. If a directory
|
||||
* will be created under that path, GKqueueFileMonitor will have to
|
||||
* handle the directory notifications. */
|
||||
sub_dir = _kqsub_new (g_steal_pointer (&path_dir), NULL,
|
||||
kqueue_monitor, source);
|
||||
if (!_kqsub_start_watching (sub_dir))
|
||||
_km_add_missing (sub_dir);
|
||||
|
||||
/* Unlike GInotifyFileMonitor, which always uses a directory monitor
|
||||
* regardless of the type of the file being monitored, kqueue doesn't
|
||||
* give us events generated by files under it when we are monitoring
|
||||
* a directory. We have to monitor the file itself to know changes which
|
||||
* was made to the file itself. */
|
||||
if (path_file != NULL)
|
||||
{
|
||||
sub_file = _kqsub_new (g_steal_pointer (&path_file),
|
||||
g_steal_pointer (&file_basename),
|
||||
kqueue_monitor, source);
|
||||
if (!_kqsub_start_watching (sub_file))
|
||||
_km_add_missing (sub_file);
|
||||
}
|
||||
|
||||
kqueue_monitor->sub_dir = sub_dir;
|
||||
kqueue_monitor->sub_file = sub_file;
|
||||
g_clear_pointer (&path_dir, g_free);
|
||||
g_clear_pointer (&path_file, g_free);
|
||||
g_clear_pointer (&file_basename, g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
g_kqueue_file_monitor_class_init (GKqueueFileMonitorClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GFileMonitorClass *file_monitor_class = G_FILE_MONITOR_CLASS (klass);
|
||||
GLocalFileMonitorClass *local_file_monitor_class = G_LOCAL_FILE_MONITOR_CLASS (klass);
|
||||
|
||||
gobject_class->finalize = g_kqueue_file_monitor_finalize;
|
||||
file_monitor_class->cancel = g_kqueue_file_monitor_cancel;
|
||||
|
||||
local_file_monitor_class->is_supported = g_kqueue_file_monitor_is_supported;
|
||||
local_file_monitor_class->start = g_kqueue_file_monitor_start;
|
||||
local_file_monitor_class->mount_notify = TRUE; /* TODO: ??? */
|
||||
}
|
||||
|
||||
static void
|
||||
g_kqueue_file_monitor_init (GKqueueFileMonitor *monitor)
|
||||
{
|
||||
}
|
||||
|
||||
static gboolean
|
||||
g_kqueue_file_monitor_callback (gint fd, GIOCondition condition, gpointer user_data)
|
||||
{
|
||||
gint64 now = g_source_get_time (kq_source);
|
||||
kqueue_sub *sub;
|
||||
GFileMonitorSource *source;
|
||||
struct kevent ev;
|
||||
struct timespec ts;
|
||||
|
||||
memset (&ts, 0, sizeof(ts));
|
||||
|
||||
/* We must hold the global lock before accessing any kqueue_sub because it is
|
||||
* possible for other threads to call g_kqueue_file_monitor_cancel, which may
|
||||
* free the kqueue_sub struct we are accessing. */
|
||||
G_LOCK (kq_lock);
|
||||
|
||||
while (kevent(fd, NULL, 0, &ev, 1, &ts) > 0)
|
||||
{
|
||||
if (ev.filter != EVFILT_VNODE || ev.udata == NULL)
|
||||
continue;
|
||||
|
||||
sub = ev.udata;
|
||||
source = sub->source;
|
||||
|
||||
/* When we are monitoring a regular file which already exists, ignore
|
||||
* events generated by its parent directory. This has to be the first
|
||||
* check to prevent the following code to emit useless events */
|
||||
if (sub->is_dir && sub->mon->sub_file != NULL && sub->mon->sub_file->fd != -1)
|
||||
continue;
|
||||
|
||||
if (ev.flags & EV_ERROR)
|
||||
ev.fflags = NOTE_REVOKE;
|
||||
|
||||
if (sub->is_dir && ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
|
||||
{
|
||||
/* If we are monitoring on a non-existent regular file, trigger the
|
||||
* rescan of missing files immediately so we don't have to wait for
|
||||
* 4 seconds for discovering missing files. We pass the sub_file
|
||||
* corresponding to the GKqueueFileMonitor to 'check_this_sub_only'
|
||||
* argument to prevent _km_scan_missing from emitting 'CREATED'
|
||||
* events because _kh_dir_diff will do it for us. */
|
||||
if (sub->mon->sub_file != NULL && sub->mon->sub_file->fd == -1)
|
||||
_km_scan_missing (sub->mon->sub_file);
|
||||
|
||||
/* If we are monitoring a regular file, don't emit 'DELETED' events
|
||||
* from the directory monitor because it will be emitted from the
|
||||
* file itself when a NOTE_DELETE is reported on sub_file. */
|
||||
_kh_dir_diff (sub, sub->mon->sub_file == NULL);
|
||||
|
||||
#ifdef NOTE_TRUNCATE
|
||||
ev.fflags &= ~(NOTE_WRITE | NOTE_EXTEND | NOTE_TRUNCATE);
|
||||
#else
|
||||
ev.fflags &= ~(NOTE_WRITE | NOTE_EXTEND);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Here starts the long section of mapping kqueue events to
|
||||
* GFileMonitorEvent. Since kqueue can return multiple events in a
|
||||
* single kevent struct, we must use 'if' instead of 'else if'. */
|
||||
if (ev.fflags & NOTE_DELETE)
|
||||
{
|
||||
struct stat st;
|
||||
if (fstat (sub->fd, &st) < 0)
|
||||
st.st_nlink = 0;
|
||||
|
||||
g_file_monitor_source_handle_event (source,
|
||||
G_FILE_MONITOR_EVENT_DELETED,
|
||||
sub->basename, NULL, NULL, now);
|
||||
|
||||
/* If the last reference to the file was removed, delete the
|
||||
* subscription from kqueue and add it to the missing list.
|
||||
* If you are monitoring a file which has hard link count higher
|
||||
* than 1, it is possible for the same file to emit 'DELETED'
|
||||
* events multiple times. */
|
||||
if (st.st_nlink == 0)
|
||||
{
|
||||
_kqsub_cancel (sub);
|
||||
_km_add_missing (sub);
|
||||
}
|
||||
}
|
||||
if (ev.fflags & NOTE_REVOKE)
|
||||
{
|
||||
g_file_monitor_source_handle_event (source,
|
||||
G_FILE_MONITOR_EVENT_UNMOUNTED,
|
||||
sub->basename, NULL, NULL, now);
|
||||
_kqsub_cancel (sub);
|
||||
_km_add_missing (sub);
|
||||
}
|
||||
if (ev.fflags & NOTE_ATTRIB)
|
||||
{
|
||||
g_file_monitor_source_handle_event (source,
|
||||
G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED,
|
||||
sub->basename, NULL, NULL, now);
|
||||
}
|
||||
#ifdef NOTE_TRUNCATE
|
||||
if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND | NOTE_TRUNCATE))
|
||||
#else
|
||||
if (ev.fflags & (NOTE_WRITE | NOTE_EXTEND))
|
||||
#endif
|
||||
{
|
||||
g_file_monitor_source_handle_event (source,
|
||||
G_FILE_MONITOR_EVENT_CHANGED,
|
||||
sub->basename, NULL, NULL, now);
|
||||
}
|
||||
if (ev.fflags & NOTE_RENAME)
|
||||
{
|
||||
/* Since there’s apparently no way to get the new name of the
|
||||
* file out of kqueue(), all we can do is say that this one has
|
||||
* been deleted. */
|
||||
g_file_monitor_source_handle_event (source,
|
||||
G_FILE_MONITOR_EVENT_DELETED,
|
||||
sub->basename, NULL, NULL, now);
|
||||
}
|
||||
#ifdef NOTE_CLOSE_WRITE
|
||||
if (ev.fflags & NOTE_CLOSE_WRITE)
|
||||
{
|
||||
g_file_monitor_source_handle_event (source,
|
||||
G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
|
||||
sub->basename, NULL, NULL, now);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Handle the case when a file is created again shortly after it was
|
||||
* deleted. It has to be the last check because 'DELETED' must happen
|
||||
* before 'CREATED'. */
|
||||
if (ev.fflags & (NOTE_DELETE | NOTE_REVOKE))
|
||||
_km_scan_missing (NULL);
|
||||
}
|
||||
|
||||
G_UNLOCK (kq_lock);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
g_kqueue_file_monitor_is_supported (void)
|
||||
{
|
||||
int errsv;
|
||||
|
||||
G_LOCK (kq_lock);
|
||||
|
||||
if (kq_queue == -1)
|
||||
{
|
||||
kq_queue = kqueue ();
|
||||
errsv = errno;
|
||||
|
||||
if (kq_queue == -1)
|
||||
{
|
||||
g_warning ("Unable to create a kqueue: %s", g_strerror (errsv));
|
||||
G_UNLOCK (kq_lock);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
kq_source = g_unix_fd_source_new (kq_queue, G_IO_IN);
|
||||
g_source_set_callback (kq_source, (GSourceFunc) g_kqueue_file_monitor_callback, NULL, NULL);
|
||||
g_source_attach (kq_source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
|
||||
}
|
||||
|
||||
G_UNLOCK (kq_lock);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
g_kqueue_file_monitor_cancel (GFileMonitor *monitor)
|
||||
{
|
||||
GKqueueFileMonitor *kqueue_monitor = G_KQUEUE_FILE_MONITOR (monitor);
|
||||
|
||||
/* We must hold the global lock before calling _kqsub_cancel. However, we
|
||||
* cannot call G_LOCK in _kqsub_cancel because it is also used by
|
||||
* g_kqueue_file_monitor_callback, which already holds the lock itself. */
|
||||
G_LOCK (kq_lock);
|
||||
|
||||
if (kqueue_monitor->sub_dir)
|
||||
{
|
||||
_kqsub_cancel (kqueue_monitor->sub_dir);
|
||||
_kqsub_free (kqueue_monitor->sub_dir);
|
||||
kqueue_monitor->sub_dir = NULL;
|
||||
}
|
||||
if (kqueue_monitor->sub_file)
|
||||
{
|
||||
_kqsub_cancel (kqueue_monitor->sub_file);
|
||||
_kqsub_free (kqueue_monitor->sub_file);
|
||||
kqueue_monitor->sub_file = NULL;
|
||||
}
|
||||
|
||||
G_UNLOCK (kq_lock);
|
||||
|
||||
#ifndef O_EVTONLY
|
||||
if (kqueue_monitor->fallback)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (kqueue_monitor->fallback, _fallback_callback, kqueue_monitor);
|
||||
g_file_monitor_cancel (kqueue_monitor->fallback);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (G_FILE_MONITOR_CLASS (g_kqueue_file_monitor_parent_class)->cancel)
|
||||
(*G_FILE_MONITOR_CLASS (g_kqueue_file_monitor_parent_class)->cancel) (monitor);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static kqueue_sub *
|
||||
_kqsub_new (gchar *filename, gchar *basename, GKqueueFileMonitor *mon, GFileMonitorSource *source)
|
||||
{
|
||||
kqueue_sub *sub;
|
||||
|
||||
sub = g_slice_new (kqueue_sub);
|
||||
sub->filename = filename;
|
||||
sub->basename = basename;
|
||||
sub->mon = mon;
|
||||
g_source_ref ((GSource *) source);
|
||||
sub->source = source;
|
||||
sub->fd = -1;
|
||||
sub->deps = NULL;
|
||||
sub->is_dir = 0;
|
||||
|
||||
return sub;
|
||||
}
|
||||
|
||||
static void
|
||||
_kqsub_free (kqueue_sub *sub)
|
||||
{
|
||||
g_assert (sub->deps == NULL);
|
||||
g_assert (sub->fd == -1);
|
||||
|
||||
g_source_unref ((GSource *) sub->source);
|
||||
g_free (sub->filename);
|
||||
g_free (sub->basename);
|
||||
g_slice_free (kqueue_sub, sub);
|
||||
}
|
||||
|
||||
static void
|
||||
_kqsub_cancel (kqueue_sub *sub)
|
||||
{
|
||||
/* WARNING: Before calling this function, you must hold a lock on kq_lock
|
||||
* or you will cause use-after-free in g_kqueue_file_monitor_callback. */
|
||||
|
||||
struct kevent ev;
|
||||
|
||||
/* Remove the event and close the file descriptor to automatically
|
||||
* delete pending events. */
|
||||
if (sub->fd != -1)
|
||||
{
|
||||
EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_DELETE, note_all (), 0, sub);
|
||||
if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1)
|
||||
{
|
||||
g_warning ("Unable to remove event for %s: %s", sub->filename, g_strerror (errno));
|
||||
}
|
||||
close (sub->fd);
|
||||
sub->fd = -1;
|
||||
}
|
||||
|
||||
_km_remove (sub);
|
||||
|
||||
if (sub->deps)
|
||||
{
|
||||
dl_free (sub->deps);
|
||||
sub->deps = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
_kqsub_start_watching (kqueue_sub *sub)
|
||||
{
|
||||
struct stat st;
|
||||
struct kevent ev;
|
||||
|
||||
sub->fd = open (sub->filename, O_KQFLAG);
|
||||
if (sub->fd == -1)
|
||||
return FALSE;
|
||||
|
||||
if (fstat (sub->fd, &st) == -1)
|
||||
{
|
||||
g_warning ("fstat failed for %s: %s", sub->filename, g_strerror (errno));
|
||||
close (sub->fd);
|
||||
sub->fd = -1;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
sub->is_dir = (st.st_mode & S_IFDIR) ? 1 : 0;
|
||||
if (sub->is_dir)
|
||||
{
|
||||
if (sub->deps)
|
||||
dl_free (sub->deps);
|
||||
|
||||
sub->deps = dl_listing (sub->filename);
|
||||
}
|
||||
|
||||
EV_SET (&ev, sub->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, note_all (), 0, sub);
|
||||
if (kevent (kq_queue, &ev, 1, NULL, 0, NULL) == -1)
|
||||
{
|
||||
g_warning ("Unable to add event for %s: %s", sub->filename, g_strerror (errno));
|
||||
close (sub->fd);
|
||||
sub->fd = -1;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
196
gio/kqueue/kqueue-helper.c
Normal file
196
gio/kqueue/kqueue-helper.c
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/*******************************************************************************
|
||||
Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <gio/glocalfile.h>
|
||||
#include <gio/glocalfilemonitor.h>
|
||||
#include <gio/gfile.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include "kqueue-helper.h"
|
||||
|
||||
typedef struct {
|
||||
kqueue_sub *sub;
|
||||
GFileMonitorSource *source;
|
||||
gboolean handle_deleted;
|
||||
} handle_ctx;
|
||||
|
||||
/**
|
||||
* handle_created:
|
||||
* @udata: a pointer to user data (#handle_context).
|
||||
* @path: file name of a new file.
|
||||
* @inode: inode number of a new file.
|
||||
*
|
||||
* A callback function for the directory diff calculation routine,
|
||||
* produces G_FILE_MONITOR_EVENT_CREATED event for a created file.
|
||||
**/
|
||||
static void
|
||||
handle_created (void *udata, const char *path, ino_t inode)
|
||||
{
|
||||
handle_ctx *ctx = NULL;
|
||||
gint64 now;
|
||||
gchar *fullname;
|
||||
struct stat st;
|
||||
|
||||
(void) inode;
|
||||
ctx = (handle_ctx *) udata;
|
||||
g_assert (udata != NULL);
|
||||
g_assert (ctx->sub != NULL);
|
||||
g_assert (ctx->source != NULL);
|
||||
|
||||
now = g_get_monotonic_time ();
|
||||
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CREATED, path,
|
||||
NULL, NULL, now);
|
||||
|
||||
/* Copied from ih_event_callback to report 'CHANGES_DONE_HINT' earlier. */
|
||||
fullname = g_build_filename (ctx->sub->filename, path, NULL);
|
||||
if (stat (fullname, &st) != 0 || !S_ISREG (st.st_mode) || st.st_nlink != 1)
|
||||
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT, path,
|
||||
NULL, NULL, now);
|
||||
g_free (fullname);
|
||||
}
|
||||
|
||||
/**
|
||||
* handle_deleted:
|
||||
* @udata: a pointer to user data (#handle_context).
|
||||
* @path: file name of the removed file.
|
||||
* @inode: inode number of the removed file.
|
||||
*
|
||||
* A callback function for the directory diff calculation routine,
|
||||
* produces G_FILE_MONITOR_EVENT_DELETED event for a deleted file.
|
||||
**/
|
||||
static void
|
||||
handle_deleted (void *udata, const char *path, ino_t inode)
|
||||
{
|
||||
handle_ctx *ctx = NULL;
|
||||
|
||||
(void) inode;
|
||||
ctx = (handle_ctx *) udata;
|
||||
g_assert (udata != NULL);
|
||||
g_assert (ctx->sub != NULL);
|
||||
g_assert (ctx->source != NULL);
|
||||
|
||||
if (!ctx->handle_deleted)
|
||||
return;
|
||||
|
||||
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_DELETED, path,
|
||||
NULL, NULL, g_get_monotonic_time ());
|
||||
}
|
||||
|
||||
/**
|
||||
* handle_moved:
|
||||
* @udata: a pointer to user data (#handle_context).
|
||||
* @from_path: file name of the source file.
|
||||
* @from_inode: inode number of the source file.
|
||||
* @to_path: file name of the replaced file.
|
||||
* @to_inode: inode number of the replaced file.
|
||||
*
|
||||
* A callback function for the directory diff calculation routine,
|
||||
* produces G_FILE_MONITOR_EVENT_RENAMED event on a move.
|
||||
**/
|
||||
static void
|
||||
handle_moved (void *udata,
|
||||
const char *from_path,
|
||||
ino_t from_inode,
|
||||
const char *to_path,
|
||||
ino_t to_inode)
|
||||
{
|
||||
handle_ctx *ctx = NULL;
|
||||
|
||||
(void) from_inode;
|
||||
(void) to_inode;
|
||||
|
||||
ctx = (handle_ctx *) udata;
|
||||
g_assert (udata != NULL);
|
||||
g_assert (ctx->sub != NULL);
|
||||
g_assert (ctx->source != NULL);
|
||||
|
||||
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_RENAMED,
|
||||
from_path, to_path, NULL, g_get_monotonic_time ());
|
||||
}
|
||||
|
||||
/**
|
||||
* handle_overwritten:
|
||||
* @data: a pointer to user data (#handle_context).
|
||||
* @path: file name of the overwritten file.
|
||||
* @node: inode number of the overwritten file.
|
||||
*
|
||||
* A callback function for the directory diff calculation routine,
|
||||
* produces G_FILE_MONITOR_EVENT_DELETED/CREATED event pair when
|
||||
* an overwrite occurs in the directory (see dep-list for details).
|
||||
**/
|
||||
static void
|
||||
handle_overwritten (void *udata, const char *path, ino_t inode)
|
||||
{
|
||||
handle_ctx *ctx = NULL;
|
||||
|
||||
(void) inode;
|
||||
ctx = (handle_ctx *) udata;
|
||||
g_assert (udata != NULL);
|
||||
g_assert (ctx->sub != NULL);
|
||||
g_assert (ctx->source != NULL);
|
||||
|
||||
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_DELETED,
|
||||
path, NULL, NULL, g_get_monotonic_time ());
|
||||
|
||||
g_file_monitor_source_handle_event (ctx->source, G_FILE_MONITOR_EVENT_CREATED,
|
||||
path, NULL, NULL, g_get_monotonic_time ());
|
||||
}
|
||||
|
||||
static const traverse_cbs cbs = {
|
||||
handle_created,
|
||||
handle_deleted,
|
||||
handle_moved,
|
||||
handle_overwritten,
|
||||
handle_moved,
|
||||
NULL, /* many added */
|
||||
NULL, /* many removed */
|
||||
NULL, /* names updated */
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
_kh_dir_diff (kqueue_sub *sub, gboolean handle_deleted)
|
||||
{
|
||||
dep_list *was;
|
||||
handle_ctx ctx;
|
||||
|
||||
memset (&ctx, 0, sizeof (handle_ctx));
|
||||
ctx.sub = sub;
|
||||
ctx.source = sub->source;
|
||||
ctx.handle_deleted = handle_deleted;
|
||||
|
||||
was = sub->deps;
|
||||
sub->deps = dl_listing (sub->filename);
|
||||
|
||||
dl_calculate (was, sub->deps, &cbs, &ctx);
|
||||
|
||||
dl_free (was);
|
||||
}
|
||||
60
gio/kqueue/kqueue-helper.h
Normal file
60
gio/kqueue/kqueue-helper.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/*******************************************************************************
|
||||
Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef __KQUEUE_HELPER_H
|
||||
#define __KQUEUE_HELPER_H
|
||||
|
||||
#include <gio/glocalfilemonitor.h>
|
||||
#include <gio/gfilemonitor.h>
|
||||
|
||||
#include "dep-list.h"
|
||||
|
||||
typedef struct _GKqueueFileMonitor GKqueueFileMonitor;
|
||||
|
||||
/**
|
||||
* kqueue_sub:
|
||||
* @mon: a pointer to the GKqueueFileMonitor which holds this subscription
|
||||
* @filename: a name of the file to monitor
|
||||
* @fd: the associated file descriptor (used by kqueue)
|
||||
*
|
||||
* Represents a subscription on a file or directory. To check whether a
|
||||
* subscription is active, check the fd field. If fd is not -1, it is an
|
||||
* active subscription which can emit events from kqueue.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
GKqueueFileMonitor *mon;
|
||||
GFileMonitorSource *source;
|
||||
gchar* filename;
|
||||
gchar* basename;
|
||||
int fd;
|
||||
dep_list* deps;
|
||||
int is_dir;
|
||||
} kqueue_sub;
|
||||
|
||||
gboolean _kqsub_start_watching (kqueue_sub *sub);
|
||||
void _kh_dir_diff (kqueue_sub *sub, gboolean handle_deleted);
|
||||
void _km_add_missing (kqueue_sub *sub);
|
||||
gboolean _km_scan_missing (kqueue_sub *check_this_sub_only);
|
||||
void _km_remove (kqueue_sub *sub);
|
||||
|
||||
#endif /* __KQUEUE_HELPER_H */
|
||||
179
gio/kqueue/kqueue-missing.c
Normal file
179
gio/kqueue/kqueue-missing.c
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/*******************************************************************************
|
||||
Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*******************************************************************************/
|
||||
|
||||
#include <glib.h>
|
||||
#include "glib-private.h"
|
||||
|
||||
#include "kqueue-helper.h"
|
||||
|
||||
|
||||
#define SCAN_MISSING_TIME 4 /* 1/4 Hz */
|
||||
|
||||
static gboolean km_debug_enabled = FALSE;
|
||||
#define KM_W if (km_debug_enabled) g_warning
|
||||
|
||||
static GSList *missing_subs_list = NULL;
|
||||
G_LOCK_DEFINE_STATIC (missing_lock);
|
||||
|
||||
static gboolean scan_missing_running = FALSE; /* must be accessed under @missing_lock */
|
||||
|
||||
|
||||
static gboolean
|
||||
_km_scan_missing_cb (gpointer user_data)
|
||||
{
|
||||
return _km_scan_missing (NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* _km_add_missing:
|
||||
* @sub: a #kqueue_sub
|
||||
*
|
||||
* Adds a subscription to the missing files list.
|
||||
**/
|
||||
void
|
||||
_km_add_missing (kqueue_sub *sub)
|
||||
{
|
||||
G_LOCK (missing_lock);
|
||||
if (g_slist_find (missing_subs_list, sub))
|
||||
{
|
||||
KM_W ("asked to add %s to missing list but it's already on the list!\n", sub->filename);
|
||||
G_UNLOCK (missing_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
KM_W ("adding %s to missing list\n", sub->filename);
|
||||
missing_subs_list = g_slist_prepend (missing_subs_list, sub);
|
||||
|
||||
if (!scan_missing_running)
|
||||
{
|
||||
GSource *source;
|
||||
scan_missing_running = TRUE;
|
||||
source = g_timeout_source_new_seconds (SCAN_MISSING_TIME);
|
||||
g_source_set_callback (source, _km_scan_missing_cb, NULL, NULL);
|
||||
g_source_attach (source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
|
||||
g_source_unref (source);
|
||||
}
|
||||
|
||||
G_UNLOCK (missing_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* _kh_file_appeared_cb:
|
||||
* @sub: a #kqueue_sub
|
||||
*
|
||||
* A callback function for kqueue-missing subsystem.
|
||||
*
|
||||
* Signals that a missing file has finally appeared in the filesystem.
|
||||
* Emits %G_FILE_MONITOR_EVENT_CREATED.
|
||||
**/
|
||||
static void
|
||||
_kh_file_appeared_cb (kqueue_sub *sub)
|
||||
{
|
||||
gint64 now = g_get_monotonic_time ();
|
||||
|
||||
g_assert (sub != NULL);
|
||||
g_assert (sub->filename);
|
||||
|
||||
if (!g_file_test (sub->filename, G_FILE_TEST_EXISTS))
|
||||
return;
|
||||
|
||||
g_file_monitor_source_handle_event (sub->source, G_FILE_MONITOR_EVENT_CREATED,
|
||||
sub->basename, NULL, NULL, now);
|
||||
g_file_monitor_source_handle_event (sub->source, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT,
|
||||
sub->basename, NULL, NULL, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* _km_scan_missing:
|
||||
* @user_data: unused
|
||||
*
|
||||
* The core missing files watching routine.
|
||||
*
|
||||
* Traverses through a list of missing files, tries to start watching each with
|
||||
* kqueue, removes the appropriate entry and invokes a user callback if the file
|
||||
* has appeared.
|
||||
*
|
||||
* Returns: %FALSE if no missing files left, %TRUE otherwise.
|
||||
**/
|
||||
gboolean
|
||||
_km_scan_missing (kqueue_sub *check_this_sub_only)
|
||||
{
|
||||
GSList *head;
|
||||
GSList *not_missing = NULL;
|
||||
gboolean retval = FALSE;
|
||||
|
||||
G_LOCK (missing_lock);
|
||||
|
||||
if (missing_subs_list)
|
||||
KM_W ("we have a job");
|
||||
|
||||
for (head = missing_subs_list; head; head = head->next)
|
||||
{
|
||||
kqueue_sub *sub = (kqueue_sub *) head->data;
|
||||
g_assert (sub != NULL);
|
||||
g_assert (sub->filename != NULL);
|
||||
|
||||
if (check_this_sub_only != NULL && sub != check_this_sub_only)
|
||||
continue;
|
||||
|
||||
if (_kqsub_start_watching (sub))
|
||||
{
|
||||
KM_W ("file %s now exists, starting watching", sub->filename);
|
||||
if (check_this_sub_only == NULL)
|
||||
_kh_file_appeared_cb (sub);
|
||||
not_missing = g_slist_prepend (not_missing, head);
|
||||
}
|
||||
}
|
||||
|
||||
for (head = not_missing; head; head = head->next)
|
||||
{
|
||||
GSList *link = (GSList *) head->data;
|
||||
missing_subs_list = g_slist_remove_link (missing_subs_list, link);
|
||||
}
|
||||
g_slist_free (not_missing);
|
||||
|
||||
if (missing_subs_list == NULL)
|
||||
{
|
||||
scan_missing_running = FALSE;
|
||||
retval = FALSE;
|
||||
}
|
||||
else
|
||||
retval = TRUE;
|
||||
|
||||
G_UNLOCK (missing_lock);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* _km_remove:
|
||||
* @sub: a #kqueue_sub
|
||||
*
|
||||
* Removes a subscription from a list of missing files.
|
||||
**/
|
||||
void
|
||||
_km_remove (kqueue_sub *sub)
|
||||
{
|
||||
G_LOCK (missing_lock);
|
||||
missing_subs_list = g_slist_remove (missing_subs_list, sub);
|
||||
G_UNLOCK (missing_lock);
|
||||
}
|
||||
13
gio/kqueue/meson.build
Normal file
13
gio/kqueue/meson.build
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
kqueue_sources = [
|
||||
'gkqueuefilemonitor.c',
|
||||
'kqueue-helper.c',
|
||||
'kqueue-missing.c',
|
||||
'dep-list.c',
|
||||
]
|
||||
|
||||
kqueue_lib = static_library('kqueue',
|
||||
sources : kqueue_sources,
|
||||
include_directories : [configinc, glibinc, gmoduleinc],
|
||||
dependencies : [gioenumtypes_dep],
|
||||
pic : true,
|
||||
c_args : gio_c_args)
|
||||
Loading…
Add table
Add a link
Reference in a new issue