Blame gio/kqueue/dep-list.c

Packit ae235b
/*******************************************************************************
Packit ae235b
  Copyright (c) 2011, 2012 Dmitry Matveev <me@dmitrymatveev.co.uk>
Packit ae235b
Packit ae235b
  Permission is hereby granted, free of charge, to any person obtaining a copy
Packit ae235b
  of this software and associated documentation files (the "Software"), to deal
Packit ae235b
  in the Software without restriction, including without limitation the rights
Packit ae235b
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
Packit ae235b
  copies of the Software, and to permit persons to whom the Software is
Packit ae235b
  furnished to do so, subject to the following conditions:
Packit ae235b
Packit ae235b
  The above copyright notice and this permission notice shall be included in
Packit ae235b
  all copies or substantial portions of the Software.
Packit ae235b
Packit ae235b
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
Packit ae235b
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Packit ae235b
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Packit ae235b
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
Packit ae235b
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
Packit ae235b
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
Packit ae235b
  THE SOFTWARE.
Packit ae235b
*******************************************************************************/
Packit ae235b
Packit ae235b
#include <glib.h>
Packit ae235b
Packit ae235b
#include <stdlib.h>  /* calloc */
Packit ae235b
#include <stdio.h>   /* printf */
Packit ae235b
#include <dirent.h>  /* opendir, readdir, closedir */
Packit ae235b
#include <string.h>  /* strcmp */
Packit ae235b
#include <assert.h>
Packit ae235b
Packit ae235b
#include "dep-list.h"
Packit ae235b
Packit ae235b
static gboolean kdl_debug_enabled = FALSE;
Packit ae235b
#define perror_msg if (kdl_debug_enabled) g_warning
Packit ae235b
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Print a list to stdout.
Packit ae235b
 *
Packit ae235b
 * @param[in] dl A pointer to a list.
Packit ae235b
 **/
Packit ae235b
void
Packit ae235b
dl_print (const dep_list *dl)
Packit ae235b
{
Packit ae235b
    while (dl != NULL) {
Packit ae235b
        printf ("%lld:%s ", (long long int) dl->inode, dl->path);
Packit ae235b
        dl = dl->next;
Packit ae235b
    }
Packit ae235b
    printf ("\n");
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Create a new list item.
Packit ae235b
 *
Packit ae235b
 * Create a new list item and initialize its fields.
Packit ae235b
 *
Packit ae235b
 * @param[in] path  A name of a file (the string is not copied!).
Packit ae235b
 * @param[in] inode A file's inode number.
Packit ae235b
 * @return A pointer to a new item or NULL in the case of error.
Packit ae235b
 **/
Packit ae235b
dep_list* dl_create (char *path, ino_t inode)
Packit ae235b
{
Packit ae235b
    dep_list *dl = calloc (1, sizeof (dep_list));
Packit ae235b
    if (dl == NULL) {
Packit ae235b
        perror_msg ("Failed to create a new dep-list item");
Packit ae235b
        return NULL;
Packit ae235b
    }
Packit ae235b
Packit ae235b
    dl->path = path;
Packit ae235b
    dl->inode = inode;
Packit ae235b
    return dl;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Create a shallow copy of a list.
Packit ae235b
 *
Packit ae235b
 * A shallow copy is a copy of a structure, but not the copy of the
Packit ae235b
 * contents. All data pointers ('path' in our case) of a list and its
Packit ae235b
 * shallow copy will point to the same memory.
Packit ae235b
 *
Packit ae235b
 * @param[in] dl A pointer to list to make a copy. May be NULL.
Packit ae235b
 * @return A shallow copy of the list.
Packit ae235b
 **/ 
Packit ae235b
dep_list*
Packit ae235b
dl_shallow_copy (const dep_list *dl)
Packit ae235b
{
Packit ae235b
    if (dl == NULL) {
Packit ae235b
        return NULL;
Packit ae235b
    }
Packit ae235b
Packit ae235b
    dep_list *head = calloc (1, sizeof (dep_list));
Packit ae235b
    if (head == NULL) {
Packit ae235b
        perror_msg ("Failed to allocate head during shallow copy");
Packit ae235b
        return NULL;
Packit ae235b
    }
Packit ae235b
Packit ae235b
    dep_list *cp = head;
Packit ae235b
    const dep_list *it = dl;
Packit ae235b
Packit ae235b
    while (it != NULL) {
Packit ae235b
        cp->path = it->path;
Packit ae235b
        cp->inode = it->inode;
Packit ae235b
        if (it->next) {
Packit ae235b
            cp->next = calloc (1, sizeof (dep_list));
Packit ae235b
            if (cp->next == NULL) {
Packit ae235b
                perror_msg ("Failed to allocate a new element during shallow copy");
Packit ae235b
                dl_shallow_free (head);
Packit ae235b
                return NULL;
Packit ae235b
            }
Packit ae235b
            cp = cp->next;
Packit ae235b
        }
Packit ae235b
        it = it->next;
Packit ae235b
    }
Packit ae235b
Packit ae235b
    return head;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Free the memory allocated for shallow copy.
Packit ae235b
 *
Packit ae235b
 * This function will free the memory used by a list structure, but
Packit ae235b
 * the list data will remain in the heap.
Packit ae235b
 *
Packit ae235b
 * @param[in] dl A pointer to a list. May be NULL.
Packit ae235b
 **/
Packit ae235b
void
Packit ae235b
dl_shallow_free (dep_list *dl)
Packit ae235b
{
Packit ae235b
    while (dl != NULL) {
Packit ae235b
        dep_list *ptr = dl;
Packit ae235b
        dl = dl->next;
Packit ae235b
        free (ptr);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Free the memory allocated for a list.
Packit ae235b
 *
Packit ae235b
 * This function will free all the memory used by a list: both
Packit ae235b
 * list structure and the list data.
Packit ae235b
 *
Packit ae235b
 * @param[in] dl A pointer to a list. May be NULL.
Packit ae235b
 **/
Packit ae235b
void
Packit ae235b
dl_free (dep_list *dl)
Packit ae235b
{
Packit ae235b
    while (dl != NULL) {
Packit ae235b
        dep_list *ptr = dl;
Packit ae235b
        dl = dl->next;
Packit ae235b
Packit ae235b
        free (ptr->path);
Packit ae235b
        free (ptr);
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Create a directory listing and return it as a list.
Packit ae235b
 *
Packit ae235b
 * @param[in] path A path to a directory.
Packit ae235b
 * @return A pointer to a list. May return NULL, check errno in this case.
Packit ae235b
 **/
Packit ae235b
dep_list*
Packit ae235b
dl_listing (const char *path)
Packit ae235b
{
Packit ae235b
    assert (path != NULL);
Packit ae235b
Packit ae235b
    dep_list *head = NULL;
Packit ae235b
    dep_list *prev = NULL;
Packit ae235b
    DIR *dir = opendir (path);
Packit ae235b
    if (dir != NULL) {
Packit ae235b
        struct dirent *ent;
Packit ae235b
Packit ae235b
        while ((ent = readdir (dir)) != NULL) {
Packit ae235b
            if (!strcmp (ent->d_name, ".") || !strcmp (ent->d_name, "..")) {
Packit ae235b
                continue;
Packit ae235b
            }
Packit ae235b
Packit ae235b
            if (head == NULL) {
Packit ae235b
                head = calloc (1, sizeof (dep_list));
Packit ae235b
                if (head == NULL) {
Packit ae235b
                    perror_msg ("Failed to allocate head during listing");
Packit ae235b
                    goto error;
Packit ae235b
                }
Packit ae235b
            }
Packit ae235b
Packit ae235b
            dep_list *iter = (prev == NULL) ? head : calloc (1, sizeof (dep_list));
Packit ae235b
            if (iter == NULL) {
Packit ae235b
                perror_msg ("Failed to allocate a new element during listing");
Packit ae235b
                goto error;
Packit ae235b
            }
Packit ae235b
Packit ae235b
            iter->path = strdup (ent->d_name);
Packit ae235b
            if (iter->path == NULL) {
Packit ae235b
                perror_msg ("Failed to copy a string during listing");
Packit ae235b
                goto error;
Packit ae235b
            }
Packit ae235b
Packit ae235b
            iter->inode = ent->d_ino;
Packit ae235b
            iter->next = NULL;
Packit ae235b
            if (prev) {
Packit ae235b
                prev->next = iter;
Packit ae235b
            }
Packit ae235b
            prev = iter;
Packit ae235b
        }
Packit ae235b
Packit ae235b
        closedir (dir);
Packit ae235b
    }
Packit ae235b
    return head;
Packit ae235b
Packit ae235b
error:
Packit ae235b
    if (dir != NULL) {
Packit ae235b
        closedir (dir);
Packit ae235b
    }
Packit ae235b
    dl_free (head);
Packit ae235b
    return NULL;
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Perform a diff on lists.
Packit ae235b
 *
Packit ae235b
 * This function performs something like a set intersection. The same items
Packit ae235b
 * will be removed from the both lists. Items are comapred by a filename.
Packit ae235b
 * 
Packit ae235b
 * @param[in,out] before A pointer to a pointer to a list. Will contain items
Packit ae235b
 *     which were not found in the 'after' list.
Packit ae235b
 * @param[in,out] after  A pointer to a pointer to a list. Will containt items
Packit ae235b
 *     which were not found in the 'before' list.
Packit ae235b
 **/
Packit ae235b
void
Packit ae235b
dl_diff (dep_list **before, dep_list **after)
Packit ae235b
{
Packit ae235b
    assert (before != NULL);
Packit ae235b
    assert (after != NULL);
Packit ae235b
Packit ae235b
    if (*before == NULL || *after == NULL) {
Packit ae235b
        return;
Packit ae235b
    }
Packit ae235b
Packit ae235b
    dep_list *before_iter = *before;
Packit ae235b
    dep_list *before_prev = NULL;
Packit ae235b
Packit ae235b
    while (before_iter != NULL) {
Packit ae235b
        dep_list *after_iter = *after;
Packit ae235b
        dep_list *after_prev = NULL;
Packit ae235b
Packit ae235b
        int matched = 0;
Packit ae235b
        while (after_iter != NULL) {
Packit ae235b
            if (strcmp (before_iter->path, after_iter->path) == 0) {
Packit ae235b
                matched = 1;
Packit ae235b
                /* removing the entry from the both lists */
Packit ae235b
                if (before_prev) {
Packit ae235b
                    before_prev->next = before_iter->next;
Packit ae235b
                } else {
Packit ae235b
                    *before = before_iter->next;
Packit ae235b
                }
Packit ae235b
Packit ae235b
                if (after_prev) {
Packit ae235b
                    after_prev->next = after_iter->next;
Packit ae235b
                } else {
Packit ae235b
                    *after = after_iter->next;
Packit ae235b
                }
Packit ae235b
                free (after_iter);
Packit ae235b
                break;
Packit ae235b
            }
Packit ae235b
            after_prev = after_iter;
Packit ae235b
            after_iter = after_iter->next;
Packit ae235b
        }
Packit ae235b
Packit ae235b
        dep_list *oldptr = before_iter;
Packit ae235b
        before_iter = before_iter->next;
Packit ae235b
        if (matched == 0) {
Packit ae235b
            before_prev = oldptr;
Packit ae235b
        } else {
Packit ae235b
            free (oldptr);
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Traverses two lists. Compares items with a supplied expression
Packit ae235b
 * and performs the passed code on a match. Removes the matched entries
Packit ae235b
 * from the both lists.
Packit ae235b
 **/
Packit ae235b
#define EXCLUDE_SIMILAR(removed_list, added_list, match_expr, matched_code) \
Packit ae235b
    assert (removed_list != NULL);                                      \
Packit ae235b
    assert (added_list != NULL);                                        \
Packit ae235b
                                                                        \
Packit ae235b
    dep_list *removed_list##_iter = *removed_list;                      \
Packit ae235b
    dep_list *removed_list##_prev = NULL;                               \
Packit ae235b
                                                                        \
Packit ae235b
    int productive = 0;                                                 \
Packit ae235b
                                                                        \
Packit ae235b
    while (removed_list##_iter != NULL) {                               \
Packit ae235b
        dep_list *added_list##_iter = *added_list;                      \
Packit ae235b
        dep_list *added_list##_prev = NULL;                             \
Packit ae235b
                                                                        \
Packit ae235b
        int matched = 0;                                                \
Packit ae235b
        while (added_list##_iter != NULL) {                             \
Packit ae235b
            if (match_expr) {                                           \
Packit ae235b
                matched = 1;                                            \
Packit ae235b
                ++productive;                                           \
Packit ae235b
                matched_code;                                           \
Packit ae235b
                                                                        \
Packit ae235b
                if (removed_list##_prev) {                              \
Packit ae235b
                    removed_list##_prev->next = removed_list##_iter->next; \
Packit ae235b
                } else {                                                \
Packit ae235b
                    *removed_list = removed_list##_iter->next;          \
Packit ae235b
                }                                                       \
Packit ae235b
                if (added_list##_prev) {                                \
Packit ae235b
                    added_list##_prev->next = added_list##_iter->next;  \
Packit ae235b
                } else {                                                \
Packit ae235b
                    *added_list = added_list##_iter->next;              \
Packit ae235b
                }                                                       \
Packit ae235b
                free (added_list##_iter);                               \
Packit ae235b
                break;                                                  \
Packit ae235b
            }                                                           \
Packit ae235b
            added_list##_iter = added_list##_iter->next;                \
Packit ae235b
        }                                                               \
Packit ae235b
        dep_list *oldptr = removed_list##_iter;                         \
Packit ae235b
        removed_list##_iter = removed_list##_iter->next;                \
Packit ae235b
        if (matched == 0) {                                             \
Packit ae235b
            removed_list##_prev = oldptr;                               \
Packit ae235b
        } else {                                                        \
Packit ae235b
            free (oldptr);                                              \
Packit ae235b
        }                                                               \
Packit ae235b
    }                                                                   \
Packit ae235b
    return (productive > 0);
Packit ae235b
Packit ae235b
Packit ae235b
#define cb_invoke(cbs, name, udata, ...) \
Packit ae235b
    do { \
Packit ae235b
        if (cbs->name) { \
Packit ae235b
            (cbs->name) (udata, ## __VA_ARGS__); \
Packit ae235b
        } \
Packit ae235b
    } while (0)
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Detect and notify about moves in the watched directory.
Packit ae235b
 *
Packit ae235b
 * A move is what happens when you rename a file in a directory, and
Packit ae235b
 * a new name is unique, i.e. you didnt overwrite any existing files
Packit ae235b
 * with this one.
Packit ae235b
 *
Packit ae235b
 * @param[in,out] removed  A list of the removed files in the directory.
Packit ae235b
 * @param[in,out] added    A list of the added files of the directory.
Packit ae235b
 * @param[in]     cbs      A pointer to #traverse_cbs, an user-defined set of 
Packit ae235b
 *     traverse callbacks.
Packit ae235b
 * @param[in]     udata    A pointer to the user-defined data.
Packit ae235b
 * @return 0 if no files were renamed, >0 otherwise.
Packit ae235b
**/
Packit ae235b
static int
Packit ae235b
dl_detect_moves (dep_list           **removed, 
Packit ae235b
                 dep_list           **added, 
Packit ae235b
                 const traverse_cbs  *cbs, 
Packit ae235b
                 void                *udata)
Packit ae235b
{
Packit ae235b
    assert (cbs != NULL);
Packit ae235b
Packit ae235b
     EXCLUDE_SIMILAR
Packit ae235b
        (removed, added,
Packit ae235b
         (removed_iter->inode == added_iter->inode),
Packit ae235b
         {
Packit ae235b
             cb_invoke (cbs, moved, udata,
Packit ae235b
                        removed_iter->path, removed_iter->inode,
Packit ae235b
                        added_iter->path, added_iter->inode);
Packit ae235b
         });
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Detect and notify about replacements in the watched directory.
Packit ae235b
 *
Packit ae235b
 * Consider you are watching a directory foo with the folloing files
Packit ae235b
 * insinde:
Packit ae235b
 *
Packit ae235b
 *    foo/bar
Packit ae235b
 *    foo/baz
Packit ae235b
 *
Packit ae235b
 * A replacement in a watched directory is what happens when you invoke
Packit ae235b
 *
Packit ae235b
 *    mv /foo/bar /foo/bar
Packit ae235b
 *
Packit ae235b
 * i.e. when you replace a file in a watched directory with another file
Packit ae235b
 * from the same directory.
Packit ae235b
 *
Packit ae235b
 * @param[in,out] removed  A list of the removed files in the directory.
Packit ae235b
 * @param[in,out] current  A list with the current contents of the directory.
Packit ae235b
 * @param[in]     cbs      A pointer to #traverse_cbs, an user-defined set of 
Packit ae235b
 *     traverse callbacks.
Packit ae235b
 * @param[in]     udata    A pointer to the user-defined data.
Packit ae235b
 * @return 0 if no files were renamed, >0 otherwise.
Packit ae235b
 **/
Packit ae235b
static int
Packit ae235b
dl_detect_replacements (dep_list           **removed,
Packit ae235b
                        dep_list           **current,
Packit ae235b
                        const traverse_cbs  *cbs,
Packit ae235b
                        void                *udata)
Packit ae235b
{
Packit ae235b
    assert (cbs != NULL);
Packit ae235b
Packit ae235b
    EXCLUDE_SIMILAR
Packit ae235b
        (removed, current,
Packit ae235b
         (removed_iter->inode == current_iter->inode),
Packit ae235b
         {
Packit ae235b
            cb_invoke (cbs, replaced, udata,
Packit ae235b
                        removed_iter->path, removed_iter->inode,
Packit ae235b
                        current_iter->path, current_iter->inode);
Packit ae235b
         });
Packit ae235b
}
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Detect and notify about overwrites in the watched directory.
Packit ae235b
 *
Packit ae235b
 * Consider you are watching a directory foo with a file inside:
Packit ae235b
 *
Packit ae235b
 *    foo/bar
Packit ae235b
 *
Packit ae235b
 * And you also have a directory tmp with a file 1:
Packit ae235b
 * 
Packit ae235b
 *    tmp/1
Packit ae235b
 *
Packit ae235b
 * You do not watching directory tmp.
Packit ae235b
 *
Packit ae235b
 * An overwrite in a watched directory is what happens when you invoke
Packit ae235b
 *
Packit ae235b
 *    mv /tmp/1 /foo/bar
Packit ae235b
 *
Packit ae235b
 * i.e. when you overwrite a file in a watched directory with another file
Packit ae235b
 * from the another directory.
Packit ae235b
 *
Packit ae235b
 * @param[in,out] previous A list with the previous contents of the directory.
Packit ae235b
 * @param[in,out] current  A list with the current contents of the directory.
Packit ae235b
 * @param[in]     cbs      A pointer to #traverse_cbs, an user-defined set of 
Packit ae235b
 *     traverse callbacks.
Packit ae235b
 * @param[in]     udata    A pointer to the user-defined data.
Packit ae235b
 * @return 0 if no files were renamed, >0 otherwise.
Packit ae235b
 **/
Packit ae235b
static int
Packit ae235b
dl_detect_overwrites (dep_list           **previous,
Packit ae235b
                      dep_list           **current,
Packit ae235b
                      const traverse_cbs  *cbs,
Packit ae235b
                      void                *udata)
Packit ae235b
{
Packit ae235b
    assert (cbs != NULL);
Packit ae235b
Packit ae235b
    EXCLUDE_SIMILAR
Packit ae235b
        (previous, current,
Packit ae235b
         (strcmp (previous_iter->path, current_iter->path) == 0
Packit ae235b
          && previous_iter->inode != current_iter->inode),
Packit ae235b
         {
Packit ae235b
             cb_invoke (cbs, overwritten, udata, current_iter->path, current_iter->inode);
Packit ae235b
         });
Packit ae235b
}
Packit ae235b
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Traverse a list and invoke a callback for each item.
Packit ae235b
 * 
Packit ae235b
 * @param[in] list  A #dep_list.
Packit ae235b
 * @param[in] cb    A #single_entry_cb callback function.
Packit ae235b
 * @param[in] udata A pointer to the user-defined data.
Packit ae235b
 **/
Packit ae235b
static void 
Packit ae235b
dl_emit_single_cb_on (dep_list        *list,
Packit ae235b
                      single_entry_cb  cb,
Packit ae235b
                      void            *udata)
Packit ae235b
{
Packit ae235b
    while (cb && list != NULL) {
Packit ae235b
        (cb) (udata, list->path, list->inode);
Packit ae235b
        list = list->next;
Packit ae235b
    }
Packit ae235b
}
Packit ae235b
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * Recognize all the changes in the directory, invoke the appropriate callbacks.
Packit ae235b
 *
Packit ae235b
 * This is the core function of directory diffing submodule.
Packit ae235b
 *
Packit ae235b
 * @param[in] before The previous contents of the directory.
Packit ae235b
 * @param[in] after  The current contents of the directory.
Packit ae235b
 * @param[in] cbs    A pointer to user callbacks (#traverse_callbacks).
Packit ae235b
 * @param[in] udata  A pointer to user data.
Packit ae235b
 **/
Packit ae235b
void
Packit ae235b
dl_calculate (dep_list           *before,
Packit ae235b
              dep_list           *after,
Packit ae235b
              const traverse_cbs *cbs,
Packit ae235b
              void               *udata)
Packit ae235b
{
Packit ae235b
    assert (cbs != NULL);
Packit ae235b
Packit ae235b
    int need_update = 0;
Packit ae235b
Packit ae235b
    dep_list *was = dl_shallow_copy (before);
Packit ae235b
    dep_list *pre = dl_shallow_copy (before);
Packit ae235b
    dep_list *now = dl_shallow_copy (after);
Packit ae235b
    dep_list *lst = dl_shallow_copy (after);
Packit ae235b
Packit ae235b
    dl_diff (&was, &now;; 
Packit ae235b
Packit ae235b
    need_update += dl_detect_moves (&was, &now, cbs, udata);
Packit ae235b
    need_update += dl_detect_replacements (&was, &lst, cbs, udata);
Packit ae235b
    dl_detect_overwrites (&pre, &lst, cbs, udata);
Packit ae235b
 
Packit ae235b
    if (need_update) {
Packit ae235b
        cb_invoke (cbs, names_updated, udata);
Packit ae235b
    }
Packit ae235b
Packit ae235b
    dl_emit_single_cb_on (was, cbs->removed, udata);
Packit ae235b
    dl_emit_single_cb_on (now, cbs->added, udata);
Packit ae235b
Packit ae235b
    cb_invoke (cbs, many_added, udata, now);
Packit ae235b
    cb_invoke (cbs, many_removed, udata, was);
Packit ae235b
    
Packit ae235b
    dl_shallow_free (lst);
Packit ae235b
    dl_shallow_free (now);
Packit ae235b
    dl_shallow_free (pre);
Packit ae235b
    dl_shallow_free (was);
Packit ae235b
}
Packit ae235b